VirtualBox

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

Last change on this file since 73895 was 73502, checked in by vboxsync, 6 years ago

IPRT: GCC 8.2.0 fixes

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