1
2
3
4
5
6 using System;
7 using System.Collections;
8 using System.IO;
9 using System.Text;
10 using System.Security;
11
12 class Credentials
13 {
14 static Credentials() : instance(new Credentials())
15 {
16 }
17 public static Credentials& Instance()
18 {
19 return *instance;
20 }
21 private Credentials()
22 {
23 users = GetUsers();
24 groups = GetGroups();
25 }
26 public string GetUserName(int uid) const
27 {
28 User* user = users.GetUser(uid);
29 if (user != null)
30 {
31 return user->Name();
32 }
33 return string();
34 }
35 public string GetGroupName(int gid) const
36 {
37 Group* group = groups.GetGroup(gid);
38 if (group != null)
39 {
40 return group->Name();
41 }
42 return string();
43 }
44 private static UniquePtr<Credentials> instance;
45 private Users users;
46 private Groups groups;
47 }
48
49 enum Fields
50 {
51 none = 0, fs = 1 << 0, inode = 1 << 1, access = 1 << 2, nlinks = 1 << 3, user = 1 << 4, uid = 1 << 5, group = 1 << 6, gid = 1 << 7, size = 1 << 8,
52 ctime = 1 << 9, mtime = 1 << 10, atime = 1 << 11, name = 1 << 12
53 }
54
55 class FieldValue
56 {
57 public nothrow FieldValue(const ustring& text_, Fields field_) : text(text_), length(cast<int>(text.Length())), field(field_)
58 {
59 }
60 public ustring text;
61 public int length;
62 public Fields field;
63 }
64
65 enum FileKind
66 {
67 regular, executable, directory, fifo
68 }
69
70 enum Flags
71 {
72 none = 0, all = 1 << 0, longFormat = 1 << 1, humanReadableSize = 1 << 2, hasColors = 1 << 3, reverse = 1 << 4
73 }
74
75 FieldValue MakeIntFieldValue(int n, Fields field)
76 {
77 return FieldValue(ToUtf32(ToString(n)), field);
78 }
79
80 FieldValue MakeStringFieldValue(const string& s, Fields field)
81 {
82 return FieldValue(ToUtf32(s), field);
83 }
84
85 FieldValue MakeUStringFieldValue(const ustring& s, Fields field)
86 {
87 return FieldValue(s, field);
88 }
89
90 FieldValue MakeTimeFieldValue(const DateTime& dateTime, Fields field)
91 {
92 return FieldValue(ToUtf32(dateTime.ToString()), field);
93 }
94
95 FieldValue MakeSizeFieldValue(long size, bool humanReadableSize)
96 {
97 if (!humanReadableSize)
98 {
99 return FieldValue(ToUtf32(ToString(size)), Fields.size);
100 }
101 else
102 {
103 long g = size / 1024 / 1024 / 1024;
104 if (g > 0)
105 {
106 long s = (size - 1) / 1024 / 1024 / 1024 + 1;
107 return FieldValue(ToUtf32(ToString(s) + "G"), Fields.size);
108 }
109 else
110 {
111 long m = size / 1024 / 1024;
112 if (m > 0)
113 {
114 long s = (size - 1) / 1024 / 1024 + 1;
115 return FieldValue(ToUtf32(ToString(s) + "M"), Fields.size);
116 }
117 else
118 {
119 long k = size / 1024;
120 if (k > 0)
121 {
122 long s = (size - 1) / 1024 + 1;
123 return FieldValue(ToUtf32(ToString(s) + "K"), Fields.size);
124 }
125 else
126 {
127 return FieldValue(ToUtf32(ToString(size)), Fields.size);
128 }
129 }
130 }
131 }
132 }
133
134 string AccessStr(Access access, bool setUIDorSetGIDBit)
135 {
136 string accessStr;
137 if ((access & Access.read) != Access.none)
138 {
139 accessStr.Append('r');
140 }
141 else
142 {
143 accessStr.Append('-');
144 }
145 if ((access & Access.write) != Access.none)
146 {
147 accessStr.Append('w');
148 }
149 else
150 {
151 accessStr.Append('-');
152 }
153 if (setUIDorSetGIDBit)
154 {
155 accessStr.Append('s');
156 }
157 else
158 {
159 if ((access & Access.execute) != Access.none)
160 {
161 accessStr.Append('x');
162 }
163 else
164 {
165 accessStr.Append('-');
166 }
167 }
168 return accessStr;
169 }
170
171 FieldValue MakeAccessFieldValue(const FileStatus& fileStatus)
172 {
173 string accessStr;
174 if (fileStatus.fileType == FileType.directory)
175 {
176 accessStr.Append('d');
177 }
178 else
179 {
180 accessStr.Append('-');
181 }
182 accessStr.Append(AccessStr(fileStatus.ownerAccess, fileStatus.setUID));
183 accessStr.Append(AccessStr(fileStatus.groupAccess, fileStatus.setGID));
184 accessStr.Append(AccessStr(fileStatus.otherAccess, false));
185 return FieldValue(ToUtf32(accessStr), Fields.access);
186 }
187
188 FormatJustify GetFieldJustification(Fields field)
189 {
190 switch (field)
191 {
192 case Fields.fs:
193 case Fields.inode:
194 case Fields.nlinks:
195 case Fields.uid:
196 case Fields.gid:
197 case Fields.size:
198 {
199 return FormatJustify.right;
200 }
201 }
202 return FormatJustify.left;
203 }
204
205 nothrow Fields ShortFields()
206 {
207 return Fields.name;
208 }
209
210 nothrow Fields LongFields()
211 {
212 return cast<Fields>(Fields.access | Fields.nlinks | Fields.user | Fields.group | Fields.size | Fields.mtime | Fields.name);
213 }
214
215 class FileInfo
216 {
217 public nothrow FileInfo(const FileStatus& fileStatus_, const DirectoryEntry& entry_) : fileStatus(fileStatus_), entry(entry_)
218 {
219 }
220 public int FieldWidth(int fieldNr) const
221 {
222 if (fieldNr >= 0 && fieldNr < fieldValues.Count())
223 {
224 return fieldValues[fieldNr].length;
225 }
226 return 0;
227 }
228 public FileKind Kind() const
229 {
230 if (fileStatus.fileType == FileType.directory) return FileKind.directory;
231 if (fileStatus.fileType == FileType.fifo) return FileKind.fifo;
232 if (fileStatus.fileType == FileType.regular)
233 {
234 if ((fileStatus.ownerAccess & Access.execute) != Access.none)
235 {
236 return FileKind.executable;
237 }
238 }
239 return FileKind.regular;
240 }
241 public const FieldValue& GetFieldValue(int fieldIndex) const
242 {
243 return fieldValues[fieldIndex];
244 }
245 public FileStatus fileStatus;
246 public DirectoryEntry entry;
247 public List<FieldValue> fieldValues;
248 public long nameFieldIndex;
249 public ustring userName;
250 public ustring groupName;
251 }
252
253 enum SortField
254 {
255 name, inode, nlinks, user, size, time
256 }
257
258 class SortBy : Rel<FileInfo>
259 {
260 public SortBy(SortField sortField_) : sortField(sortField_)
261 {
262 }
263 public bool operator()(const FileInfo& left, const FileInfo& right) const
264 {
265 switch (sortField)
266 {
267 case SortField.inode:
268 {
269 if (left.fileStatus.inodeNumber < right.fileStatus.inodeNumber) return true;
270 if (left.fileStatus.inodeNumber > right.fileStatus.inodeNumber) return false;
271 break;
272 }
273 case SortField.nlinks:
274 {
275 if (left.fileStatus.nlinks < right.fileStatus.nlinks) return true;
276 if (left.fileStatus.nlinks > right.fileStatus.nlinks) return false;
277 break;
278 }
279 case SortField.user:
280 {
281 if (left.userName < right.userName) return true;
282 if (left.userName > right.userName) return false;
283 break;
284 }
285 case SortField.size:
286 {
287 if (left.fileStatus.fileSize > right.fileStatus.fileSize) return true;
288 if (left.fileStatus.fileSize < right.fileStatus.fileSize) return false;
289 break;
290 }
291 case SortField.time:
292 {
293 if (left.fileStatus.mtime < right.fileStatus.mtime) return true;
294 if (left.fileStatus.mtime > right.fileStatus.mtime) return false;
295 break;
296 }
297 }
298 return left.fieldValues[left.nameFieldIndex].text < right.fieldValues[right.nameFieldIndex].text;
299 }
300 private SortField sortField;
301 }
302
303 void Sort(List<FileInfo>& fileInfos, SortField sortField)
304 {
305 System.Sort(fileInfos, SortBy(sortField));
306 }
307
308 string GetColorString(const FileInfo& fileInfo, bool hasColors)
309 {
310 if (!hasColors)
311 {
312 return string();
313 }
314 switch (fileInfo.Kind())
315 {
316 case FileKind.regular:
317 {
318 return SetColors(ConsoleColor.white, DefaultConsoleBackColor());
319 }
320 case FileKind.executable:
321 {
322 return SetColors(ConsoleColor.green, DefaultConsoleBackColor());
323 }
324 case FileKind.directory:
325 {
326 return SetColors(ConsoleColor.cyan, DefaultConsoleBackColor());
327 }
328 case FileKind.fifo:
329 {
330 return SetColors(ConsoleColor.red, DefaultConsoleBackColor());
331 }
332 }
333 return SetColors(ConsoleColor.white, DefaultConsoleBackColor());
334 }
335
336 string ResetColorString(bool hasColors)
337 {
338 if (!hasColors)
339 {
340 return string();
341 }
342 return ResetColors();
343 }
344
345 void MakeFileInfo(FileInfo& fileInfo, Fields fields, bool humanReadableSize)
346 {
347 if ((fields & Fields.fs) != Fields.none)
348 {
349 fileInfo.fieldValues.Add(MakeIntFieldValue(fileInfo.fileStatus.fileSystemNumber, Fields.fs));
350 }
351 if ((fields & Fields.inode) != Fields.none)
352 {
353 fileInfo.fieldValues.Add(MakeIntFieldValue(fileInfo.fileStatus.inodeNumber, Fields.inode));
354 }
355 if ((fields & Fields.access) != Fields.none)
356 {
357 fileInfo.fieldValues.Add(MakeAccessFieldValue(fileInfo.fileStatus));
358 }
359 if ((fields & Fields.nlinks) != Fields.none)
360 {
361 fileInfo.fieldValues.Add(MakeIntFieldValue(fileInfo.fileStatus.nlinks, Fields.nlinks));
362 }
363 if ((fields & Fields.user) != Fields.none)
364 {
365 fileInfo.userName = ToUtf32(Credentials.Instance().GetUserName(fileInfo.fileStatus.uid));
366 fileInfo.fieldValues.Add(MakeUStringFieldValue(fileInfo.userName, Fields.user));
367 }
368 if ((fields & Fields.uid) != Fields.none)
369 {
370 fileInfo.fieldValues.Add(MakeIntFieldValue(fileInfo.fileStatus.uid, Fields.uid));
371 }
372 if ((fields & Fields.group) != Fields.none)
373 {
374 fileInfo.groupName = ToUtf32(Credentials.Instance().GetGroupName(fileInfo.fileStatus.gid));
375 fileInfo.fieldValues.Add(MakeUStringFieldValue(fileInfo.groupName, Fields.group));
376 }
377 if ((fields & Fields.gid) != Fields.none)
378 {
379 fileInfo.fieldValues.Add(MakeIntFieldValue(fileInfo.fileStatus.gid, Fields.gid));
380 }
381 if ((fields & Fields.size) != Fields.none)
382 {
383 fileInfo.fieldValues.Add(MakeSizeFieldValue(fileInfo.fileStatus.fileSize, humanReadableSize));
384 }
385 if ((fields & Fields.ctime) != Fields.none)
386 {
387 fileInfo.fieldValues.Add(MakeTimeFieldValue(fileInfo.fileStatus.ctime, Fields.ctime));
388 }
389 if ((fields & Fields.mtime) != Fields.none)
390 {
391 fileInfo.fieldValues.Add(MakeTimeFieldValue(fileInfo.fileStatus.mtime, Fields.mtime));
392 }
393 if ((fields & Fields.atime) != Fields.none)
394 {
395 fileInfo.fieldValues.Add(MakeTimeFieldValue(fileInfo.fileStatus.atime, Fields.atime));
396 }
397 if ((fields & Fields.name) != Fields.none)
398 {
399 fileInfo.nameFieldIndex = fileInfo.fieldValues.Count();
400 fileInfo.fieldValues.Add(MakeStringFieldValue(fileInfo.entry.name, Fields.name));
401 }
402 }
403
404 void MakeFileInfos(List<FileInfo>& fileInfos, Fields fields, bool humanReadableSize)
405 {
406 for (FileInfo& fileInfo : fileInfos)
407 {
408 MakeFileInfo(fileInfo, fields, humanReadableSize);
409 }
410 }
411
412 int NumberOfLines(int numberOfFileInfos, int numberOfColumns)
413 {
414 if (numberOfFileInfos > 0)
415 {
416 return (numberOfFileInfos - 1) / numberOfColumns + 1;
417 }
418 else
419 {
420 return 0;
421 }
422 }
423
424 int NumberOfFields(Fields fields)
425 {
426 int nfields = 0;
427 if ((fields & Fields.fs) != Fields.none)
428 {
429 ++nfields;
430 }
431 if ((fields & Fields.inode) != Fields.none)
432 {
433 ++nfields;
434 }
435 if ((fields & Fields.access) != Fields.none)
436 {
437 ++nfields;
438 }
439 if ((fields & Fields.nlinks) != Fields.none)
440 {
441 ++nfields;
442 }
443 if ((fields & Fields.user) != Fields.none)
444 {
445 ++nfields;
446 }
447 if ((fields & Fields.uid) != Fields.none)
448 {
449 ++nfields;
450 }
451 if ((fields & Fields.group) != Fields.none)
452 {
453 ++nfields;
454 }
455 if ((fields & Fields.gid) != Fields.none)
456 {
457 ++nfields;
458 }
459 if ((fields & Fields.size) != Fields.none)
460 {
461 ++nfields;
462 }
463 if ((fields & Fields.ctime) != Fields.none)
464 {
465 ++nfields;
466 }
467 if ((fields & Fields.mtime) != Fields.none)
468 {
469 ++nfields;
470 }
471 if ((fields & Fields.atime) != Fields.none)
472 {
473 ++nfields;
474 }
475 if ((fields & Fields.name) != Fields.none)
476 {
477 ++nfields;
478 }
479 return nfields;
480 }
481
482 int CalculateFieldWidth(const List<FileInfo>& fileInfos, int columnNr, int fieldNr, int numberOfLines)
483 {
484 int fieldWidth = 0;
485 for (int i = 0; i < numberOfLines; ++i;)
486 {
487 int fileInfoIndex = columnNr * numberOfLines + i;
488 if (fileInfoIndex < fileInfos.Count())
489 {
490 const FileInfo& fileInfo = fileInfos[fileInfoIndex];
491 int w = fileInfo.FieldWidth(fieldNr);
492 if (w > fieldWidth)
493 {
494 fieldWidth = w;
495 }
496 }
497 }
498 return fieldWidth;
499 }
500
501 int CalculateColumnWidth(const List<FileInfo>& fileInfos, int columnNr, int numberOfLines, int numberOfFields)
502 {
503 int columnWidth = 0;
504 for (int f = 0; f < numberOfFields; ++f;)
505 {
506 if (f > 0)
507 {
508 ++columnWidth;
509 }
510 int fieldWidth = CalculateFieldWidth(fileInfos, columnNr, f, numberOfLines);
511 columnWidth = columnWidth + fieldWidth;
512 }
513 return columnWidth;
514 }
515
516 int CalculateLineLength(const List<FileInfo>& fileInfos, int numberOfColumns, int numberOfFields, int screenWidth)
517 {
518 int lineLength = 0;
519 int numberOfLines = NumberOfLines(cast<int>(fileInfos.Count()), numberOfColumns);
520 for (int c = 0; c < numberOfColumns; ++c;)
521 {
522 if (c > 0)
523 {
524 ++lineLength;
525 ++lineLength;
526 }
527 int columnWidth = CalculateColumnWidth(fileInfos, c, numberOfLines, numberOfFields);
528 lineLength = lineLength + columnWidth;
529 if (lineLength >= screenWidth)
530 {
531 break;
532 }
533 }
534 return lineLength;
535 }
536
537 string MakeLine(const List<FileInfo>& fileInfos, int lineNr, int numberOfLines, int numberOfColumns, int numberOfFields, bool hasColors, const Map<Pair<int, int>, int>& fieldWidths)
538 {
539 string line;
540 for (int c = 0; c < numberOfColumns; ++c;)
541 {
542 if (c > 0)
543 {
544 line.Append(" ");
545 }
546 int fileInfoIndex = c * numberOfLines + lineNr;
547 if (fileInfoIndex < fileInfos.Count())
548 {
549 const FileInfo& fileInfo = fileInfos[fileInfoIndex];
550 for (int f = 0; f < numberOfFields; ++f;)
551 {
552 if (f > 0)
553 {
554 line.Append(' ');
555 }
556 const FieldValue& fieldValue = fileInfo.GetFieldValue(f);
557 FormatJustify justify = GetFieldJustification(fieldValue.field);
558 ustring text = Format(fieldValue.text, fieldWidths[MakePair(c, f)], justify);
559 if (fieldValue.field == Fields.name)
560 {
561 line.Append(GetColorString(fileInfo, hasColors)).Append(ToUtf8(text)).Append(ResetColorString(hasColors));
562 }
563 else
564 {
565 line.Append(ToUtf8(text));
566 }
567 }
568 }
569 }
570 return line;
571 }
572
573 List<string> MakeLines(List<FileInfo>& fileInfos, SortField sortField, Fields fields, Flags flags, int screenWidth)
574 {
575 MakeFileInfos(fileInfos, fields, (flags & Flags.humanReadableSize) != Flags.none);
576 Sort(fileInfos, sortField);
577 if ((flags & Flags.reverse) != Flags.none)
578 {
579 Reverse(fileInfos.Begin(), fileInfos.End());
580 }
581 int numberOfFields = NumberOfFields(fields);
582 int numberOfColumns = 1;
583 int lineLength = 0;
584 int ncols = numberOfColumns;
585 if (screenWidth > 0 && (flags & Flags.longFormat) == Flags.none)
586 {
587 while (ncols < screenWidth)
588 {
589 int len = CalculateLineLength(fileInfos, ncols, numberOfFields, screenWidth);
590 if (len >= screenWidth)
591 {
592 break;
593 }
594 else
595 {
596 numberOfColumns = ncols;
597 lineLength = len;
598 ++ncols;
599 }
600 }
601 }
602 int numberOfLines = NumberOfLines(cast<int>(fileInfos.Count()), numberOfColumns);
603 Map<Pair<int, int>, int> fieldWidths;
604 for (int c = 0; c < numberOfColumns; ++c;)
605 {
606 for (int f = 0; f < numberOfFields; ++f;)
607 {
608 int w = CalculateFieldWidth(fileInfos, c, f, numberOfLines);
609 fieldWidths[MakePair(c, f)] = w;
610 }
611 }
612 List<string> lines;
613 for (int i = 0; i < numberOfLines; ++i;)
614 {
615 lines.Add(MakeLine(fileInfos, i, numberOfLines, numberOfColumns, numberOfFields, (flags & Flags.hasColors) != Flags.none, fieldWidths));
616 }
617 return lines;
618 }
619
620 void PrintLines(const List<string>& lines)
621 {
622 for (const string& line : lines)
623 {
624 Console.Out() << line << endl();
625 }
626 }
627
628 void ListFiles(const List<string>& files, SortField sortField, Fields additionalFields, Flags flags, int screenWidth)
629 {
630 Fields fields;
631 if ((flags & Flags.longFormat) != Flags.none)
632 {
633 fields = LongFields();
634 }
635 else
636 {
637 fields = ShortFields();
638 }
639 fields = cast<Fields>(fields | additionalFields);
640 List<FileInfo> fileInfos;
641 DirectoryEntry dirEntry;
642 for (const string& file : files)
643 {
644 FileStatus fileStatus;
645 Stat(file.Chars(), fileStatus);
646 DirectoryEntry entry(fileStatus.inodeNumber, file);
647 if (fileStatus.fileType == FileType.regular)
648 {
649 fileInfos.Add(FileInfo(fileStatus, entry));
650 }
651 else if (fileStatus.fileType == FileType.directory)
652 {
653 List<FileInfo> dirFileInfos;
654 DirectoryReader reader(file);
655 while (reader.Read(dirEntry))
656 {
657 if (dirEntry.name.StartsWith("."))
658 {
659 if ((flags & Flags.all) == Flags.none)
660 {
661 continue;
662 }
663 }
664 string path = Path.Combine(file, dirEntry.name);
665 FileStatus status;
666 Stat(path.Chars(), status);
667 dirFileInfos.Add(FileInfo(status, dirEntry));
668 }
669 if (!dirFileInfos.IsEmpty())
670 {
671 List<string> lines = MakeLines(dirFileInfos, sortField, fields, flags, screenWidth);
672 PrintLines(lines);
673 }
674 }
675 }
676 if (!fileInfos.IsEmpty())
677 {
678 List<string> lines = MakeLines(fileInfos, sortField, fields, flags, screenWidth);
679 PrintLines(lines);
680 }
681 }