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 }