VirtualBox

source: vbox/trunk/src/VBox/Runtime/common/fs/RTFsCmdLs.cpp@ 93115

Last change on this file since 93115 was 93115, checked in by vboxsync, 3 years ago

scm --update-copyright-year

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 70.5 KB
Line 
1/* $Id: RTFsCmdLs.cpp 93115 2022-01-01 11:31:46Z vboxsync $ */
2/** @file
3 * IPRT - /bin/ls like utility for testing the VFS code.
4 */
5
6/*
7 * Copyright (C) 2017-2022 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 *
17 * The contents of this file may alternatively be used under the terms
18 * of the Common Development and Distribution License Version 1.0
19 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
20 * VirtualBox OSE distribution, in which case the provisions of the
21 * CDDL are applicable instead of those of the GPL.
22 *
23 * You may elect to license modified versions of this file under the
24 * terms and conditions of either the GPL or the CDDL or both.
25 */
26
27
28/*********************************************************************************************************************************
29* Header Files *
30*********************************************************************************************************************************/
31#include <iprt/vfs.h>
32
33#include <iprt/buildconfig.h>
34#include <iprt/err.h>
35#include <iprt/file.h>
36#include <iprt/getopt.h>
37#include <iprt/initterm.h>
38#include <iprt/mem.h>
39#include <iprt/message.h>
40#include <iprt/param.h>
41#include <iprt/path.h>
42#include <iprt/sort.h>
43#include <iprt/stream.h>
44#include <iprt/string.h>
45
46
47/*********************************************************************************************************************************
48* Structures and Typedefs *
49*********************************************************************************************************************************/
50/**
51 * Display entry.
52 */
53typedef struct RTCMDLSENTRY
54{
55 /** The information about the entry. */
56 RTFSOBJINFO Info;
57 /** Symbolic link target (allocated after the name). */
58 const char *pszTarget;
59 /** Owner if applicable(allocated after the name). */
60 const char *pszOwner;
61 /** Group if applicable (allocated after the name). */
62 const char *pszGroup;
63 /** The length of szName. */
64 size_t cchName;
65 /** The entry name. */
66 RT_FLEXIBLE_ARRAY_EXTENSION
67 char szName[RT_FLEXIBLE_ARRAY];
68} RTCMDLSENTRY;
69/** Pointer to a ls display entry. */
70typedef RTCMDLSENTRY *PRTCMDLSENTRY;
71/** Pointer to a ls display entry pointer. */
72typedef PRTCMDLSENTRY *PPRTCMDLSENTRY;
73
74
75/**
76 * Collection of display entries.
77 */
78typedef struct RTCMDLSCOLLECTION
79{
80 /** Current size of papEntries. */
81 size_t cEntries;
82 /** Memory allocated for papEntries. */
83 size_t cEntriesAllocated;
84 /** Current entries pending sorting and display. */
85 PPRTCMDLSENTRY papEntries;
86
87 /** Total number of bytes allocated for the above entries. */
88 uint64_t cbTotalAllocated;
89 /** Total number of file content bytes. */
90 uint64_t cbTotalFiles;
91
92 /** The collection name (path). */
93 RT_FLEXIBLE_ARRAY_EXTENSION
94 char szName[RT_FLEXIBLE_ARRAY];
95} RTCMDLSCOLLECTION;
96/** Pointer to a display entry collection. */
97typedef RTCMDLSCOLLECTION *PRTCMDLSCOLLECTION;
98/** Pointer to a display entry collection pointer. */
99typedef PRTCMDLSCOLLECTION *PPRTCMDLSCOLLECTION;
100
101
102/** Sorting. */
103typedef enum RTCMDLSSORT
104{
105 RTCMDLSSORT_INVALID = 0,
106 RTCMDLSSORT_NONE,
107 RTCMDLSSORT_NAME,
108 RTCMDLSSORT_EXTENSION,
109 RTCMDLSSORT_SIZE,
110 RTCMDLSSORT_TIME,
111 RTCMDLSSORT_VERSION
112} RTCMDLSSORT;
113
114/** Time selection. */
115typedef enum RTCMDLSTIME
116{
117 RTCMDLSTIME_INVALID = 0,
118 RTCMDLSTIME_BTIME,
119 RTCMDLSTIME_CTIME,
120 RTCMDLSTIME_MTIME,
121 RTCMDLSTIME_ATIME
122} RTCMDLSTIME;
123
124/** Time display style. */
125typedef enum RTCMDLSTIMESTYLE
126{
127 RTCMDLSTIMESTYLE_INVALID = 0,
128 RTCMDLSTIMESTYLE_FULL_ISO,
129 RTCMDLSTIMESTYLE_LONG_ISO,
130 RTCMDLSTIMESTYLE_ISO,
131 RTCMDLSTIMESTYLE_LOCALE,
132 RTCMDLSTIMESTYLE_CUSTOM
133} RTCMDLSTIMESTYLE;
134
135/** Coloring selection. */
136typedef enum RTCMDLSCOLOR
137{
138 RTCMDLSCOLOR_INVALID = 0,
139 RTCMDLSCOLOR_NONE
140} RTCMDLSCOLOR;
141
142/** Formatting. */
143typedef enum RTCMDLSFORMAT
144{
145 RTCMDLSFORMAT_INVALID = 0,
146 RTCMDLSFORMAT_COLS_VERTICAL, /**< -C/default */
147 RTCMDLSFORMAT_COLS_HORIZONTAL, /**< -x */
148 RTCMDLSFORMAT_COMMAS, /**< -m */
149 RTCMDLSFORMAT_SINGLE, /**< -1 */
150 RTCMDLSFORMAT_LONG, /**< -l */
151 RTCMDLSFORMAT_MACHINE_READABLE /**< --machine-readable */
152} RTCMDLSFORMAT;
153
154
155/**
156 * LS command options and state.
157 */
158typedef struct RTCMDLSOPTS
159{
160 /** @name Traversal.
161 * @{ */
162 bool fFollowSymlinksInDirs; /**< -L */
163 bool fFollowSymlinkToAnyArgs;
164 bool fFollowSymlinkToDirArgs;
165 bool fFollowDirectoryArgs; /**< Inverse -d/--directory. */
166 bool fRecursive; /**< -R */
167 /** @} */
168
169
170 /** @name Filtering.
171 * @{ */
172 bool fShowHidden; /**< -a/--all or -A/--almost-all */
173 bool fShowDotAndDotDot; /**< -a vs -A */
174 bool fShowBackups; /**< Inverse -B/--ignore-backups (*~). */
175 /** @} */
176
177 /** @name Sorting
178 * @{ */
179 RTCMDLSSORT enmSort; /**< --sort */
180 bool fReverseSort; /**< -r */
181 bool fGroupDirectoriesFirst; /**< fGroupDirectoriesFirst */
182 /** @} */
183
184 /** @name Formatting
185 * @{ */
186 RTCMDLSFORMAT enmFormat; /**< --format */
187
188 bool fEscapeNonGraphicChars; /**< -b, --escape */
189 bool fEscapeControlChars;
190 bool fHideControlChars; /**< -q/--hide-control-chars, --show-control-chars */
191
192 bool fHumanReadableSizes; /**< -h */
193 bool fSiUnits; /**< --si */
194 uint32_t cbBlock; /**< --block-size=N, -k */
195
196 bool fShowOwner;
197 bool fShowGroup;
198 bool fNumericalIds; /**< -n */
199 bool fShowINode;
200 bool fShowAllocatedSize; /**< -s */
201 uint8_t cchTab; /**< -T */
202 uint32_t cchWidth; /**< -w */
203
204 RTCMDLSCOLOR enmColor; /**< --color */
205
206 RTCMDLSTIME enmTime; /**< --time */
207 RTCMDLSTIMESTYLE enmTimeStyle; /**< --time-style, --full-time */
208 const char *pszTimeCustom; /**< --time-style=+xxx */
209 /** @} */
210
211 /** @name State
212 * @{ */
213 /** Current size of papCollections. */
214 size_t cCollections;
215 /** Memory allocated for papCollections. */
216 size_t cCollectionsAllocated;
217 /** Current entry collection pending display, the last may also be pending
218 * sorting. */
219 PPRTCMDLSCOLLECTION papCollections;
220 /** @} */
221} RTCMDLSOPTS;
222/** Pointer to ls options and state. */
223typedef RTCMDLSOPTS *PRTCMDLSOPTS;
224
225
226
227
228/** @callback_method_impl{FNRTSORTCMP, Dirs first + Unsorted} */
229static DECLCALLBACK(int) rtCmdLsEntryCmpDirFirstUnsorted(void const *pvElement1, void const *pvElement2, void *pvUser)
230{
231 RT_NOREF(pvUser);
232 PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1;
233 PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2;
234 return !RTFS_IS_DIRECTORY(pEntry1->Info.Attr.fMode) - !RTFS_IS_DIRECTORY(pEntry2->Info.Attr.fMode);
235}
236
237
238/** @callback_method_impl{FNRTSORTCMP, Name} */
239static DECLCALLBACK(int) rtCmdLsEntryCmpName(void const *pvElement1, void const *pvElement2, void *pvUser)
240{
241 RT_NOREF(pvUser);
242 PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1;
243 PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2;
244 return RTStrCmp(pEntry1->szName, pEntry2->szName);
245}
246
247
248/** @callback_method_impl{FNRTSORTCMP, Dirs first + Name} */
249static DECLCALLBACK(int) rtCmdLsEntryCmpDirFirstName(void const *pvElement1, void const *pvElement2, void *pvUser)
250{
251 RT_NOREF(pvUser);
252 PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1;
253 PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2;
254 int iDiff = !RTFS_IS_DIRECTORY(pEntry1->Info.Attr.fMode) - !RTFS_IS_DIRECTORY(pEntry2->Info.Attr.fMode);
255 if (!iDiff)
256 iDiff = rtCmdLsEntryCmpName(pEntry1, pEntry2, pvUser);
257 return iDiff;
258}
259
260
261/** @callback_method_impl{FNRTSORTCMP, extension} */
262static DECLCALLBACK(int) rtCmdLsEntryCmpExtension(void const *pvElement1, void const *pvElement2, void *pvUser)
263{
264 RT_NOREF(pvUser);
265 PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1;
266 PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2;
267 int iDiff = RTStrCmp(RTPathSuffix(pEntry1->szName), RTPathSuffix(pEntry2->szName));
268 if (!iDiff)
269 iDiff = RTStrCmp(pEntry1->szName, pEntry2->szName);
270 return iDiff;
271}
272
273
274/** @callback_method_impl{FNRTSORTCMP, Dirs first + Ext + Name} */
275static DECLCALLBACK(int) rtCmdLsEntryCmpDirFirstExtension(void const *pvElement1, void const *pvElement2, void *pvUser)
276{
277 RT_NOREF(pvUser);
278 PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1;
279 PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2;
280 int iDiff = !RTFS_IS_DIRECTORY(pEntry1->Info.Attr.fMode) - !RTFS_IS_DIRECTORY(pEntry2->Info.Attr.fMode);
281 if (!iDiff)
282 iDiff = rtCmdLsEntryCmpExtension(pEntry1, pEntry2, pvUser);
283 return iDiff;
284}
285
286
287/** @callback_method_impl{FNRTSORTCMP, Allocated size + Name} */
288static DECLCALLBACK(int) rtCmdLsEntryCmpAllocated(void const *pvElement1, void const *pvElement2, void *pvUser)
289{
290 RT_NOREF(pvUser);
291 PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1;
292 PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2;
293 if (pEntry1->Info.cbAllocated == pEntry2->Info.cbAllocated)
294 return rtCmdLsEntryCmpName(pEntry1, pEntry2, pvUser);
295 return pEntry1->Info.cbAllocated < pEntry2->Info.cbAllocated ? -1 : 1;
296}
297
298
299/** @callback_method_impl{FNRTSORTCMP, Dirs first + Allocated size + Name} */
300static DECLCALLBACK(int) rtCmdLsEntryCmpDirFirstAllocated(void const *pvElement1, void const *pvElement2, void *pvUser)
301{
302 RT_NOREF(pvUser);
303 PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1;
304 PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2;
305 int iDiff = !RTFS_IS_DIRECTORY(pEntry1->Info.Attr.fMode) - !RTFS_IS_DIRECTORY(pEntry2->Info.Attr.fMode);
306 if (!iDiff)
307 iDiff = rtCmdLsEntryCmpAllocated(pEntry1, pEntry2, pvUser);
308 return iDiff;
309}
310
311
312/** @callback_method_impl{FNRTSORTCMP, Content size + Name} */
313static DECLCALLBACK(int) rtCmdLsEntryCmpSize(void const *pvElement1, void const *pvElement2, void *pvUser)
314{
315 RT_NOREF(pvUser);
316 PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1;
317 PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2;
318 if (pEntry1->Info.cbObject == pEntry2->Info.cbObject)
319 return rtCmdLsEntryCmpName(pEntry1, pEntry2, pvUser);
320 return pEntry1->Info.cbObject < pEntry2->Info.cbObject ? -1 : 1;
321}
322
323
324/** @callback_method_impl{FNRTSORTCMP, Dirs first + Content size + Name} */
325static DECLCALLBACK(int) rtCmdLsEntryCmpDirFirstSize(void const *pvElement1, void const *pvElement2, void *pvUser)
326{
327 PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1;
328 PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2;
329 int iDiff = !RTFS_IS_DIRECTORY(pEntry1->Info.Attr.fMode) - !RTFS_IS_DIRECTORY(pEntry2->Info.Attr.fMode);
330 if (!iDiff)
331 iDiff = rtCmdLsEntryCmpSize(pEntry1, pEntry2, pvUser);
332 return iDiff;
333}
334
335
336/** @callback_method_impl{FNRTSORTCMP, Modification time + name} */
337static DECLCALLBACK(int) rtCmdLsEntryCmpMTime(void const *pvElement1, void const *pvElement2, void *pvUser)
338{
339 PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1;
340 PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2;
341 int iDiff = RTTimeSpecCompare(&pEntry1->Info.ModificationTime, &pEntry2->Info.ModificationTime);
342 if (!iDiff)
343 iDiff = rtCmdLsEntryCmpName(pEntry1, pEntry2, pvUser);
344 return iDiff;
345}
346
347
348/** @callback_method_impl{FNRTSORTCMP, Dirs first + Modification time + Name} */
349static DECLCALLBACK(int) rtCmdLsEntryCmpDirFirstMTime(void const *pvElement1, void const *pvElement2, void *pvUser)
350{
351 RT_NOREF(pvUser);
352 PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1;
353 PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2;
354 int iDiff = !RTFS_IS_DIRECTORY(pEntry1->Info.Attr.fMode) - !RTFS_IS_DIRECTORY(pEntry2->Info.Attr.fMode);
355 if (!iDiff)
356 iDiff = rtCmdLsEntryCmpMTime(pEntry1, pEntry2, pvUser);
357 return iDiff;
358}
359
360
361/** @callback_method_impl{FNRTSORTCMP, Birth time + name} */
362static DECLCALLBACK(int) rtCmdLsEntryCmpBTime(void const *pvElement1, void const *pvElement2, void *pvUser)
363{
364 PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1;
365 PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2;
366 int iDiff = RTTimeSpecCompare(&pEntry1->Info.BirthTime, &pEntry2->Info.BirthTime);
367 if (!iDiff)
368 iDiff = rtCmdLsEntryCmpName(pEntry1, pEntry2, pvUser);
369 return iDiff;
370}
371
372
373/** @callback_method_impl{FNRTSORTCMP, Dirs first + Birth time + Name} */
374static DECLCALLBACK(int) rtCmdLsEntryCmpDirFirstBTime(void const *pvElement1, void const *pvElement2, void *pvUser)
375{
376 RT_NOREF(pvUser);
377 PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1;
378 PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2;
379 int iDiff = !RTFS_IS_DIRECTORY(pEntry1->Info.Attr.fMode) - !RTFS_IS_DIRECTORY(pEntry2->Info.Attr.fMode);
380 if (!iDiff)
381 iDiff = rtCmdLsEntryCmpBTime(pEntry1, pEntry2, pvUser);
382 return iDiff;
383}
384
385
386/** @callback_method_impl{FNRTSORTCMP, Change time + name} */
387static DECLCALLBACK(int) rtCmdLsEntryCmpCTime(void const *pvElement1, void const *pvElement2, void *pvUser)
388{
389 PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1;
390 PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2;
391 int iDiff = RTTimeSpecCompare(&pEntry1->Info.ChangeTime, &pEntry2->Info.ChangeTime);
392 if (!iDiff)
393 iDiff = rtCmdLsEntryCmpName(pEntry1, pEntry2, pvUser);
394 return iDiff;
395}
396
397
398/** @callback_method_impl{FNRTSORTCMP, Dirs first + Change time + Name} */
399static DECLCALLBACK(int) rtCmdLsEntryCmpDirFirstCTime(void const *pvElement1, void const *pvElement2, void *pvUser)
400{
401 RT_NOREF(pvUser);
402 PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1;
403 PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2;
404 int iDiff = !RTFS_IS_DIRECTORY(pEntry1->Info.Attr.fMode) - !RTFS_IS_DIRECTORY(pEntry2->Info.Attr.fMode);
405 if (!iDiff)
406 iDiff = rtCmdLsEntryCmpCTime(pEntry1, pEntry2, pvUser);
407 return iDiff;
408}
409
410
411/** @callback_method_impl{FNRTSORTCMP, Accessed time + name} */
412static DECLCALLBACK(int) rtCmdLsEntryCmpATime(void const *pvElement1, void const *pvElement2, void *pvUser)
413{
414 PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1;
415 PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2;
416 int iDiff = RTTimeSpecCompare(&pEntry1->Info.AccessTime, &pEntry2->Info.AccessTime);
417 if (!iDiff)
418 iDiff = rtCmdLsEntryCmpName(pEntry1, pEntry2, pvUser);
419 return iDiff;
420}
421
422
423/** @callback_method_impl{FNRTSORTCMP, Dirs first + Accessed time + Name} */
424static DECLCALLBACK(int) rtCmdLsEntryCmpDirFirstATime(void const *pvElement1, void const *pvElement2, void *pvUser)
425{
426 RT_NOREF(pvUser);
427 PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1;
428 PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2;
429 int iDiff = !RTFS_IS_DIRECTORY(pEntry1->Info.Attr.fMode) - !RTFS_IS_DIRECTORY(pEntry2->Info.Attr.fMode);
430 if (!iDiff)
431 iDiff = rtCmdLsEntryCmpATime(pEntry1, pEntry2, pvUser);
432 return iDiff;
433}
434
435
436/** @callback_method_impl{FNRTSORTCMP, Name as version} */
437static DECLCALLBACK(int) rtCmdLsEntryCmpVersion(void const *pvElement1, void const *pvElement2, void *pvUser)
438{
439 RT_NOREF(pvUser);
440 PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1;
441 PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2;
442 return RTStrVersionCompare(pEntry1->szName, pEntry2->szName);
443}
444
445
446/** @callback_method_impl{FNRTSORTCMP, Dirs first + Name as version} */
447static DECLCALLBACK(int) rtCmdLsEntryCmpDirFirstVersion(void const *pvElement1, void const *pvElement2, void *pvUser)
448{
449 RT_NOREF(pvUser);
450 PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1;
451 PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2;
452 int iDiff = !RTFS_IS_DIRECTORY(pEntry1->Info.Attr.fMode) - !RTFS_IS_DIRECTORY(pEntry2->Info.Attr.fMode);
453 if (!iDiff)
454 iDiff = rtCmdLsEntryCmpVersion(pEntry1, pEntry2, pvUser);
455 return iDiff;
456}
457
458
459/**
460 * Sorts the entries in the collections according the sorting options.
461 *
462 * @param pOpts The options and state.
463 */
464static void rtCmdLsSortCollections(PRTCMDLSOPTS pOpts)
465{
466 /*
467 * Sort the entries in each collection.
468 */
469 PFNRTSORTCMP pfnCmp;
470 switch (pOpts->enmSort)
471 {
472 case RTCMDLSSORT_NONE:
473 pfnCmp = pOpts->fGroupDirectoriesFirst ? rtCmdLsEntryCmpDirFirstUnsorted : NULL;
474 break;
475 default: AssertFailed(); RT_FALL_THRU();
476 case RTCMDLSSORT_NAME:
477 pfnCmp = pOpts->fGroupDirectoriesFirst ? rtCmdLsEntryCmpDirFirstName : rtCmdLsEntryCmpName;
478 break;
479 case RTCMDLSSORT_EXTENSION:
480 pfnCmp = pOpts->fGroupDirectoriesFirst ? rtCmdLsEntryCmpDirFirstExtension : rtCmdLsEntryCmpExtension;
481 break;
482 case RTCMDLSSORT_SIZE:
483 if (pOpts->fShowAllocatedSize)
484 pfnCmp = pOpts->fGroupDirectoriesFirst ? rtCmdLsEntryCmpDirFirstAllocated : rtCmdLsEntryCmpAllocated;
485 else
486 pfnCmp = pOpts->fGroupDirectoriesFirst ? rtCmdLsEntryCmpDirFirstSize : rtCmdLsEntryCmpSize;
487 break;
488 case RTCMDLSSORT_TIME:
489 switch (pOpts->enmTime)
490 {
491 default: AssertFailed(); RT_FALL_THRU();
492 case RTCMDLSTIME_MTIME: pfnCmp = pOpts->fGroupDirectoriesFirst ? rtCmdLsEntryCmpDirFirstMTime : rtCmdLsEntryCmpMTime; break;
493 case RTCMDLSTIME_BTIME: pfnCmp = pOpts->fGroupDirectoriesFirst ? rtCmdLsEntryCmpDirFirstBTime : rtCmdLsEntryCmpBTime; break;
494 case RTCMDLSTIME_CTIME: pfnCmp = pOpts->fGroupDirectoriesFirst ? rtCmdLsEntryCmpDirFirstCTime : rtCmdLsEntryCmpCTime; break;
495 case RTCMDLSTIME_ATIME: pfnCmp = pOpts->fGroupDirectoriesFirst ? rtCmdLsEntryCmpDirFirstATime : rtCmdLsEntryCmpATime; break;
496 }
497 break;
498 case RTCMDLSSORT_VERSION:
499 pfnCmp = pOpts->fGroupDirectoriesFirst ? rtCmdLsEntryCmpDirFirstVersion : rtCmdLsEntryCmpVersion;
500 break;
501 }
502 if (pfnCmp)
503 {
504 /*
505 * Walk thru the collections and sort their entries.
506 */
507 size_t i = pOpts->cCollections;
508 while (i-- > 0)
509 {
510 PRTCMDLSCOLLECTION pCollection = pOpts->papCollections[i];
511 RTSortApvShell((void **)pCollection->papEntries, pCollection->cEntries, pfnCmp, NULL);
512
513 if (pOpts->fReverseSort)
514 {
515 PPRTCMDLSENTRY papEntries = pCollection->papEntries;
516 size_t iHead = 0;
517 size_t iTail = pCollection->cEntries;
518 while (iHead < iTail)
519 {
520 PRTCMDLSENTRY pTmp = papEntries[iHead];
521 papEntries[iHead] = papEntries[iTail];
522 papEntries[iTail] = pTmp;
523 iHead++;
524 iTail--;
525 }
526 }
527 }
528 }
529
530 /** @todo sort the collections too, except for the first one. */
531}
532
533
534/**
535 * Format human readable size.
536 */
537static const char *rtCmdLsFormatSizeHumanReadable(PRTCMDLSOPTS pOpts, uint64_t cb, char *pszDst, size_t cbDst)
538{
539 if (pOpts->fHumanReadableSizes)
540 {
541 if (!pOpts->fSiUnits)
542 {
543 size_t cch = RTStrPrintf(pszDst, cbDst, "%Rhub", cb);
544 if (pszDst[cch - 1] == 'i')
545 pszDst[cch - 1] = '\0'; /* drop the trailing 'i' */
546 }
547 else
548 RTStrPrintf(pszDst, cbDst, "%Rhui", cb);
549 }
550 else if (pOpts->cbBlock)
551 RTStrFormatU64(pszDst, cbDst, (cb + pOpts->cbBlock - 1) / pOpts->cbBlock, 10, 0, 0, 0);
552 else
553 RTStrFormatU64(pszDst, cbDst, cb, 10, 0, 0, 0);
554 return pszDst;
555}
556
557
558/**
559 * Format block count.
560 */
561static const char *rtCmdLsFormatBlocks(PRTCMDLSOPTS pOpts, uint64_t cb, char *pszDst, size_t cbDst)
562{
563 if (pOpts->fHumanReadableSizes)
564 return rtCmdLsFormatSizeHumanReadable(pOpts, cb, pszDst, cbDst);
565
566 uint32_t cbBlock = pOpts->cbBlock;
567 if (cbBlock == 0)
568 cbBlock = _1K;
569 RTStrFormatU64(pszDst, cbDst, (cb + cbBlock / 2 - 1) / cbBlock, 10, 0, 0, 0);
570 return pszDst;
571}
572
573
574/**
575 * Format file size.
576 */
577static const char *rtCmdLsFormatSize(PRTCMDLSOPTS pOpts, uint64_t cb, char *pszDst, size_t cbDst)
578{
579 if (pOpts->fHumanReadableSizes)
580 return rtCmdLsFormatSizeHumanReadable(pOpts, cb, pszDst, cbDst);
581 if (pOpts->cbBlock > 0)
582 return rtCmdLsFormatBlocks(pOpts, cb, pszDst, cbDst);
583 RTStrFormatU64(pszDst, cbDst, cb, 10, 0, 0, 0);
584 return pszDst;
585}
586
587
588/**
589 * Format name, i.e. escape, hide, quote stuff.
590 */
591static const char *rtCmdLsFormatName(PRTCMDLSOPTS pOpts, const char *pszName, char *pszDst, size_t cbDst)
592{
593 if ( !pOpts->fEscapeNonGraphicChars
594 && !pOpts->fEscapeControlChars
595 && !pOpts->fHideControlChars)
596 return pszName;
597 /** @todo implement name formatting. */
598 RT_NOREF(pszDst, cbDst);
599 return pszName;
600}
601
602
603/**
604 * Figures out the length for a 32-bit number when formatted as decimal.
605 * @returns Number of digits.
606 * @param uValue The number.
607 */
608DECLINLINE(size_t) rtCmdLsDecimalFormatLengthU32(uint32_t uValue)
609{
610 if (uValue < 10)
611 return 1;
612 if (uValue < 100)
613 return 2;
614 if (uValue < 1000)
615 return 3;
616 if (uValue < 10000)
617 return 4;
618 if (uValue < 100000)
619 return 5;
620 if (uValue < 1000000)
621 return 6;
622 if (uValue < 10000000)
623 return 7;
624 if (uValue < 100000000)
625 return 8;
626 if (uValue < 1000000000)
627 return 9;
628 return 10;
629}
630
631
632/**
633 * Formats the given group ID according to the specified options.
634 *
635 * @returns pszDst
636 * @param pOpts The options and state.
637 * @param gid The GID to format.
638 * @param pszOwner The owner returned by the FS.
639 * @param pszDst The output buffer.
640 * @param cbDst The output buffer size.
641 */
642static const char *rtCmdLsDecimalFormatGroup(PRTCMDLSOPTS pOpts, RTGID gid, const char *pszGroup, char *pszDst, size_t cbDst)
643{
644 if (!pOpts->fNumericalIds)
645 {
646 if (pszGroup)
647 {
648 RTStrCopy(pszDst, cbDst, pszGroup);
649 return pszDst;
650 }
651 if (gid == NIL_RTGID)
652 return "<Nil>";
653 }
654 RTStrFormatU64(pszDst, cbDst, gid, 10, 0, 0, 0);
655 return pszDst;
656}
657
658
659/**
660 * Formats the given user ID according to the specified options.
661 *
662 * @returns pszDst
663 * @param pOpts The options and state.
664 * @param uid The UID to format.
665 * @param pszOwner The owner returned by the FS.
666 * @param pszDst The output buffer.
667 * @param cbDst The output buffer size.
668 */
669static const char *rtCmdLsDecimalFormatOwner(PRTCMDLSOPTS pOpts, RTUID uid, const char *pszOwner, char *pszDst, size_t cbDst)
670{
671 if (!pOpts->fNumericalIds)
672 {
673 if (pszOwner)
674 {
675 RTStrCopy(pszDst, cbDst, pszOwner);
676 return pszDst;
677 }
678 if (uid == NIL_RTUID)
679 return "<Nil>";
680 }
681 RTStrFormatU64(pszDst, cbDst, uid, 10, 0, 0, 0);
682 return pszDst;
683}
684
685
686/**
687 * Formats the given timestamp according to the desired --time-style.
688 *
689 * @returns pszDst
690 * @param pOpts The options and state.
691 * @param pTimestamp The timestamp.
692 * @param pszDst The output buffer.
693 * @param cbDst The output buffer size.
694 */
695static const char *rtCmdLsFormatTimestamp(PRTCMDLSOPTS pOpts, PCRTTIMESPEC pTimestamp, char *pszDst, size_t cbDst)
696{
697 /** @todo timestamp formatting according to the given style. */
698 RT_NOREF(pOpts);
699 return RTTimeSpecToString(pTimestamp, pszDst, cbDst);
700}
701
702
703
704/**
705 * RTCMDLSFORMAT_MACHINE_READABLE: --machine-readable
706 */
707static RTEXITCODE rtCmdLsDisplayCollectionInMachineReadableFormat(PRTCMDLSOPTS pOpts, PRTCMDLSCOLLECTION pCollection,
708 char *pszTmp, size_t cbTmp)
709{
710 RT_NOREF(pOpts, pCollection, pszTmp, cbTmp);
711 RTMsgError("Machine readable format not implemented\n");
712 return RTEXITCODE_FAILURE;
713}
714
715
716/**
717 * RTCMDLSFORMAT_COMMAS: -m
718 */
719static RTEXITCODE rtCmdLsDisplayCollectionInCvsFormat(PRTCMDLSOPTS pOpts, PRTCMDLSCOLLECTION pCollection,
720 char *pszTmp, size_t cbTmp)
721{
722 RT_NOREF(pOpts, pCollection, pszTmp, cbTmp);
723 RTMsgError("Table output formats not implemented\n");
724 return RTEXITCODE_FAILURE;
725}
726
727
728/**
729 * RTCMDLSFORMAT_LONG: -l
730 */
731static RTEXITCODE rtCmdLsDisplayCollectionInLongFormat(PRTCMDLSOPTS pOpts, PRTCMDLSCOLLECTION pCollection,
732 char *pszTmp, size_t cbTmp, size_t cchAllocatedCol)
733{
734 /*
735 * Figure the width of the size, the link count, the uid, the gid, and the inode columns.
736 */
737 size_t cchSizeCol = 1;
738 size_t cchLinkCol = 1;
739 size_t cchUidCol = pOpts->fShowOwner ? 1 : 0;
740 size_t cchGidCol = pOpts->fShowGroup ? 1 : 0;
741 size_t cchINodeCol = pOpts->fShowINode ? 1 : 0;
742
743 size_t i = pCollection->cEntries;
744 while (i-- > 0)
745 {
746 PRTCMDLSENTRY pEntry = pCollection->papEntries[i];
747
748 rtCmdLsFormatSize(pOpts, pEntry->Info.cbObject, pszTmp, cbTmp);
749 size_t cchTmp = strlen(pszTmp);
750 if (cchTmp > cchSizeCol)
751 cchSizeCol = cchTmp;
752
753 cchTmp = rtCmdLsDecimalFormatLengthU32(pEntry->Info.Attr.u.Unix.cHardlinks) + 1;
754 if (cchTmp > cchLinkCol)
755 cchLinkCol = cchTmp;
756
757 if (pOpts->fShowOwner)
758 {
759 rtCmdLsDecimalFormatOwner(pOpts, pEntry->Info.Attr.u.Unix.uid, pEntry->pszOwner, pszTmp, cbTmp);
760 cchTmp = strlen(pszTmp);
761 if (cchTmp > cchUidCol)
762 cchUidCol = cchTmp;
763 }
764
765 if (pOpts->fShowGroup)
766 {
767 rtCmdLsDecimalFormatGroup(pOpts, pEntry->Info.Attr.u.Unix.gid, pEntry->pszGroup, pszTmp, cbTmp);
768 cchTmp = strlen(pszTmp);
769 if (cchTmp > cchGidCol)
770 cchGidCol = cchTmp;
771 }
772
773 if (pOpts->fShowINode)
774 {
775 cchTmp = RTStrFormatU64(pszTmp, cchTmp, pEntry->Info.Attr.u.Unix.INodeId, 10, 0, 0, 0);
776 if (cchTmp > cchINodeCol)
777 cchINodeCol = cchTmp;
778 }
779 }
780
781 /*
782 * Determin time member offset.
783 */
784 size_t offTime;
785 switch (pOpts->enmTime)
786 {
787 default: AssertFailed(); RT_FALL_THRU();
788 case RTCMDLSTIME_MTIME: offTime = RT_UOFFSETOF(RTCMDLSENTRY, Info.ModificationTime); break;
789 case RTCMDLSTIME_BTIME: offTime = RT_UOFFSETOF(RTCMDLSENTRY, Info.BirthTime); break;
790 case RTCMDLSTIME_CTIME: offTime = RT_UOFFSETOF(RTCMDLSENTRY, Info.ChangeTime); break;
791 case RTCMDLSTIME_ATIME: offTime = RT_UOFFSETOF(RTCMDLSENTRY, Info.AccessTime); break;
792 }
793
794 /*
795 * Display the entries.
796 */
797 for (i = 0; i < pCollection->cEntries; i++)
798 {
799 PRTCMDLSENTRY pEntry = pCollection->papEntries[i];
800
801 if (cchINodeCol)
802 RTPrintf("%*RU64 ", cchINodeCol, pEntry->Info.Attr.u.Unix.INodeId);
803 if (cchAllocatedCol)
804 RTPrintf("%*s ", cchAllocatedCol, rtCmdLsFormatBlocks(pOpts, pEntry->Info.cbAllocated, pszTmp, cbTmp));
805
806 RTFMODE fMode = pEntry->Info.Attr.fMode;
807 switch (fMode & RTFS_TYPE_MASK)
808 {
809 case RTFS_TYPE_FIFO: RTPrintf("f"); break;
810 case RTFS_TYPE_DEV_CHAR: RTPrintf("c"); break;
811 case RTFS_TYPE_DIRECTORY: RTPrintf("d"); break;
812 case RTFS_TYPE_DEV_BLOCK: RTPrintf("b"); break;
813 case RTFS_TYPE_FILE: RTPrintf("-"); break;
814 case RTFS_TYPE_SYMLINK: RTPrintf("l"); break;
815 case RTFS_TYPE_SOCKET: RTPrintf("s"); break;
816 case RTFS_TYPE_WHITEOUT: RTPrintf("w"); break;
817 default: RTPrintf("?"); AssertFailed(); break;
818 }
819 /** @todo sticy bits++ */
820 RTPrintf("%c%c%c",
821 fMode & RTFS_UNIX_IRUSR ? 'r' : '-',
822 fMode & RTFS_UNIX_IWUSR ? 'w' : '-',
823 fMode & RTFS_UNIX_IXUSR ? 'x' : '-');
824 RTPrintf("%c%c%c",
825 fMode & RTFS_UNIX_IRGRP ? 'r' : '-',
826 fMode & RTFS_UNIX_IWGRP ? 'w' : '-',
827 fMode & RTFS_UNIX_IXGRP ? 'x' : '-');
828 RTPrintf("%c%c%c",
829 fMode & RTFS_UNIX_IROTH ? 'r' : '-',
830 fMode & RTFS_UNIX_IWOTH ? 'w' : '-',
831 fMode & RTFS_UNIX_IXOTH ? 'x' : '-');
832 if (1)
833 {
834 RTPrintf(" %c%c%c%c%c%c%c%c%c%c%c%c%c%c",
835 fMode & RTFS_DOS_READONLY ? 'R' : '-',
836 fMode & RTFS_DOS_HIDDEN ? 'H' : '-',
837 fMode & RTFS_DOS_SYSTEM ? 'S' : '-',
838 fMode & RTFS_DOS_DIRECTORY ? 'D' : '-',
839 fMode & RTFS_DOS_ARCHIVED ? 'A' : '-',
840 fMode & RTFS_DOS_NT_DEVICE ? 'd' : '-',
841 fMode & RTFS_DOS_NT_NORMAL ? 'N' : '-',
842 fMode & RTFS_DOS_NT_TEMPORARY ? 'T' : '-',
843 fMode & RTFS_DOS_NT_SPARSE_FILE ? 'P' : '-',
844 fMode & RTFS_DOS_NT_REPARSE_POINT ? 'J' : '-',
845 fMode & RTFS_DOS_NT_COMPRESSED ? 'C' : '-',
846 fMode & RTFS_DOS_NT_OFFLINE ? 'O' : '-',
847 fMode & RTFS_DOS_NT_NOT_CONTENT_INDEXED ? 'I' : '-',
848 fMode & RTFS_DOS_NT_ENCRYPTED ? 'E' : '-');
849 }
850 RTPrintf(" %*u", cchLinkCol, pEntry->Info.Attr.u.Unix.cHardlinks);
851 if (cchUidCol)
852 RTPrintf(" %*s", cchUidCol,
853 rtCmdLsDecimalFormatOwner(pOpts, pEntry->Info.Attr.u.Unix.uid, pEntry->pszOwner, pszTmp, cbTmp));
854 if (cchGidCol)
855 RTPrintf(" %*s", cchGidCol,
856 rtCmdLsDecimalFormatGroup(pOpts, pEntry->Info.Attr.u.Unix.gid, pEntry->pszGroup, pszTmp, cbTmp));
857 RTPrintf(" %*s", cchSizeCol, rtCmdLsFormatSize(pOpts, pEntry->Info.cbObject, pszTmp, cbTmp));
858
859 PCRTTIMESPEC pTime = (PCRTTIMESPEC)((uintptr_t)pEntry + offTime);
860 RTPrintf(" %s", rtCmdLsFormatTimestamp(pOpts, pTime, pszTmp, cbTmp));
861
862 RTPrintf(" %s\n", rtCmdLsFormatName(pOpts, pEntry->szName, pszTmp, cbTmp));
863 }
864
865 return RTEXITCODE_SUCCESS;
866}
867
868
869/**
870 * RTCMDLSFORMAT_SINGLE: -1
871 */
872static RTEXITCODE rtCmdLsDisplayCollectionInSingleFormat(PRTCMDLSOPTS pOpts, PRTCMDLSCOLLECTION pCollection,
873 char *pszTmp, size_t cbTmp, size_t cchAllocatedCol)
874{
875 if (cchAllocatedCol > 0)
876 for (size_t i = 0; i < pCollection->cEntries; i++)
877 {
878 PRTCMDLSENTRY pEntry = pCollection->papEntries[i];
879 RTPrintf("%*s %s\n",
880 cchAllocatedCol, rtCmdLsFormatBlocks(pOpts, pEntry->Info.cbAllocated, pszTmp, cbTmp / 4),
881 rtCmdLsFormatName(pOpts, pEntry->szName, &pszTmp[cbTmp / 4], cbTmp / 4 * 3));
882 }
883 else
884 for (size_t i = 0; i < pCollection->cEntries; i++)
885 {
886 PRTCMDLSENTRY pEntry = pCollection->papEntries[i];
887 RTPrintf("%s\n", rtCmdLsFormatName(pOpts, pEntry->szName, pszTmp, cbTmp));
888 }
889
890 return RTEXITCODE_SUCCESS;
891}
892
893
894/**
895 * RTCMDLSFORMAT_COLS_VERTICAL: default, -C; RTCMDLSFORMAT_COLS_HORIZONTAL: -x
896 */
897static RTEXITCODE rtCmdLsDisplayCollectionInTableFormat(PRTCMDLSOPTS pOpts, PRTCMDLSCOLLECTION pCollection,
898 char *pszTmp, size_t cbTmp, size_t cchAllocatedCol)
899{
900 RT_NOREF(pOpts, pCollection, pszTmp, cbTmp, cchAllocatedCol);
901 RTMsgError("Table output formats not implemented\n");
902 return RTEXITCODE_FAILURE;
903}
904
905
906/**
907 * Does the actual displaying of the entry collections.
908 *
909 * @returns Program exit code.
910 * @param pOpts The options and state.
911 */
912static RTEXITCODE rtCmdLsDisplayCollections(PRTCMDLSOPTS pOpts)
913{
914 rtCmdLsSortCollections(pOpts);
915
916 bool const fNeedCollectionName = pOpts->cCollections > 2
917 || ( pOpts->cCollections == 2
918 && pOpts->papCollections[0]->cEntries > 0);
919 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
920 for (size_t iCollection = 0; iCollection < pOpts->cCollections; iCollection++)
921 {
922 PRTCMDLSCOLLECTION pCollection = pOpts->papCollections[iCollection];
923 char szTmp[RTPATH_MAX*2];
924
925 /* The header. */
926 if (iCollection != 0)
927 {
928 if ( iCollection > 1
929 || pOpts->papCollections[0]->cEntries > 0)
930 RTPrintf("\n");
931 if (fNeedCollectionName)
932 RTPrintf("%s:\n", rtCmdLsFormatName(pOpts, pCollection->szName, szTmp, sizeof(szTmp)));
933 RTPrintf("total %s\n", rtCmdLsFormatBlocks(pOpts, pCollection->cbTotalAllocated, szTmp, sizeof(szTmp)));
934 }
935
936 /* Format the entries. */
937 RTEXITCODE rcExit2;
938 if (pOpts->enmFormat == RTCMDLSFORMAT_MACHINE_READABLE)
939 rcExit2 = rtCmdLsDisplayCollectionInMachineReadableFormat(pOpts, pCollection, szTmp, sizeof(szTmp));
940 else if (pOpts->enmFormat == RTCMDLSFORMAT_COMMAS)
941 rcExit2 = rtCmdLsDisplayCollectionInCvsFormat(pOpts, pCollection, szTmp, sizeof(szTmp));
942 else
943 {
944 /* If the allocated size is requested, calculate the column width. */
945 size_t cchAllocatedCol = 0;
946 if (pOpts->fShowAllocatedSize)
947 {
948 size_t i = pCollection->cEntries;
949 while (i-- > 0)
950 {
951 rtCmdLsFormatBlocks(pOpts, pCollection->papEntries[i]->Info.cbAllocated, szTmp, sizeof(szTmp));
952 size_t cchTmp = strlen(szTmp);
953 if (cchTmp > cchAllocatedCol)
954 cchAllocatedCol = cchTmp;
955 }
956 }
957
958 /* Do the individual formatting. */
959 if (pOpts->enmFormat == RTCMDLSFORMAT_LONG)
960 rcExit2 = rtCmdLsDisplayCollectionInLongFormat(pOpts, pCollection, szTmp, sizeof(szTmp), cchAllocatedCol);
961 else if (pOpts->enmFormat == RTCMDLSFORMAT_SINGLE)
962 rcExit2 = rtCmdLsDisplayCollectionInSingleFormat(pOpts, pCollection, szTmp, sizeof(szTmp), cchAllocatedCol);
963 else
964 rcExit2 = rtCmdLsDisplayCollectionInTableFormat(pOpts, pCollection, szTmp, sizeof(szTmp), cchAllocatedCol);
965 }
966 if (rcExit2 != RTEXITCODE_SUCCESS)
967 rcExit = rcExit2;
968 }
969 return rcExit;
970}
971
972
973/**
974 * Frees all collections and their entries.
975 * @param pOpts The options and state.
976 */
977static void rtCmdLsFreeCollections(PRTCMDLSOPTS pOpts)
978{
979 size_t i = pOpts->cCollections;
980 while (i-- > 0)
981 {
982 PRTCMDLSCOLLECTION pCollection = pOpts->papCollections[i];
983 PPRTCMDLSENTRY papEntries = pCollection->papEntries;
984 size_t j = pCollection->cEntries;
985 while (j-- > 0)
986 {
987 RTMemFree(papEntries[j]);
988 papEntries[j] = NULL;
989 }
990 RTMemFree(papEntries);
991 pCollection->papEntries = NULL;
992 pCollection->cEntries = 0;
993 pCollection->cEntriesAllocated = 0;
994 RTMemFree(pCollection);
995 pOpts->papCollections[i] = NULL;
996 }
997
998 RTMemFree(pOpts->papCollections);
999 pOpts->papCollections = NULL;
1000 pOpts->cCollections = 0;
1001 pOpts->cCollectionsAllocated = 0;
1002}
1003
1004
1005/**
1006 * Allocates a new collection.
1007 *
1008 * @returns Pointer to the collection.
1009 * @param pOpts The options and state.
1010 * @param pszName The collection name. Empty for special first
1011 * collection.
1012 */
1013static PRTCMDLSCOLLECTION rtCmdLsNewCollection(PRTCMDLSOPTS pOpts, const char *pszName)
1014{
1015 /* Grow the pointer table? */
1016 if (pOpts->cCollections >= pOpts->cCollectionsAllocated)
1017 {
1018 size_t cNew = pOpts->cCollectionsAllocated ? pOpts->cCollectionsAllocated * 2 : 16;
1019 void *pvNew = RTMemRealloc(pOpts->papCollections, cNew * sizeof(pOpts->papCollections[0]));
1020 if (!pvNew)
1021 {
1022 RTMsgError("Out of memory! (resize collections)");
1023 return NULL;
1024 }
1025 pOpts->cCollectionsAllocated = cNew;
1026 pOpts->papCollections = (PPRTCMDLSCOLLECTION)pvNew;
1027
1028 /* If this is the first time and pszName isn't empty, add the zero'th
1029 entry for the command line stuff (hardcoded first collection). */
1030 if ( pOpts->cCollections == 0
1031 && *pszName)
1032 {
1033 PRTCMDLSCOLLECTION pCollection = (PRTCMDLSCOLLECTION)RTMemAllocZ(RT_UOFFSETOF(RTCMDLSCOLLECTION, szName[1]));
1034 if (!pCollection)
1035 {
1036 RTMsgError("Out of memory! (collection)");
1037 return NULL;
1038 }
1039 pOpts->papCollections[0] = pCollection;
1040 pOpts->cCollections = 1;
1041 }
1042 }
1043
1044 /* Add new collection. */
1045 size_t cbName = strlen(pszName) + 1;
1046 PRTCMDLSCOLLECTION pCollection = (PRTCMDLSCOLLECTION)RTMemAllocZ(RT_UOFFSETOF_DYN(RTCMDLSCOLLECTION, szName[cbName]));
1047 if (pCollection)
1048 {
1049 memcpy(pCollection->szName, pszName, cbName);
1050 pOpts->papCollections[pOpts->cCollections++] = pCollection;
1051 }
1052 else
1053 RTMsgError("Out of memory! (collection)");
1054 return pCollection;
1055}
1056
1057
1058/**
1059 * Adds one entry to a collection.
1060 * @returns Program exit code
1061 * @param pCollection The collection.
1062 * @param pszEntry The entry name.
1063 * @param pInfo The entry info.
1064 * @param pszOwner The owner name if available, otherwise NULL.
1065 * @param pszGroup The group anme if available, otherwise NULL.
1066 * @param pszTarget The symbolic link target if applicable and
1067 * available, otherwise NULL.
1068 */
1069static RTEXITCODE rtCmdLsAddOne(PRTCMDLSCOLLECTION pCollection, const char *pszEntry, PRTFSOBJINFO pInfo,
1070 const char *pszOwner, const char *pszGroup, const char *pszTarget)
1071{
1072
1073 /* Make sure there is space in the collection for the new entry. */
1074 if (pCollection->cEntries >= pCollection->cEntriesAllocated)
1075 {
1076 size_t cNew = pCollection->cEntriesAllocated ? pCollection->cEntriesAllocated * 2 : 16;
1077 void *pvNew = RTMemRealloc(pCollection->papEntries, cNew * sizeof(pCollection->papEntries[0]));
1078 if (!pvNew)
1079 return RTMsgErrorExitFailure("Out of memory! (resize entries)");
1080 pCollection->papEntries = (PPRTCMDLSENTRY)pvNew;
1081 pCollection->cEntriesAllocated = cNew;
1082 }
1083
1084 /* Create and insert a new entry. */
1085 size_t const cchEntry = strlen(pszEntry);
1086 size_t const cbOwner = pszOwner ? strlen(pszOwner) + 1 : 0;
1087 size_t const cbGroup = pszGroup ? strlen(pszGroup) + 1 : 0;
1088 size_t const cbTarget = pszTarget ? strlen(pszTarget) + 1 : 0;
1089 size_t const cbEntry = RT_UOFFSETOF_DYN(RTCMDLSENTRY, szName[cchEntry + 1 + cbOwner + cbGroup + cbTarget]);
1090 PRTCMDLSENTRY pEntry = (PRTCMDLSENTRY)RTMemAlloc(cbEntry);
1091 if (pEntry)
1092 {
1093 pEntry->Info = *pInfo;
1094 pEntry->pszTarget = NULL; /** @todo symbolic links. */
1095 pEntry->pszOwner = NULL;
1096 pEntry->pszGroup = NULL;
1097 pEntry->cchName = cchEntry;
1098 memcpy(pEntry->szName, pszEntry, cchEntry);
1099 pEntry->szName[cchEntry] = '\0';
1100
1101 char *psz = &pEntry->szName[cchEntry + 1];
1102 if (pszTarget)
1103 {
1104 pEntry->pszTarget = psz;
1105 memcpy(psz, pszTarget, cbTarget);
1106 psz += cbTarget;
1107 }
1108 if (pszOwner)
1109 {
1110 pEntry->pszOwner = psz;
1111 memcpy(psz, pszOwner, cbOwner);
1112 psz += cbOwner;
1113 }
1114 if (pszGroup)
1115 {
1116 pEntry->pszGroup = psz;
1117 memcpy(psz, pszGroup, cbGroup);
1118 }
1119
1120 pCollection->papEntries[pCollection->cEntries++] = pEntry;
1121 pCollection->cbTotalAllocated += pEntry->Info.cbAllocated;
1122 pCollection->cbTotalFiles += pEntry->Info.cbObject;
1123 return RTEXITCODE_SUCCESS;
1124 }
1125 return RTMsgErrorExitFailure("Out of memory! (entry)");
1126}
1127
1128
1129/**
1130 * Checks if the entry is to be filtered out.
1131 *
1132 * @returns true if filtered out, false if included.
1133 * @param pOpts The options and state.
1134 * @param pszEntry The entry name.
1135 * @param pInfo The entry info.
1136 */
1137static bool rtCmdLsIsFilteredOut(PRTCMDLSOPTS pOpts, const char *pszEntry, PCRTFSOBJINFO pInfo)
1138{
1139 /*
1140 * Should we filter out this entry?
1141 */
1142 if ( !pOpts->fShowHidden
1143 && (pInfo->Attr.fMode & RTFS_DOS_HIDDEN))
1144 return true;
1145
1146 size_t const cchEntry = strlen(pszEntry);
1147 if ( !pOpts->fShowDotAndDotDot
1148 && cchEntry <= 2
1149 && pszEntry[0] == '.'
1150 && ( cchEntry == 1
1151 || pszEntry[1] == '.' ))
1152 return true;
1153
1154 if ( !pOpts->fShowBackups
1155 && pszEntry[cchEntry - 1] == '~')
1156 return true;
1157 return false;
1158}
1159
1160
1161/**
1162 * Processes a directory, recursing into subdirectories if desired.
1163 *
1164 * @returns Program exit code.
1165 * @param pOpts The options.
1166 * @param hVfsDir The directory.
1167 * @param pszPath Path buffer, RTPATH_MAX in size.
1168 * @param cchPath The length of the current path.
1169 * @param pInfo The parent information.
1170 */
1171static RTEXITCODE rtCmdLsProcessDirectory(PRTCMDLSOPTS pOpts, RTVFSDIR hVfsDir, char *pszPath, size_t cchPath, PCRTFSOBJINFO pInfo)
1172{
1173 /*
1174 * Create a new collection for this directory.
1175 */
1176 RT_NOREF(pInfo);
1177 PRTCMDLSCOLLECTION pCollection = rtCmdLsNewCollection(pOpts, pszPath);
1178 if (!pCollection)
1179 return RTEXITCODE_FAILURE;
1180
1181 /*
1182 * Process the directory entries.
1183 */
1184 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
1185 size_t cbDirEntryAlloced = sizeof(RTDIRENTRYEX);
1186 PRTDIRENTRYEX pDirEntry = (PRTDIRENTRYEX)RTMemTmpAlloc(cbDirEntryAlloced);
1187 if (!pDirEntry)
1188 return RTMsgErrorExitFailure("Out of memory! (direntry buffer)");
1189
1190 for (;;)
1191 {
1192 /*
1193 * Read the next entry.
1194 */
1195 size_t cbDirEntry = cbDirEntryAlloced;
1196 int rc = RTVfsDirReadEx(hVfsDir, pDirEntry, &cbDirEntry, RTFSOBJATTRADD_UNIX);
1197 if (RT_FAILURE(rc))
1198 {
1199 if (rc == VERR_BUFFER_OVERFLOW)
1200 {
1201 RTMemTmpFree(pDirEntry);
1202 cbDirEntryAlloced = RT_ALIGN_Z(RT_MIN(cbDirEntry, cbDirEntryAlloced) + 64, 64);
1203 pDirEntry = (PRTDIRENTRYEX)RTMemTmpAlloc(cbDirEntryAlloced);
1204 if (pDirEntry)
1205 continue;
1206 rcExit = RTMsgErrorExitFailure("Out of memory (direntry buffer)");
1207 }
1208 else if (rc != VERR_NO_MORE_FILES)
1209 rcExit = RTMsgErrorExitFailure("RTVfsDirReadEx failed: %Rrc\n", rc);
1210 break;
1211 }
1212
1213 /*
1214 * Process the entry.
1215 */
1216 if (rtCmdLsIsFilteredOut(pOpts, pDirEntry->szName, &pDirEntry->Info))
1217 continue;
1218
1219
1220 const char *pszOwner = NULL;
1221 RTFSOBJINFO OwnerInfo;
1222 if (pDirEntry->Info.Attr.u.Unix.uid != NIL_RTUID && pOpts->fShowOwner)
1223 {
1224 rc = RTVfsDirQueryPathInfo(hVfsDir, pDirEntry->szName, &OwnerInfo, RTFSOBJATTRADD_UNIX_OWNER, RTPATH_F_ON_LINK);
1225 if (RT_SUCCESS(rc) && OwnerInfo.Attr.u.UnixOwner.szName[0])
1226 pszOwner = &OwnerInfo.Attr.u.UnixOwner.szName[0];
1227 }
1228
1229 const char *pszGroup = NULL;
1230 RTFSOBJINFO GroupInfo;
1231 if (pDirEntry->Info.Attr.u.Unix.gid != NIL_RTGID && pOpts->fShowGroup)
1232 {
1233 rc = RTVfsDirQueryPathInfo(hVfsDir, pDirEntry->szName, &GroupInfo, RTFSOBJATTRADD_UNIX_GROUP, RTPATH_F_ON_LINK);
1234 if (RT_SUCCESS(rc) && GroupInfo.Attr.u.UnixGroup.szName[0])
1235 pszGroup = &GroupInfo.Attr.u.UnixGroup.szName[0];
1236 }
1237
1238 RTEXITCODE rcExit2 = rtCmdLsAddOne(pCollection, pDirEntry->szName, &pDirEntry->Info, pszOwner, pszGroup, NULL);
1239 if (rcExit2 != RTEXITCODE_SUCCESS)
1240 rcExit = rcExit2;
1241 }
1242
1243 RTMemTmpFree(pDirEntry);
1244
1245 /*
1246 * Recurse into subdirectories if requested.
1247 */
1248 if (pOpts->fRecursive)
1249 {
1250 for (uint32_t i = 0; i < pCollection->cEntries; i++)
1251 {
1252 PRTCMDLSENTRY pEntry = pCollection->papEntries[i];
1253 if (RTFS_IS_SYMLINK(pEntry->Info.Attr.fMode))
1254 {
1255 if (!pOpts->fFollowSymlinksInDirs)
1256 continue;
1257 /** @todo implement following symbolic links in the tree. */
1258 continue;
1259 }
1260 else if ( !RTFS_IS_DIRECTORY(pEntry->Info.Attr.fMode)
1261 || ( pEntry->szName[0] == '.'
1262 && ( pEntry->szName[1] == '\0'
1263 || ( pEntry->szName[1] == '.'
1264 && pEntry->szName[2] == '\0'))) )
1265 continue;
1266
1267 /* Open subdirectory and process it. */
1268 RTVFSDIR hSubDir;
1269 int rc = RTVfsDirOpenDir(hVfsDir, pEntry->szName, 0 /*fFlags*/, &hSubDir);
1270 if (RT_SUCCESS(rc))
1271 {
1272 if (cchPath + 1 + pEntry->cchName + 1 < RTPATH_MAX)
1273 {
1274 pszPath[cchPath] = RTPATH_SLASH;
1275 memcpy(&pszPath[cchPath + 1], pEntry->szName, pEntry->cchName + 1);
1276 RTEXITCODE rcExit2 = rtCmdLsProcessDirectory(pOpts, hSubDir, pszPath,
1277 cchPath + 1 + pEntry->cchName, &pEntry->Info);
1278 if (rcExit2 != RTEXITCODE_SUCCESS)
1279 rcExit = rcExit2;
1280 pszPath[cchPath] = '\0';
1281 }
1282 else
1283 rcExit = RTMsgErrorExitFailure("Too deep recursion: %s%c%s", pszPath, RTPATH_SLASH, pEntry->szName);
1284 RTVfsDirRelease(hSubDir);
1285 }
1286 else
1287 rcExit = RTMsgErrorExitFailure("RTVfsDirOpenDir failed on %s in %s: %Rrc\n", pEntry->szName, pszPath, rc);
1288 }
1289 }
1290 return rcExit;
1291}
1292
1293
1294/**
1295 * Processes one argument.
1296 *
1297 * @returns Program exit code.
1298 * @param pOpts The options.
1299 * @param pszArg The argument.
1300 */
1301static RTEXITCODE rtCmdLsProcessArgument(PRTCMDLSOPTS pOpts, const char *pszArg)
1302{
1303 /*
1304 * Query info about the object 'pszArg' indicates.
1305 */
1306 RTERRINFOSTATIC ErrInfo;
1307 uint32_t offError;
1308 RTFSOBJINFO Info;
1309 uint32_t fPath = pOpts->fFollowSymlinkToAnyArgs ? RTPATH_F_FOLLOW_LINK : RTPATH_F_ON_LINK;
1310 int rc = RTVfsChainQueryInfo(pszArg, &Info, RTFSOBJATTRADD_UNIX, fPath, &offError, RTErrInfoInitStatic(&ErrInfo));
1311 if (RT_FAILURE(rc))
1312 return RTVfsChainMsgErrorExitFailure("RTVfsChainQueryInfo", pszArg, rc, offError, &ErrInfo.Core);
1313
1314 /* Symbolic links requires special handling of course. */
1315 if (RTFS_IS_SYMLINK(Info.Attr.fMode))
1316 {
1317 if (pOpts->fFollowSymlinkToDirArgs)
1318 {
1319 RTFSOBJINFO Info2;
1320 rc = RTVfsChainQueryInfo(pszArg, &Info2, RTFSOBJATTRADD_UNIX, RTPATH_F_FOLLOW_LINK,
1321 &offError, RTErrInfoInitStatic(&ErrInfo));
1322 if (RT_SUCCESS(rc) && !RTFS_IS_DIRECTORY(Info.Attr.fMode))
1323 Info = Info2;
1324 }
1325 }
1326
1327 /*
1328 * If it's not a directory or we've been told to process directories
1329 * without going into them, just add it to the default collection.
1330 */
1331 if ( !pOpts->fFollowDirectoryArgs
1332 || !RTFS_IS_DIRECTORY(Info.Attr.fMode))
1333 {
1334 if ( pOpts->cCollections > 0
1335 || rtCmdLsNewCollection(pOpts, "") != NULL)
1336 {
1337 const char *pszOwner = NULL;
1338 RTFSOBJINFO OwnerInfo;
1339 if (Info.Attr.u.Unix.uid != NIL_RTUID && pOpts->fShowOwner)
1340 {
1341 rc = RTVfsChainQueryInfo(pszArg, &OwnerInfo, RTFSOBJATTRADD_UNIX_OWNER, fPath, NULL, NULL);
1342 if (RT_SUCCESS(rc) && OwnerInfo.Attr.u.UnixOwner.szName[0])
1343 pszOwner = &OwnerInfo.Attr.u.UnixOwner.szName[0];
1344 }
1345
1346 const char *pszGroup = NULL;
1347 RTFSOBJINFO GroupInfo;
1348 if (Info.Attr.u.Unix.gid != NIL_RTGID && pOpts->fShowGroup)
1349 {
1350 rc = RTVfsChainQueryInfo(pszArg, &GroupInfo, RTFSOBJATTRADD_UNIX_GROUP, fPath, NULL, NULL);
1351 if (RT_SUCCESS(rc) && GroupInfo.Attr.u.UnixGroup.szName[0])
1352 pszGroup = &GroupInfo.Attr.u.UnixGroup.szName[0];
1353 }
1354
1355 return rtCmdLsAddOne(pOpts->papCollections[0], pszArg, &Info, pszOwner, pszGroup, NULL);
1356 }
1357 return RTEXITCODE_FAILURE;
1358 }
1359
1360 /*
1361 * Open the directory.
1362 */
1363 RTVFSDIR hVfsDir;
1364 rc = RTVfsChainOpenDir(pszArg, 0 /*fFlags*/, &hVfsDir, &offError, RTErrInfoInitStatic(&ErrInfo));
1365 if (RT_FAILURE(rc))
1366 return RTVfsChainMsgErrorExitFailure("RTVfsChainOpenDir", pszArg, rc, offError, &ErrInfo.Core);
1367
1368 RTEXITCODE rcExit;
1369 char szPath[RTPATH_MAX];
1370 size_t cchPath = strlen(pszArg);
1371 if (cchPath < sizeof(szPath))
1372 {
1373 memcpy(szPath, pszArg, cchPath + 1);
1374 rcExit = rtCmdLsProcessDirectory(pOpts, hVfsDir, szPath, cchPath, &Info);
1375 }
1376 else
1377 rcExit = RTMsgErrorExitFailure("Too long argument: %s", pszArg);
1378 RTVfsDirRelease(hVfsDir);
1379 return rcExit;
1380}
1381
1382
1383/**
1384 * A /bin/ls clone.
1385 *
1386 * @returns Program exit code.
1387 *
1388 * @param cArgs The number of arguments.
1389 * @param papszArgs The argument vector. (Note that this may be
1390 * reordered, so the memory must be writable.)
1391 */
1392RTR3DECL(RTEXITCODE) RTFsCmdLs(unsigned cArgs, char **papszArgs)
1393{
1394 /*
1395 * Parse the command line.
1396 */
1397#define OPT_AUTHOR 1000
1398#define OPT_BLOCK_SIZE 1001
1399#define OPT_COLOR 1002
1400#define OPT_FILE_TYPE 1003
1401#define OPT_FORMAT 1004
1402#define OPT_FULL_TIME 1005
1403#define OPT_GROUP_DIRECTORIES_FIRST 1006
1404#define OPT_SI 1007
1405#define OPT_DEREFERENCE_COMMAND_LINE_SYMLINK_TO_DIR 1008
1406#define OPT_HIDE 1009
1407#define OPT_INDICATOR_STYLE 1010
1408#define OPT_MACHINE_READABLE 1011
1409#define OPT_SHOW_CONTROL_CHARS 1012
1410#define OPT_QUOTING_STYLE 1013
1411#define OPT_SORT 1014
1412#define OPT_TIME 1015
1413#define OPT_TIME_STYLE 1016
1414 static const RTGETOPTDEF s_aOptions[] =
1415 {
1416 { "--all", 'a', RTGETOPT_REQ_NOTHING },
1417 { "--almost-all", 'A', RTGETOPT_REQ_NOTHING },
1418 //{ "--author", OPT_AUTHOR, RTGETOPT_REQ_NOTHING },
1419 { "--escape", 'b', RTGETOPT_REQ_NOTHING },
1420 { "--block-size", OPT_BLOCK_SIZE, RTGETOPT_REQ_UINT32 },
1421 { "--ctime", 'c', RTGETOPT_REQ_NOTHING },
1422 //{ "--columns", 'C', RTGETOPT_REQ_NOTHING },
1423 //{ "--color", OPT_COLOR, RTGETOPT_OPT_STRING },
1424 { "--directory", 'd', RTGETOPT_REQ_NOTHING },
1425 //{ "--dired", 'D', RTGETOPT_REQ_NOTHING },
1426 { "--dash-f", 'f', RTGETOPT_REQ_NOTHING },
1427 //{ "--classify", 'F', RTGETOPT_REQ_NOTHING },
1428 //{ "--file-type", OPT_FILE_TYPE, RTGETOPT_REQ_NOTHING },
1429 { "--format", OPT_FORMAT, RTGETOPT_REQ_STRING },
1430 { "--full-time", OPT_FULL_TIME, RTGETOPT_REQ_NOTHING },
1431 { "--dash-g", 'g', RTGETOPT_REQ_NOTHING },
1432 { "--group-directories-first", OPT_GROUP_DIRECTORIES_FIRST, RTGETOPT_REQ_NOTHING },
1433 { "--no-group", 'G', RTGETOPT_REQ_NOTHING },
1434 { "--human-readable", 'h', RTGETOPT_REQ_NOTHING },
1435 { "--si", OPT_SI, RTGETOPT_REQ_NOTHING },
1436 { "--dereference-command-line", 'H', RTGETOPT_REQ_NOTHING },
1437 { "--dereference-command-line-symlink-to-dir", OPT_DEREFERENCE_COMMAND_LINE_SYMLINK_TO_DIR, RTGETOPT_REQ_NOTHING },
1438 //{ "--hide" OPT_HIDE, RTGETOPT_REQ_STRING },
1439 //{ "--indicator-style" OPT_INDICATOR_STYLE, RTGETOPT_REQ_STRING },
1440 { "--inode", 'i', RTGETOPT_REQ_NOTHING },
1441 { "--block-size-1kib", 'k', RTGETOPT_REQ_NOTHING },
1442 { "--long", 'l', RTGETOPT_REQ_NOTHING },
1443 { "--dereference", 'L', RTGETOPT_REQ_NOTHING },
1444 { "--format-commas", 'm', RTGETOPT_REQ_NOTHING },
1445 { "--machinereadable", OPT_MACHINE_READABLE, RTGETOPT_REQ_NOTHING },
1446 { "--machine-readable", OPT_MACHINE_READABLE, RTGETOPT_REQ_NOTHING },
1447 { "--numeric-uid-gid", 'n', RTGETOPT_REQ_NOTHING },
1448 { "--literal", 'N', RTGETOPT_REQ_NOTHING },
1449 { "--long-without-group-info", 'o', RTGETOPT_REQ_NOTHING },
1450 //{ "--indicator-style", 'p', RTGETOPT_REQ_STRING },
1451 { "--hide-control-chars", 'q', RTGETOPT_REQ_NOTHING },
1452 { "--show-control-chars", OPT_SHOW_CONTROL_CHARS, RTGETOPT_REQ_NOTHING },
1453 //{ "--quote-name", 'Q', RTGETOPT_REQ_NOTHING },
1454 //{ "--quoting-style", OPT_QUOTING_STYLE, RTGETOPT_REQ_STRING },
1455 { "--reverse", 'r', RTGETOPT_REQ_NOTHING },
1456 { "--recursive", 'R', RTGETOPT_REQ_NOTHING },
1457 { "--size", 's', RTGETOPT_REQ_NOTHING },
1458 { "--sort-by-size", 'S', RTGETOPT_REQ_NOTHING },
1459 { "--sort", OPT_SORT, RTGETOPT_REQ_STRING },
1460 { "--time", OPT_TIME, RTGETOPT_REQ_STRING },
1461 { "--time-style", OPT_TIME_STYLE, RTGETOPT_REQ_STRING },
1462 { "--sort-by-time", 't', RTGETOPT_REQ_NOTHING },
1463 { "--tabsize", 'T', RTGETOPT_REQ_UINT8 },
1464 { "--atime", 'u', RTGETOPT_REQ_NOTHING },
1465 { "--unsorted", 'U', RTGETOPT_REQ_NOTHING },
1466 { "--version-sort", 'v', RTGETOPT_REQ_NOTHING },
1467 { "--width", 'w', RTGETOPT_REQ_UINT32 },
1468 { "--list-by-line", 'x', RTGETOPT_REQ_NOTHING },
1469 { "--sort-by-extension", 'X', RTGETOPT_REQ_NOTHING },
1470 { "--one-file-per-line", '1', RTGETOPT_REQ_NOTHING },
1471 { "--help", '?', RTGETOPT_REQ_NOTHING },
1472 };
1473
1474 RTCMDLSOPTS Opts;
1475 Opts.fFollowSymlinksInDirs = false;
1476 Opts.fFollowSymlinkToAnyArgs = false;
1477 Opts.fFollowSymlinkToDirArgs = false;
1478 Opts.fFollowDirectoryArgs = true;
1479 Opts.fRecursive = false;
1480 Opts.fShowHidden = false;
1481 Opts.fShowDotAndDotDot = false;
1482 Opts.fShowBackups = true;
1483 Opts.enmSort = RTCMDLSSORT_NAME;
1484 Opts.fReverseSort = false;
1485 Opts.fGroupDirectoriesFirst = false;
1486 Opts.enmFormat = RTCMDLSFORMAT_COLS_VERTICAL;
1487 Opts.fEscapeNonGraphicChars = false;
1488 Opts.fEscapeControlChars = true;
1489 Opts.fHideControlChars = false;
1490 Opts.fHumanReadableSizes = false; /**< -h */
1491 Opts.fSiUnits = false;
1492 Opts.cbBlock = 0;
1493 Opts.fShowOwner = true;
1494 Opts.fShowGroup = true;
1495 Opts.fNumericalIds = false;
1496 Opts.fShowINode = false;
1497 Opts.fShowAllocatedSize = false;
1498 Opts.cchTab = 8;
1499 Opts.cchWidth = 80;
1500 Opts.enmColor = RTCMDLSCOLOR_NONE;
1501 Opts.enmTime = RTCMDLSTIME_MTIME;
1502 Opts.enmTimeStyle = RTCMDLSTIMESTYLE_LOCALE;
1503 Opts.pszTimeCustom = NULL;
1504
1505 Opts.cCollections = 0;
1506 Opts.cCollectionsAllocated = 0;
1507 Opts.papCollections = NULL;
1508
1509
1510 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
1511 unsigned cProcessed = 0;
1512 RTGETOPTSTATE GetState;
1513 int rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1,
1514 RTGETOPTINIT_FLAGS_OPTS_FIRST);
1515 if (RT_FAILURE(rc))
1516 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "RTGetOptInit: %Rrc", rc);
1517
1518 for (;;)
1519 {
1520 RTGETOPTUNION ValueUnion;
1521 int chOpt = RTGetOpt(&GetState, &ValueUnion);
1522 switch (chOpt)
1523 {
1524 case 0:
1525 /* When reaching the end of arguments without having processed any
1526 files/dirs/whatever yet, we do the current directory. */
1527 if (cProcessed > 0)
1528 {
1529 RTEXITCODE rcExit2 = rtCmdLsDisplayCollections(&Opts);
1530 if (rcExit2 != RTEXITCODE_SUCCESS)
1531 rcExit = rcExit2;
1532 rtCmdLsFreeCollections(&Opts);
1533 return rcExit;
1534 }
1535 ValueUnion.psz = ".";
1536 RT_FALL_THRU();
1537 case VINF_GETOPT_NOT_OPTION:
1538 {
1539 RTEXITCODE rcExit2 = rtCmdLsProcessArgument(&Opts, ValueUnion.psz);
1540 if (rcExit2 != RTEXITCODE_SUCCESS)
1541 rcExit = rcExit2;
1542 cProcessed++;
1543 break;
1544 }
1545
1546 case 'a':
1547 Opts.fShowHidden = true;
1548 Opts.fShowDotAndDotDot = true;
1549 break;
1550
1551 case 'A':
1552 Opts.fShowHidden = true;
1553 Opts.fShowDotAndDotDot = false;
1554 break;
1555
1556 case 'b':
1557 Opts.fEscapeNonGraphicChars = true;
1558 break;
1559
1560 case OPT_BLOCK_SIZE:
1561 if (!ValueUnion.u32)
1562 {
1563 Assert(!Opts.papCollections);
1564 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Invalid block size: %u", ValueUnion.u32);
1565 }
1566 Opts.cbBlock = ValueUnion.u32;
1567 Opts.fHumanReadableSizes = false;
1568 Opts.fSiUnits = false;
1569 break;
1570
1571 case 'c':
1572 Opts.enmTime = RTCMDLSTIME_CTIME;
1573 break;
1574
1575 case 'C':
1576 Opts.enmFormat = RTCMDLSFORMAT_COLS_VERTICAL;
1577 break;
1578
1579 case 'd':
1580 Opts.fFollowDirectoryArgs = false;
1581 Opts.fFollowSymlinkToAnyArgs = false;
1582 Opts.fFollowSymlinkToDirArgs = false;
1583 Opts.fRecursive = false;
1584 break;
1585
1586 case 'f':
1587 Opts.fShowHidden = true;
1588 Opts.fShowDotAndDotDot = true;
1589 if (Opts.enmFormat == RTCMDLSFORMAT_LONG)
1590 Opts.enmFormat = RTCMDLSFORMAT_COLS_VERTICAL;
1591 Opts.enmColor = RTCMDLSCOLOR_NONE;
1592 Opts.enmSort = RTCMDLSSORT_NONE;
1593 break;
1594
1595 case OPT_FORMAT:
1596 if ( strcmp(ValueUnion.psz, "across") == 0
1597 || strcmp(ValueUnion.psz, "horizontal") == 0)
1598 Opts.enmFormat = RTCMDLSFORMAT_COLS_HORIZONTAL;
1599 else if (strcmp(ValueUnion.psz, "commas") == 0)
1600 Opts.enmFormat = RTCMDLSFORMAT_COMMAS;
1601 else if ( strcmp(ValueUnion.psz, "long") == 0
1602 || strcmp(ValueUnion.psz, "verbose") == 0)
1603 Opts.enmFormat = RTCMDLSFORMAT_LONG;
1604 else if (strcmp(ValueUnion.psz, "single-column") == 0)
1605 Opts.enmFormat = RTCMDLSFORMAT_SINGLE;
1606 else if (strcmp(ValueUnion.psz, "vertical") == 0)
1607 Opts.enmFormat = RTCMDLSFORMAT_COLS_VERTICAL;
1608 else if (strcmp(ValueUnion.psz, "machine-readable") == 0)
1609 Opts.enmFormat = RTCMDLSFORMAT_MACHINE_READABLE;
1610 else
1611 {
1612 Assert(!Opts.papCollections);
1613 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown format: %s", ValueUnion.psz);
1614 }
1615 break;
1616
1617 case OPT_FULL_TIME:
1618 Opts.enmFormat = RTCMDLSFORMAT_LONG;
1619 Opts.enmTimeStyle = RTCMDLSTIMESTYLE_FULL_ISO;
1620 break;
1621
1622 case 'g':
1623 Opts.enmFormat = RTCMDLSFORMAT_LONG;
1624 Opts.fShowOwner = false;
1625 break;
1626
1627 case OPT_GROUP_DIRECTORIES_FIRST:
1628 Opts.fGroupDirectoriesFirst = true;
1629 break;
1630
1631 case 'G':
1632 Opts.fShowGroup = false;
1633 break;
1634
1635 case 'h':
1636 Opts.fHumanReadableSizes = true;
1637 Opts.fSiUnits = false;
1638 break;
1639
1640 case OPT_SI:
1641 Opts.fHumanReadableSizes = true;
1642 Opts.fSiUnits = true;
1643 break;
1644
1645 case 'H':
1646 Opts.fFollowSymlinkToAnyArgs = true;
1647 Opts.fFollowSymlinkToDirArgs = true;
1648 break;
1649
1650 case OPT_DEREFERENCE_COMMAND_LINE_SYMLINK_TO_DIR:
1651 Opts.fFollowSymlinkToAnyArgs = false;
1652 Opts.fFollowSymlinkToDirArgs = true;
1653 break;
1654
1655 case 'i':
1656 Opts.fShowINode = true;
1657 break;
1658
1659 case 'k':
1660 Opts.cbBlock = _1K;
1661 Opts.fHumanReadableSizes = false;
1662 Opts.fSiUnits = false;
1663 break;
1664
1665 case 'l':
1666 Opts.enmFormat = RTCMDLSFORMAT_LONG;
1667 break;
1668
1669 case 'L':
1670 Opts.fFollowSymlinksInDirs = true;
1671 Opts.fFollowSymlinkToAnyArgs = true;
1672 Opts.fFollowSymlinkToDirArgs = true;
1673 break;
1674
1675 case 'm':
1676 Opts.enmFormat = RTCMDLSFORMAT_COMMAS;
1677 break;
1678
1679 case OPT_MACHINE_READABLE:
1680 Opts.enmFormat = RTCMDLSFORMAT_MACHINE_READABLE;
1681 break;
1682
1683 case 'n':
1684 Opts.fNumericalIds = true;
1685 break;
1686
1687 case 'N':
1688 Opts.fEscapeNonGraphicChars = false;
1689 Opts.fEscapeControlChars = false;
1690 Opts.fHideControlChars = false;
1691 break;
1692
1693 case 'o':
1694 Opts.enmFormat = RTCMDLSFORMAT_LONG;
1695 Opts.fShowGroup = false;
1696 break;
1697
1698 case 'q':
1699 Opts.fHideControlChars = true;
1700 break;
1701
1702 case OPT_SHOW_CONTROL_CHARS:
1703 Opts.fHideControlChars = true;
1704 break;
1705
1706 case 'r':
1707 Opts.fReverseSort = true;
1708 break;
1709
1710 case 'R':
1711 Opts.fRecursive = true;
1712 break;
1713
1714 case 's':
1715 Opts.fShowAllocatedSize = true;
1716 break;
1717
1718 case 'S':
1719 Opts.enmSort = RTCMDLSSORT_SIZE;
1720 break;
1721
1722 case OPT_SORT:
1723 if (strcmp(ValueUnion.psz, "none") == 0)
1724 Opts.enmSort = RTCMDLSSORT_NONE;
1725 else if (strcmp(ValueUnion.psz, "extension") == 0)
1726 Opts.enmSort = RTCMDLSSORT_EXTENSION;
1727 else if (strcmp(ValueUnion.psz, "size") == 0)
1728 Opts.enmSort = RTCMDLSSORT_SIZE;
1729 else if (strcmp(ValueUnion.psz, "time") == 0)
1730 Opts.enmSort = RTCMDLSSORT_TIME;
1731 else if (strcmp(ValueUnion.psz, "version") == 0)
1732 Opts.enmSort = RTCMDLSSORT_VERSION;
1733 else
1734 {
1735 Assert(!Opts.papCollections);
1736 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown sort by: %s", ValueUnion.psz);
1737 }
1738 break;
1739
1740 case OPT_TIME:
1741 if ( strcmp(ValueUnion.psz, "btime") == 0
1742 || strcmp(ValueUnion.psz, "birth") == 0)
1743 Opts.enmTime = RTCMDLSTIME_BTIME;
1744 else if ( strcmp(ValueUnion.psz, "ctime") == 0
1745 || strcmp(ValueUnion.psz, "status") == 0)
1746 Opts.enmTime = RTCMDLSTIME_CTIME;
1747 else if ( strcmp(ValueUnion.psz, "mtime") == 0
1748 || strcmp(ValueUnion.psz, "write") == 0
1749 || strcmp(ValueUnion.psz, "modify") == 0)
1750 Opts.enmTime = RTCMDLSTIME_MTIME;
1751 else if ( strcmp(ValueUnion.psz, "atime") == 0
1752 || strcmp(ValueUnion.psz, "access") == 0
1753 || strcmp(ValueUnion.psz, "use") == 0)
1754 Opts.enmTime = RTCMDLSTIME_ATIME;
1755 else
1756 {
1757 Assert(!Opts.papCollections);
1758 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown time attribute: %s", ValueUnion.psz);
1759 }
1760 break;
1761
1762 case OPT_TIME_STYLE:
1763 if (strcmp(ValueUnion.psz, "full-iso") == 0)
1764 Opts.enmTimeStyle = RTCMDLSTIMESTYLE_FULL_ISO;
1765 else if (strcmp(ValueUnion.psz, "long-iso") == 0)
1766 Opts.enmTimeStyle = RTCMDLSTIMESTYLE_LONG_ISO;
1767 else if (strcmp(ValueUnion.psz, "iso") == 0)
1768 Opts.enmTimeStyle = RTCMDLSTIMESTYLE_ISO;
1769 else if (strcmp(ValueUnion.psz, "locale") == 0)
1770 Opts.enmTimeStyle = RTCMDLSTIMESTYLE_LOCALE;
1771 else if (*ValueUnion.psz == '+')
1772 {
1773 Opts.enmTimeStyle = RTCMDLSTIMESTYLE_CUSTOM;
1774 Opts.pszTimeCustom = ValueUnion.psz;
1775 }
1776 else
1777 {
1778 Assert(!Opts.papCollections);
1779 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown sort by: %s", ValueUnion.psz);
1780 }
1781 break;
1782
1783 case 't':
1784 Opts.enmSort = RTCMDLSSORT_TIME;
1785 break;
1786
1787 case 'T':
1788 Opts.cchTab = ValueUnion.u8;
1789 break;
1790
1791 case 'u':
1792 Opts.enmTime = RTCMDLSTIME_ATIME;
1793 break;
1794
1795 case 'U':
1796 Opts.enmSort = RTCMDLSSORT_NONE;
1797 break;
1798
1799 case 'v':
1800 Opts.enmSort = RTCMDLSSORT_VERSION;
1801 break;
1802
1803 case 'w':
1804 Opts.cchWidth = ValueUnion.u32;
1805 break;
1806
1807 case 'x':
1808 Opts.enmFormat = RTCMDLSFORMAT_COLS_HORIZONTAL;
1809 break;
1810
1811 case 'X':
1812 Opts.enmSort = RTCMDLSSORT_EXTENSION;
1813 break;
1814
1815 case '1':
1816 Opts.enmFormat = RTCMDLSFORMAT_SINGLE;
1817 break;
1818
1819 case '?':
1820 {
1821 RTPrintf("Usage: to be written\n"
1822 "Options dump:\n");
1823 for (unsigned i = 0; i < RT_ELEMENTS(s_aOptions); i++)
1824 if (s_aOptions[i].iShort < 127 && s_aOptions[i].iShort >= 0x20)
1825 RTPrintf(" -%c,%s\n", s_aOptions[i].iShort, s_aOptions[i].pszLong);
1826 else
1827 RTPrintf(" %s\n", s_aOptions[i].pszLong);
1828#ifdef RT_OS_WINDOWS
1829 const char *pszProgNm = RTPathFilename(papszArgs[0]);
1830 RTPrintf("\n"
1831 "The path prefix '\\\\:iprtnt:\\' can be used to access the NT namespace.\n"
1832 "To list devices: %s -la \\\\:iprtnt:\\Device\n"
1833 "To list win32 devices: %s -la \\\\:iprtnt:\\GLOBAL??\n"
1834 "To list the root (hack/bug): %s -la \\\\:iprtnt:\\\n",
1835 pszProgNm, pszProgNm, pszProgNm);
1836#endif
1837 Assert(!Opts.papCollections);
1838 return RTEXITCODE_SUCCESS;
1839 }
1840
1841 case 'V':
1842 RTPrintf("%sr%d\n", RTBldCfgVersion(), RTBldCfgRevision());
1843 Assert(!Opts.papCollections);
1844 return RTEXITCODE_SUCCESS;
1845
1846 default:
1847 Assert(!Opts.papCollections);
1848 return RTGetOptPrintError(chOpt, &ValueUnion);
1849 }
1850 }
1851}
1852
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette