1 // Written in the D programming language. 2 3 /** 4 This module contains a set of various useful helpers for miscellaneous stuff. 5 6 Copyright: LightHouse Software, 2021 7 License: $(HTTP https://github.com/aquaratixc/ESL-License, Experimental Software License 1.0). 8 Authors: Oleg Bakharev, 9 Ilya Pertsev 10 */ 11 module styx2000.extrautil.mischelpers; 12 13 private 14 { 15 import std.path : baseName; 16 import std.file : DirEntry, SpanMode; 17 import std.range : chunks; 18 import std.stdio : File; 19 import std.string : format, strip; 20 21 import styx2000.extrautil.casts; 22 import styx2000.extrautil.siphash : hash8; 23 import styx2000.extrautil.styxmessage : StyxMessage; 24 25 import styx2000.protoconst.permissions; 26 27 import styx2000.protobj; 28 } 29 30 /// Create Qid object from Qid type, version and path 31 auto createQid(STYX_QID_TYPE type = STYX_QID_TYPE.QTFILE, uint vers = 0, ulong path = 0) 32 { 33 return new Qid(type, vers, path); 34 } 35 36 /// Create Qid object from DirEntry 37 auto createQid(DirEntry de) 38 { 39 return new Qid( 40 (de.isDir) ? STYX_QID_TYPE.QTDIR : STYX_QID_TYPE.QTFILE, 41 cast(uint) de.timeLastModified.toUnixTime, 42 hash8(de.name) 43 ); 44 } 45 46 /// Convenient helper for creating Qid from path string 47 auto createQid(string path) 48 { 49 return createQid( 50 DirEntry(path) 51 ); 52 } 53 54 /// Create Stat object from DirEntry 55 auto createStat(DirEntry de, ushort type = 0, ushort dev = 0, string uid = "", string gid = "", string muid = "") 56 { 57 auto qid = createQid(de); 58 return new Stat( 59 dev, 60 type, 61 qid, 62 new Perm((qid.getType << 24) | (de.attributes & 0x1ff)), 63 cast(uint) de.timeLastAccessed.toUnixTime, 64 cast(uint) de.timeLastModified.toUnixTime, 65 (de.isDir) ? 0 : de.size, 66 baseName(de.name), 67 uid, 68 gid, 69 muid 70 ); 71 } 72 73 74 /// Convenient helper for creating Stat object from path string 75 auto createStat(string path, ushort type = 0, ushort dev = 0, string uid = "", string gid = "", string muid = "") 76 { 77 return createStat(DirEntry(path), type, dev, uid, gid, muid); 78 } 79 80 81 /// Translate Qid to his string representation (string are the same as in Plan 9) 82 auto toPlan9Qid(T : Qid)(T qid) 83 { 84 string type; 85 86 final switch (qid.getType) with (STYX_QID_TYPE) 87 { 88 case QTDIR: 89 type = `d`; 90 break; 91 case QTAPPEND: 92 type = `a`; 93 break; 94 case QTEXCL: 95 type = `l`; 96 break; 97 case QTAUTH: 98 type = `u`; 99 break; 100 case QTTMP: 101 type = `t`; // ??? 102 break; 103 case QTFILE: 104 type = ` `; 105 break; 106 } 107 108 return format( 109 `(%0.16x %d %s)`, 110 qid.getPath, 111 qid.getVers, 112 type, 113 ); 114 } 115 116 117 /// Translate Nwname to his string representation (string are the same as in Plan 9) 118 auto toPlan9Nwname(Nwname nwname) 119 { 120 string representation = `nwname `; 121 122 auto numberOfNames = nwname.countOfNames ; 123 124 if (numberOfNames == 0) 125 { 126 representation ~= `0 `; 127 } 128 else 129 { 130 representation ~= format(`%d `, numberOfNames); 131 132 foreach (i, name; nwname.getNwname) 133 { 134 representation ~= format(`%d:%s `, i, name); 135 } 136 } 137 138 return representation; 139 } 140 141 142 /// Translate Nwqid to his string representation (string are the same as in Plan 9) 143 auto toPlan9Nwqid(Nwqid nwqid) 144 { 145 string representation = `nwqid `; 146 147 auto numberOfQids = nwqid.countOfQids; 148 149 if (numberOfQids == 0) 150 { 151 representation ~= `0 `; 152 } 153 else 154 { 155 representation ~= format(`%d `, numberOfQids); 156 157 foreach (i, qid; nwqid.getNwqid) 158 { 159 representation ~= format(`%d:%s `, i, qid.toPlan9Qid); 160 } 161 } 162 163 return representation; 164 } 165 166 167 /// Translate permission to his string representation (string are the same as in Plan 9) 168 auto toPlan9Permissions(Perm perm) 169 { 170 return format(`%0.16o`, perm.getPerm); 171 } 172 173 174 /// Translate Stat to his string representation (string are the same as in Plan 9) 175 auto toPlan9Stat(Stat stat) 176 { 177 return format( 178 `'%s' '%s' '%s' '%s' q %s m %s at %d mt %d l %d t %d d %d`, 179 stat.getName, 180 stat.getUid, 181 stat.getGid, 182 stat.getMuid, 183 stat.getQid.toPlan9Qid, 184 stat.getMode.toPlan9Permissions, 185 stat.getAtime, 186 stat.getMtime, 187 stat.getLength, 188 stat.getType, 189 stat.getDev 190 ); 191 } 192 193 194 /// Translate Plan 9 Message to standard output form in Plan 9 195 auto toPlan9Message(StyxMessage msg) 196 { 197 string representation; 198 199 // if length of message >= 3 - it has Plan 9 string repressentation 200 // if not - empty string (incorrect message) 201 if (msg.length >= 3) 202 { 203 auto tag = msg[2].toTag.getTag; 204 205 final switch (msg[1].toType.getType) with (STYX_MESSAGE_TYPE) 206 { 207 /// T-Messages 208 209 case T_VERSION: 210 representation = format( 211 `Tversion tag %d msize %d version '%s'`, 212 tag, 213 msg[3].toMsize.getMsize, 214 msg[4].toVersion.getVersion 215 ); 216 break; 217 case T_AUTH: 218 uint afid = msg[3].toAfid.getAfid; 219 representation = format( 220 `Tauth tag %d afid uname '%s' aname '%s'`, 221 tag, 222 cast(int) ((afid == uint.max) ? -1 : afid), 223 msg[4].toUname.getUname, 224 msg[5].toAname.getAname 225 ); 226 break; 227 case T_FLUSH: 228 representation = format( 229 `Tflush oldtag %d`, 230 msg[3].toOldTag.getTag 231 ); 232 break; 233 case T_ATTACH: 234 uint afid = msg[4].toAfid.getAfid; 235 representation = format( 236 `Tattach tag %d fid %d afid %d uname '%s' aname '%s'`, 237 tag, 238 msg[3].toFid.getFid, 239 cast(int) ((afid == uint.max) ? -1 : afid), 240 msg[5].toUname.getUname, 241 msg[6].toAname.getAname 242 ); 243 break; 244 case T_WALK: 245 representation = format( 246 `Twalk tag %d fid %d newfid %d %s`, 247 tag, 248 msg[3].toFid.getFid, 249 msg[4].toNewFid.getFid, 250 msg[5].toNwname.toPlan9Nwname, 251 ); 252 break; 253 case T_OPEN: 254 representation = format( 255 `Topen tag %d fid %d mode %d`, 256 tag, 257 msg[3].toFid.getFid, 258 cast(uint) msg[4].toMode.getMode 259 ); 260 break; 261 case T_CREATE: 262 representation = format( 263 `Tcreate tag %d fid %d '%s' m %s mode %d`, 264 tag, 265 msg[3].toFid.getFid, 266 msg[4].toName.getName, 267 msg[5].toPerm.toPlan9Permissions, 268 cast(uint) msg[6].toMode.getMode 269 ); 270 break; 271 case T_READ: 272 representation = format( 273 `Tread tag %d fid %d offset %d count %d`, 274 tag, 275 msg[3].toFid.getFid, 276 msg[4].toOffset.getOffset, 277 msg[5].toCount.getCount, 278 ); 279 break; 280 case T_WRITE: 281 uint count = msg[5].toCount.getCount; 282 representation = format( 283 `Twrite tag %d fid %d offset %d count %d '%s'`, 284 tag, 285 msg[3].toFid.getFid, 286 msg[4].toOffset.getOffset, 287 count, 288 msg[6].toData.toPlan9Chunk(count) 289 ); 290 break; 291 case T_CLUNK: 292 representation = format( 293 `Tclunk tag %d fid %d`, 294 tag, 295 msg[3].toFid.getFid, 296 ); 297 break; 298 case T_REMOVE: 299 representation = format( 300 `Tremove tag %d fid %d`, 301 tag, 302 msg[3].toFid.getFid, 303 ); 304 break; 305 case T_STAT: 306 representation = format( 307 `Tstat tag %d fid %d`, 308 tag, 309 msg[3].toFid.getFid, 310 ); 311 break; 312 case T_WSTAT: 313 representation = format( 314 `Twstat tag %d fid %d stat %s`, 315 tag, 316 msg[3].toFid.getFid, 317 msg[4].toStat.toPlan9Stat 318 ); 319 break; 320 321 /// R-Messages 322 323 case R_VERSION: 324 representation = format( 325 `Rversion tag %d msize %d version '%s'`, 326 tag, 327 msg[3].toMsize.getMsize, 328 msg[4].toVersion.getVersion 329 ); 330 break; 331 case R_AUTH: 332 representation = format( 333 `Rauth tag %d aqid %s`, 334 tag, 335 msg[3].toAqid.toPlan9Qid 336 ); 337 break; 338 case R_FLUSH: 339 representation = format( 340 `Rflush tag %d`, 341 tag, 342 ); 343 break; 344 case R_ERROR: 345 representation = format( 346 `Rerror tag %d ename %s`, 347 tag, 348 msg[3].toEname.getName 349 ); 350 break; 351 case R_ATTACH: 352 representation = format( 353 `Rattach tag %d qid %s`, 354 tag, 355 msg[3].toQid.toPlan9Qid 356 ); 357 break; 358 case R_WALK: 359 representation = format( 360 `Rwalk tag %d %s`, 361 tag, 362 msg[3].toNwqid.toPlan9Nwqid 363 ); 364 break; 365 case R_OPEN: 366 representation = format( 367 `Ropen tag %d qid %s iounit %d`, 368 tag, 369 msg[3].toQid.toPlan9Qid, 370 msg[4].toIounit.getUnit, 371 ); 372 break; 373 case R_CREATE: 374 representation = format( 375 `Rcreate tag %d qid %s iounit %d`, 376 tag, 377 msg[3].toQid.toPlan9Qid, 378 msg[4].toIounit.getUnit, 379 ); 380 break; 381 case R_READ: 382 uint count = msg[3].toCount.getCount; 383 representation = format( 384 `Rread tag %d count %d '%s'`, 385 tag, 386 count, 387 msg[4].toData.toPlan9Chunk(count) 388 ); 389 break; 390 case R_WRITE: 391 representation = format( 392 `Rwrite tag %d count %d`, 393 tag, 394 msg[3].toCount.getCount, 395 ); 396 break; 397 case R_CLUNK: 398 representation = format( 399 `Rclunk tag %d`, 400 tag, 401 ); 402 break; 403 case R_REMOVE: 404 representation = format( 405 `Rremove tag %d`, 406 tag 407 ); 408 break; 409 case R_STAT: 410 representation = format( 411 `Rstat tag %d stat %s`, 412 tag, 413 msg[3].toStat.toPlan9Stat, 414 ); 415 break; 416 case R_WSTAT: 417 representation = format( 418 `Rwstat tag %d`, 419 tag 420 ); 421 break; 422 } 423 } 424 425 return representation; 426 } 427 428 429 /// Translate data chunks to their string representation (string are the same as in Plan 9) 430 auto toPlan9Chunk(Data data, uint count) 431 { 432 // raw chunk for pretty printing 433 enum RAW_CHUNK_SIZE = 16 * 4; 434 ubyte[] bytes; 435 436 string representation; 437 if (count != 0) 438 { 439 bytes = data.getData[0..count]; 440 } 441 442 if (bytes.length > RAW_CHUNK_SIZE) 443 { 444 bytes = bytes[0..RAW_CHUNK_SIZE]; 445 } 446 447 foreach (e; bytes.chunks(4)) 448 { 449 foreach (b; e) 450 { 451 representation ~= format(`%0.2x`, b); 452 } 453 representation ~= " "; 454 } 455 456 return representation.strip; 457 } 458 459 460 /// Translate integer mode (such as 0755) to readable permission string (e.g drwxrwxr-x for 0755) 461 auto toPlan9Mode(uint mode) 462 { 463 enum BITS = ["---", "--x", "-w-", "-wx", "r--", "r-x", "rw-", "rwx"]; 464 465 auto bits(uint s) 466 { 467 return BITS[(mode >> s) & 7]; 468 } 469 470 string d = "-"; 471 472 if (mode & STYX_FILE_PERMISSION.DMDIR) 473 { 474 d = "d"; 475 } 476 477 if (mode & STYX_FILE_PERMISSION.DMAPPEND) 478 { 479 d = "a"; 480 } 481 482 if (mode & STYX_FILE_PERMISSION.DMAUTH) 483 { 484 d = "A"; 485 } 486 487 if (mode & STYX_FILE_PERMISSION.DMEXCL) 488 { 489 d ~= "l"; 490 } 491 else 492 { 493 d ~= "-"; 494 } 495 496 return format("%s%s%s%s", d, bits(6), bits(3), bits(0)); 497 } 498 499 500 /// Translates Perm object to permission string (e.g drwxrwxr-x) 501 auto toPlan9Mode(Perm perm) 502 { 503 return toPlan9Mode(perm.getPerm); 504 } 505 506 507 /// Translates string mode (e.g drwxrwxr-x) to integer value 508 auto fromPlan9Mode(string mode) 509 { 510 /// bitmaps for all possible permissions 511 enum BITS = ["---" : 0, "--x" : 1, "-w-" : 2, "-wx" : 3, "r--" : 4, "r-x" : 5, "rw-" : 6, "rwx" : 7]; 512 uint pms; 513 514 if (mode.length >= 11) 515 { 516 /// normalizing string (length must be 11 symbols long) 517 mode = mode[0..11]; 518 519 /// recognizing first character 520 switch (mode[0]) 521 { 522 case 'd': 523 pms |= STYX_FILE_PERMISSION.DMDIR; 524 break; 525 case 'a': 526 pms |= STYX_FILE_PERMISSION.DMAPPEND; 527 break; 528 case 'A': 529 pms |= STYX_FILE_PERMISSION.DMAUTH; 530 break; 531 default: 532 break; 533 } 534 535 /// recognizing second character 536 if (mode[1] == 'l') 537 { 538 pms |= STYX_FILE_PERMISSION.DMEXCL; 539 } 540 541 542 /// replace two symbols from the beginning 543 mode = mode[2..$]; 544 545 int i = 2; 546 547 while (i >= 0) 548 { 549 auto m = mode[0..3]; 550 pms |= BITS[m] << (3 * i); 551 mode = mode[3..$]; 552 i--; 553 } 554 } 555 else 556 { 557 throw new Exception("The permission string must be 11 characters long"); 558 } 559 560 return pms; 561 } 562 563 /// Translates string mode (e.g drwxrwxr-x) to Perm object 564 auto fromPlan9Permissions(string mode) 565 { 566 return new Perm( 567 fromPlan9Mode(mode) 568 ); 569 }