VirtualBox

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

Last change on this file since 72652 was 70422, checked in by vboxsync, 7 years ago

IPRT: Renamed RTCmdLs to RTFsCmdLs and moved it into the runtime, leaving a main wrapper behind. Want to use it in VBoxControl for debugging purposes. [build fix]

  • 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 70422 2018-01-02 11:25:46Z 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 case RTCMDLSSORT_VERSION:
495 pfnCmp = pOpts->fGroupDirectoriesFirst ? rtCmdLsEntryCmpDirFirstVersion : rtCmdLsEntryCmpVersion;
496 break;
497 }
498 if (pfnCmp)
499 {
500 /*
501 * Walk thru the collections and sort their entries.
502 */
503 size_t i = pOpts->cCollections;
504 while (i-- > 0)
505 {
506 PRTCMDLSCOLLECTION pCollection = pOpts->papCollections[i];
507 RTSortApvShell((void **)pCollection->papEntries, pCollection->cEntries, pfnCmp, NULL);
508
509 if (pOpts->fReverseSort)
510 {
511 PPRTCMDLSENTRY papEntries = pCollection->papEntries;
512 size_t iHead = 0;
513 size_t iTail = pCollection->cEntries;
514 while (iHead < iTail)
515 {
516 PRTCMDLSENTRY pTmp = papEntries[iHead];
517 papEntries[iHead] = papEntries[iTail];
518 papEntries[iTail] = pTmp;
519 iHead++;
520 iTail--;
521 }
522 }
523 }
524 }
525
526 /** @todo sort the collections too, except for the first one. */
527}
528
529
530/**
531 * Format human readable size.
532 */
533static const char *rtCmdLsFormatSizeHumanReadable(PRTCMDLSOPTS pOpts, uint64_t cb, char *pszDst, size_t cbDst)
534{
535 if (pOpts->fHumanReadableSizes)
536 {
537 if (!pOpts->fSiUnits)
538 {
539 size_t cch = RTStrPrintf(pszDst, cbDst, "%Rhub", cb);
540 if (pszDst[cch - 1] == 'i')
541 pszDst[cch - 1] = '\0'; /* drop the trailing 'i' */
542 }
543 else
544 RTStrPrintf(pszDst, cbDst, "%Rhui", cb);
545 }
546 else if (pOpts->cbBlock)
547 RTStrFormatU64(pszDst, cbDst, (cb + pOpts->cbBlock - 1) / pOpts->cbBlock, 10, 0, 0, 0);
548 else
549 RTStrFormatU64(pszDst, cbDst, cb, 10, 0, 0, 0);
550 return pszDst;
551}
552
553
554/**
555 * Format block count.
556 */
557static const char *rtCmdLsFormatBlocks(PRTCMDLSOPTS pOpts, uint64_t cb, char *pszDst, size_t cbDst)
558{
559 if (pOpts->fHumanReadableSizes)
560 return rtCmdLsFormatSizeHumanReadable(pOpts, cb, pszDst, cbDst);
561
562 uint32_t cbBlock = pOpts->cbBlock;
563 if (cbBlock == 0)
564 cbBlock = _1K;
565 RTStrFormatU64(pszDst, cbDst, (cb + cbBlock / 2 - 1) / cbBlock, 10, 0, 0, 0);
566 return pszDst;
567}
568
569
570/**
571 * Format file size.
572 */
573static const char *rtCmdLsFormatSize(PRTCMDLSOPTS pOpts, uint64_t cb, char *pszDst, size_t cbDst)
574{
575 if (pOpts->fHumanReadableSizes)
576 return rtCmdLsFormatSizeHumanReadable(pOpts, cb, pszDst, cbDst);
577 if (pOpts->cbBlock > 0)
578 return rtCmdLsFormatBlocks(pOpts, cb, pszDst, cbDst);
579 RTStrFormatU64(pszDst, cbDst, cb, 10, 0, 0, 0);
580 return pszDst;
581}
582
583
584/**
585 * Format name, i.e. escape, hide, quote stuff.
586 */
587static const char *rtCmdLsFormatName(PRTCMDLSOPTS pOpts, const char *pszName, char *pszDst, size_t cbDst)
588{
589 if ( !pOpts->fEscapeNonGraphicChars
590 && !pOpts->fEscapeControlChars
591 && !pOpts->fHideControlChars)
592 return pszName;
593 /** @todo implement name formatting. */
594 RT_NOREF(pszDst, cbDst);
595 return pszName;
596}
597
598
599/**
600 * Figures out the length for a 32-bit number when formatted as decimal.
601 * @returns Number of digits.
602 * @param uValue The number.
603 */
604DECLINLINE(size_t) rtCmdLsDecimalFormatLengthU32(uint32_t uValue)
605{
606 if (uValue < 10)
607 return 1;
608 if (uValue < 100)
609 return 2;
610 if (uValue < 1000)
611 return 3;
612 if (uValue < 10000)
613 return 4;
614 if (uValue < 100000)
615 return 5;
616 if (uValue < 1000000)
617 return 6;
618 if (uValue < 10000000)
619 return 7;
620 if (uValue < 100000000)
621 return 8;
622 if (uValue < 1000000000)
623 return 9;
624 return 10;
625}
626
627
628/**
629 * Formats the given group ID according to the specified options.
630 *
631 * @returns pszDst
632 * @param pOpts The options and state.
633 * @param gid The GID to format.
634 * @param pszOwner The owner returned by the FS.
635 * @param pszDst The output buffer.
636 * @param cbDst The output buffer size.
637 */
638static const char *rtCmdLsDecimalFormatGroup(PRTCMDLSOPTS pOpts, RTGID gid, const char *pszGroup, char *pszDst, size_t cbDst)
639{
640 if (!pOpts->fNumericalIds)
641 {
642 if (pszGroup)
643 {
644 RTStrCopy(pszDst, cbDst, pszGroup);
645 return pszDst;
646 }
647 if (gid == NIL_RTGID)
648 return "<Nil>";
649 }
650 RTStrFormatU64(pszDst, cbDst, gid, 10, 0, 0, 0);
651 return pszDst;
652}
653
654
655/**
656 * Formats the given user ID according to the specified options.
657 *
658 * @returns pszDst
659 * @param pOpts The options and state.
660 * @param uid The UID to format.
661 * @param pszOwner The owner returned by the FS.
662 * @param pszDst The output buffer.
663 * @param cbDst The output buffer size.
664 */
665static const char *rtCmdLsDecimalFormatOwner(PRTCMDLSOPTS pOpts, RTUID uid, const char *pszOwner, char *pszDst, size_t cbDst)
666{
667 if (!pOpts->fNumericalIds)
668 {
669 if (pszOwner)
670 {
671 RTStrCopy(pszDst, cbDst, pszOwner);
672 return pszDst;
673 }
674 if (uid == NIL_RTUID)
675 return "<Nil>";
676 }
677 RTStrFormatU64(pszDst, cbDst, uid, 10, 0, 0, 0);
678 return pszDst;
679}
680
681
682/**
683 * Formats the given timestamp according to the desired --time-style.
684 *
685 * @returns pszDst
686 * @param pOpts The options and state.
687 * @param pTimestamp The timestamp.
688 * @param pszDst The output buffer.
689 * @param cbDst The output buffer size.
690 */
691static const char *rtCmdLsFormatTimestamp(PRTCMDLSOPTS pOpts, PCRTTIMESPEC pTimestamp, char *pszDst, size_t cbDst)
692{
693 /** @todo timestamp formatting according to the given style. */
694 RT_NOREF(pOpts);
695 return RTTimeSpecToString(pTimestamp, pszDst, cbDst);
696}
697
698
699
700/**
701 * RTCMDLSFORMAT_MACHINE_READABLE: --machine-readable
702 */
703static RTEXITCODE rtCmdLsDisplayCollectionInMachineReadableFormat(PRTCMDLSOPTS pOpts, PRTCMDLSCOLLECTION pCollection,
704 char *pszTmp, size_t cbTmp)
705{
706 RT_NOREF(pOpts, pCollection, pszTmp, cbTmp);
707 RTMsgError("Machine readable format not implemented\n");
708 return RTEXITCODE_FAILURE;
709}
710
711
712/**
713 * RTCMDLSFORMAT_COMMAS: -m
714 */
715static RTEXITCODE rtCmdLsDisplayCollectionInCvsFormat(PRTCMDLSOPTS pOpts, PRTCMDLSCOLLECTION pCollection,
716 char *pszTmp, size_t cbTmp)
717{
718 RT_NOREF(pOpts, pCollection, pszTmp, cbTmp);
719 RTMsgError("Table output formats not implemented\n");
720 return RTEXITCODE_FAILURE;
721}
722
723
724/**
725 * RTCMDLSFORMAT_LONG: -l
726 */
727static RTEXITCODE rtCmdLsDisplayCollectionInLongFormat(PRTCMDLSOPTS pOpts, PRTCMDLSCOLLECTION pCollection,
728 char *pszTmp, size_t cbTmp, size_t cchAllocatedCol)
729{
730 /*
731 * Figure the width of the size, the link count, the uid, the gid, and the inode columns.
732 */
733 size_t cchSizeCol = 1;
734 size_t cchLinkCol = 1;
735 size_t cchUidCol = pOpts->fShowOwner ? 1 : 0;
736 size_t cchGidCol = pOpts->fShowGroup ? 1 : 0;
737 size_t cchINodeCol = pOpts->fShowINode ? 1 : 0;
738
739 size_t i = pCollection->cEntries;
740 while (i-- > 0)
741 {
742 PRTCMDLSENTRY pEntry = pCollection->papEntries[i];
743
744 rtCmdLsFormatSize(pOpts, pEntry->Info.cbObject, pszTmp, cbTmp);
745 size_t cchTmp = strlen(pszTmp);
746 if (cchTmp > cchSizeCol)
747 cchSizeCol = cchTmp;
748
749 cchTmp = rtCmdLsDecimalFormatLengthU32(pEntry->Info.Attr.u.Unix.cHardlinks) + 1;
750 if (cchTmp > cchLinkCol)
751 cchLinkCol = cchTmp;
752
753 if (pOpts->fShowOwner)
754 {
755 rtCmdLsDecimalFormatOwner(pOpts, pEntry->Info.Attr.u.Unix.uid, pEntry->pszOwner, pszTmp, cbTmp);
756 cchTmp = strlen(pszTmp);
757 if (cchTmp > cchUidCol)
758 cchUidCol = cchTmp;
759 }
760
761 if (pOpts->fShowGroup)
762 {
763 rtCmdLsDecimalFormatGroup(pOpts, pEntry->Info.Attr.u.Unix.gid, pEntry->pszGroup, pszTmp, cbTmp);
764 cchTmp = strlen(pszTmp);
765 if (cchTmp > cchGidCol)
766 cchGidCol = cchTmp;
767 }
768
769 if (pOpts->fShowINode)
770 {
771 cchTmp = RTStrFormatU64(pszTmp, cchTmp, pEntry->Info.Attr.u.Unix.INodeId, 10, 0, 0, 0);
772 if (cchTmp > cchINodeCol)
773 cchINodeCol = cchTmp;
774 }
775 }
776
777 /*
778 * Determin time member offset.
779 */
780 size_t offTime;
781 switch (pOpts->enmTime)
782 {
783 default: AssertFailed(); RT_FALL_THRU();
784 case RTCMDLSTIME_MTIME: offTime = RT_OFFSETOF(RTCMDLSENTRY, Info.ModificationTime); break;
785 case RTCMDLSTIME_BTIME: offTime = RT_OFFSETOF(RTCMDLSENTRY, Info.BirthTime); break;
786 case RTCMDLSTIME_CTIME: offTime = RT_OFFSETOF(RTCMDLSENTRY, Info.ChangeTime); break;
787 case RTCMDLSTIME_ATIME: offTime = RT_OFFSETOF(RTCMDLSENTRY, Info.AccessTime); break;
788 }
789
790 /*
791 * Display the entries.
792 */
793 for (i = 0; i < pCollection->cEntries; i++)
794 {
795 PRTCMDLSENTRY pEntry = pCollection->papEntries[i];
796
797 if (cchINodeCol)
798 RTPrintf("%*RU64 ", cchINodeCol, pEntry->Info.Attr.u.Unix.INodeId);
799 if (cchAllocatedCol)
800 RTPrintf("%*s ", cchAllocatedCol, rtCmdLsFormatBlocks(pOpts, pEntry->Info.cbAllocated, pszTmp, cbTmp));
801
802 RTFMODE fMode = pEntry->Info.Attr.fMode;
803 switch (fMode & RTFS_TYPE_MASK)
804 {
805 case RTFS_TYPE_FIFO: RTPrintf("f"); break;
806 case RTFS_TYPE_DEV_CHAR: RTPrintf("c"); break;
807 case RTFS_TYPE_DIRECTORY: RTPrintf("d"); break;
808 case RTFS_TYPE_DEV_BLOCK: RTPrintf("b"); break;
809 case RTFS_TYPE_FILE: RTPrintf("-"); break;
810 case RTFS_TYPE_SYMLINK: RTPrintf("l"); break;
811 case RTFS_TYPE_SOCKET: RTPrintf("s"); break;
812 case RTFS_TYPE_WHITEOUT: RTPrintf("w"); break;
813 default: RTPrintf("?"); AssertFailed(); break;
814 }
815 /** @todo sticy bits++ */
816 RTPrintf("%c%c%c",
817 fMode & RTFS_UNIX_IRUSR ? 'r' : '-',
818 fMode & RTFS_UNIX_IWUSR ? 'w' : '-',
819 fMode & RTFS_UNIX_IXUSR ? 'x' : '-');
820 RTPrintf("%c%c%c",
821 fMode & RTFS_UNIX_IRGRP ? 'r' : '-',
822 fMode & RTFS_UNIX_IWGRP ? 'w' : '-',
823 fMode & RTFS_UNIX_IXGRP ? 'x' : '-');
824 RTPrintf("%c%c%c",
825 fMode & RTFS_UNIX_IROTH ? 'r' : '-',
826 fMode & RTFS_UNIX_IWOTH ? 'w' : '-',
827 fMode & RTFS_UNIX_IXOTH ? 'x' : '-');
828 if (1)
829 {
830 RTPrintf(" %c%c%c%c%c%c%c%c%c%c%c%c%c%c",
831 fMode & RTFS_DOS_READONLY ? 'R' : '-',
832 fMode & RTFS_DOS_HIDDEN ? 'H' : '-',
833 fMode & RTFS_DOS_SYSTEM ? 'S' : '-',
834 fMode & RTFS_DOS_DIRECTORY ? 'D' : '-',
835 fMode & RTFS_DOS_ARCHIVED ? 'A' : '-',
836 fMode & RTFS_DOS_NT_DEVICE ? 'd' : '-',
837 fMode & RTFS_DOS_NT_NORMAL ? 'N' : '-',
838 fMode & RTFS_DOS_NT_TEMPORARY ? 'T' : '-',
839 fMode & RTFS_DOS_NT_SPARSE_FILE ? 'P' : '-',
840 fMode & RTFS_DOS_NT_REPARSE_POINT ? 'J' : '-',
841 fMode & RTFS_DOS_NT_COMPRESSED ? 'C' : '-',
842 fMode & RTFS_DOS_NT_OFFLINE ? 'O' : '-',
843 fMode & RTFS_DOS_NT_NOT_CONTENT_INDEXED ? 'I' : '-',
844 fMode & RTFS_DOS_NT_ENCRYPTED ? 'E' : '-');
845 }
846 RTPrintf(" %*u", cchLinkCol, pEntry->Info.Attr.u.Unix.cHardlinks);
847 if (cchUidCol)
848 RTPrintf(" %*s", cchUidCol,
849 rtCmdLsDecimalFormatOwner(pOpts, pEntry->Info.Attr.u.Unix.uid, pEntry->pszOwner, pszTmp, cbTmp));
850 if (cchGidCol)
851 RTPrintf(" %*s", cchGidCol,
852 rtCmdLsDecimalFormatGroup(pOpts, pEntry->Info.Attr.u.Unix.gid, pEntry->pszGroup, pszTmp, cbTmp));
853 RTPrintf(" %*s", cchSizeCol, rtCmdLsFormatSize(pOpts, pEntry->Info.cbObject, pszTmp, cbTmp));
854
855 PCRTTIMESPEC pTime = (PCRTTIMESPEC)((uintptr_t)pEntry + offTime);
856 RTPrintf(" %s", rtCmdLsFormatTimestamp(pOpts, pTime, pszTmp, cbTmp));
857
858 RTPrintf(" %s\n", rtCmdLsFormatName(pOpts, pEntry->szName, pszTmp, cbTmp));
859 }
860
861 return RTEXITCODE_SUCCESS;
862}
863
864
865/**
866 * RTCMDLSFORMAT_SINGLE: -1
867 */
868static RTEXITCODE rtCmdLsDisplayCollectionInSingleFormat(PRTCMDLSOPTS pOpts, PRTCMDLSCOLLECTION pCollection,
869 char *pszTmp, size_t cbTmp, size_t cchAllocatedCol)
870{
871 if (cchAllocatedCol > 0)
872 for (size_t i = 0; i < pCollection->cEntries; i++)
873 {
874 PRTCMDLSENTRY pEntry = pCollection->papEntries[i];
875 RTPrintf("%*s %s\n",
876 cchAllocatedCol, rtCmdLsFormatBlocks(pOpts, pEntry->Info.cbAllocated, pszTmp, cbTmp / 4),
877 rtCmdLsFormatName(pOpts, pEntry->szName, &pszTmp[cbTmp / 4], cbTmp / 4 * 3));
878 }
879 else
880 for (size_t i = 0; i < pCollection->cEntries; i++)
881 {
882 PRTCMDLSENTRY pEntry = pCollection->papEntries[i];
883 RTPrintf("%s\n", rtCmdLsFormatName(pOpts, pEntry->szName, pszTmp, cbTmp));
884 }
885
886 return RTEXITCODE_SUCCESS;
887}
888
889
890/**
891 * RTCMDLSFORMAT_COLS_VERTICAL: default, -C; RTCMDLSFORMAT_COLS_HORIZONTAL: -x
892 */
893static RTEXITCODE rtCmdLsDisplayCollectionInTableFormat(PRTCMDLSOPTS pOpts, PRTCMDLSCOLLECTION pCollection,
894 char *pszTmp, size_t cbTmp, size_t cchAllocatedCol)
895{
896 RT_NOREF(pOpts, pCollection, pszTmp, cbTmp, cchAllocatedCol);
897 RTMsgError("Table output formats not implemented\n");
898 return RTEXITCODE_FAILURE;
899}
900
901
902/**
903 * Does the actual displaying of the entry collections.
904 *
905 * @returns Program exit code.
906 * @param pOpts The options and state.
907 */
908static RTEXITCODE rtCmdLsDisplayCollections(PRTCMDLSOPTS pOpts)
909{
910 rtCmdLsSortCollections(pOpts);
911
912 bool const fNeedCollectionName = pOpts->cCollections > 2
913 || ( pOpts->cCollections == 2
914 && pOpts->papCollections[0]->cEntries > 0);
915 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
916 for (size_t iCollection = 0; iCollection < pOpts->cCollections; iCollection++)
917 {
918 PRTCMDLSCOLLECTION pCollection = pOpts->papCollections[iCollection];
919 char szTmp[RTPATH_MAX*2];
920
921 /* The header. */
922 if (iCollection != 0)
923 {
924 if ( iCollection > 1
925 || pOpts->papCollections[0]->cEntries > 0)
926 RTPrintf("\n");
927 if (fNeedCollectionName)
928 RTPrintf("%s:\n", rtCmdLsFormatName(pOpts, pCollection->szName, szTmp, sizeof(szTmp)));
929 RTPrintf("total %s\n", rtCmdLsFormatBlocks(pOpts, pCollection->cbTotalAllocated, szTmp, sizeof(szTmp)));
930 }
931
932 /* Format the entries. */
933 RTEXITCODE rcExit2;
934 if (pOpts->enmFormat == RTCMDLSFORMAT_MACHINE_READABLE)
935 rcExit2 = rtCmdLsDisplayCollectionInMachineReadableFormat(pOpts, pCollection, szTmp, sizeof(szTmp));
936 else if (pOpts->enmFormat == RTCMDLSFORMAT_COMMAS)
937 rcExit2 = rtCmdLsDisplayCollectionInCvsFormat(pOpts, pCollection, szTmp, sizeof(szTmp));
938 else
939 {
940 /* If the allocated size is requested, calculate the column width. */
941 size_t cchAllocatedCol = 0;
942 if (pOpts->fShowAllocatedSize)
943 {
944 size_t i = pCollection->cEntries;
945 while (i-- > 0)
946 {
947 rtCmdLsFormatBlocks(pOpts, pCollection->papEntries[i]->Info.cbAllocated, szTmp, sizeof(szTmp));
948 size_t cchTmp = strlen(szTmp);
949 if (cchTmp > cchAllocatedCol)
950 cchAllocatedCol = cchTmp;
951 }
952 }
953
954 /* Do the individual formatting. */
955 if (pOpts->enmFormat == RTCMDLSFORMAT_LONG)
956 rcExit2 = rtCmdLsDisplayCollectionInLongFormat(pOpts, pCollection, szTmp, sizeof(szTmp), cchAllocatedCol);
957 else if (pOpts->enmFormat == RTCMDLSFORMAT_SINGLE)
958 rcExit2 = rtCmdLsDisplayCollectionInSingleFormat(pOpts, pCollection, szTmp, sizeof(szTmp), cchAllocatedCol);
959 else
960 rcExit2 = rtCmdLsDisplayCollectionInTableFormat(pOpts, pCollection, szTmp, sizeof(szTmp), cchAllocatedCol);
961 }
962 if (rcExit2 != RTEXITCODE_SUCCESS)
963 rcExit = rcExit2;
964 }
965 return rcExit;
966}
967
968
969/**
970 * Frees all collections and their entries.
971 * @param pOpts The options and state.
972 */
973static void rtCmdLsFreeCollections(PRTCMDLSOPTS pOpts)
974{
975 size_t i = pOpts->cCollections;
976 while (i-- > 0)
977 {
978 PRTCMDLSCOLLECTION pCollection = pOpts->papCollections[i];
979 PPRTCMDLSENTRY papEntries = pCollection->papEntries;
980 size_t j = pCollection->cEntries;
981 while (j-- > 0)
982 {
983 RTMemFree(papEntries[j]);
984 papEntries[j] = NULL;
985 }
986 RTMemFree(papEntries);
987 pCollection->papEntries = NULL;
988 pCollection->cEntries = 0;
989 pCollection->cEntriesAllocated = 0;
990 RTMemFree(pCollection);
991 pOpts->papCollections[i] = NULL;
992 }
993
994 RTMemFree(pOpts->papCollections);
995 pOpts->papCollections = NULL;
996 pOpts->cCollections = 0;
997 pOpts->cCollectionsAllocated = 0;
998}
999
1000
1001/**
1002 * Allocates a new collection.
1003 *
1004 * @returns Pointer to the collection.
1005 * @param pOpts The options and state.
1006 * @param pszName The collection name. Empty for special first
1007 * collection.
1008 */
1009static PRTCMDLSCOLLECTION rtCmdLsNewCollection(PRTCMDLSOPTS pOpts, const char *pszName)
1010{
1011 /* Grow the pointer table? */
1012 if (pOpts->cCollections >= pOpts->cCollectionsAllocated)
1013 {
1014 size_t cNew = pOpts->cCollectionsAllocated ? pOpts->cCollectionsAllocated * 2 : 16;
1015 void *pvNew = RTMemRealloc(pOpts->papCollections, cNew * sizeof(pOpts->papCollections[0]));
1016 if (!pvNew)
1017 {
1018 RTMsgError("Out of memory! (resize collections)");
1019 return NULL;
1020 }
1021 pOpts->cCollectionsAllocated = cNew;
1022 pOpts->papCollections = (PPRTCMDLSCOLLECTION)pvNew;
1023
1024 /* If this is the first time and pszName isn't empty, add the zero'th
1025 entry for the command line stuff (hardcoded first collection). */
1026 if ( pOpts->cCollections == 0
1027 && *pszName)
1028 {
1029 PRTCMDLSCOLLECTION pCollection = (PRTCMDLSCOLLECTION)RTMemAllocZ(RT_OFFSETOF(RTCMDLSCOLLECTION, szName[1]));
1030 if (!pCollection)
1031 {
1032 RTMsgError("Out of memory! (collection)");
1033 return NULL;
1034 }
1035 pOpts->papCollections[0] = pCollection;
1036 pOpts->cCollections = 1;
1037 }
1038 }
1039
1040 /* Add new collection. */
1041 size_t cbName = strlen(pszName) + 1;
1042 PRTCMDLSCOLLECTION pCollection = (PRTCMDLSCOLLECTION)RTMemAllocZ(RT_OFFSETOF(RTCMDLSCOLLECTION, szName[cbName]));
1043 if (pCollection)
1044 {
1045 memcpy(pCollection->szName, pszName, cbName);
1046 pOpts->papCollections[pOpts->cCollections++] = pCollection;
1047 }
1048 else
1049 RTMsgError("Out of memory! (collection)");
1050 return pCollection;
1051}
1052
1053
1054/**
1055 * Adds one entry to a collection.
1056 * @returns Program exit code
1057 * @param pCollection The collection.
1058 * @param pszEntry The entry name.
1059 * @param pInfo The entry info.
1060 * @param pszOwner The owner name if available, otherwise NULL.
1061 * @param pszGroup The group anme if available, otherwise NULL.
1062 * @param pszTarget The symbolic link target if applicable and
1063 * available, otherwise NULL.
1064 */
1065static RTEXITCODE rtCmdLsAddOne(PRTCMDLSCOLLECTION pCollection, const char *pszEntry, PRTFSOBJINFO pInfo,
1066 const char *pszOwner, const char *pszGroup, const char *pszTarget)
1067{
1068
1069 /* Make sure there is space in the collection for the new entry. */
1070 if (pCollection->cEntries >= pCollection->cEntriesAllocated)
1071 {
1072 size_t cNew = pCollection->cEntriesAllocated ? pCollection->cEntriesAllocated * 2 : 16;
1073 void *pvNew = RTMemRealloc(pCollection->papEntries, cNew * sizeof(pCollection->papEntries[0]));
1074 if (!pvNew)
1075 return RTMsgErrorExitFailure("Out of memory! (resize entries)");
1076 pCollection->papEntries = (PPRTCMDLSENTRY)pvNew;
1077 pCollection->cEntriesAllocated = cNew;
1078 }
1079
1080 /* Create and insert a new entry. */
1081 size_t const cchEntry = strlen(pszEntry);
1082 size_t const cbOwner = pszOwner ? strlen(pszOwner) + 1 : 0;
1083 size_t const cbGroup = pszGroup ? strlen(pszGroup) + 1 : 0;
1084 size_t const cbTarget = pszTarget ? strlen(pszTarget) + 1 : 0;
1085 size_t const cbEntry = RT_OFFSETOF(RTCMDLSENTRY, szName[cchEntry + 1 + cbOwner + cbGroup + cbTarget]);
1086 PRTCMDLSENTRY pEntry = (PRTCMDLSENTRY)RTMemAlloc(cbEntry);
1087 if (pEntry)
1088 {
1089 pEntry->Info = *pInfo;
1090 pEntry->pszTarget = NULL; /** @todo symbolic links. */
1091 pEntry->pszOwner = NULL;
1092 pEntry->pszGroup = NULL;
1093 pEntry->cchName = cchEntry;
1094 memcpy(pEntry->szName, pszEntry, cchEntry);
1095 pEntry->szName[cchEntry] = '\0';
1096
1097 char *psz = &pEntry->szName[cchEntry + 1];
1098 if (pszTarget)
1099 {
1100 pEntry->pszTarget = psz;
1101 memcpy(psz, pszTarget, cbTarget);
1102 psz += cbTarget;
1103 }
1104 if (pszOwner)
1105 {
1106 pEntry->pszOwner = psz;
1107 memcpy(psz, pszOwner, cbOwner);
1108 psz += cbOwner;
1109 }
1110 if (pszGroup)
1111 {
1112 pEntry->pszGroup = psz;
1113 memcpy(psz, pszGroup, cbGroup);
1114 }
1115
1116 pCollection->papEntries[pCollection->cEntries++] = pEntry;
1117 pCollection->cbTotalAllocated += pEntry->Info.cbAllocated;
1118 pCollection->cbTotalFiles += pEntry->Info.cbObject;
1119 return RTEXITCODE_SUCCESS;
1120 }
1121 return RTMsgErrorExitFailure("Out of memory! (entry)");
1122}
1123
1124
1125/**
1126 * Checks if the entry is to be filtered out.
1127 *
1128 * @returns true if filtered out, false if included.
1129 * @param pOpts The options and state.
1130 * @param pszEntry The entry name.
1131 * @param pInfo The entry info.
1132 */
1133static bool rtCmdLsIsFilteredOut(PRTCMDLSOPTS pOpts, const char *pszEntry, PCRTFSOBJINFO pInfo)
1134{
1135 /*
1136 * Should we filter out this entry?
1137 */
1138 if ( !pOpts->fShowHidden
1139 && (pInfo->Attr.fMode & RTFS_DOS_HIDDEN))
1140 return true;
1141
1142 size_t const cchEntry = strlen(pszEntry);
1143 if ( !pOpts->fShowDotAndDotDot
1144 && cchEntry <= 2
1145 && pszEntry[0] == '.'
1146 && ( cchEntry == 1
1147 || pszEntry[1] == '.' ))
1148 return true;
1149
1150 if ( !pOpts->fShowBackups
1151 && pszEntry[cchEntry - 1] == '~')
1152 return true;
1153 return false;
1154}
1155
1156
1157/**
1158 * Processes a directory, recursing into subdirectories if desired.
1159 *
1160 * @returns Program exit code.
1161 * @param pOpts The options.
1162 * @param hVfsDir The directory.
1163 * @param pszPath Path buffer, RTPATH_MAX in size.
1164 * @param cchPath The length of the current path.
1165 * @param pInfo The parent information.
1166 */
1167static RTEXITCODE rtCmdLsProcessDirectory(PRTCMDLSOPTS pOpts, RTVFSDIR hVfsDir, char *pszPath, size_t cchPath, PCRTFSOBJINFO pInfo)
1168{
1169 /*
1170 * Create a new collection for this directory.
1171 */
1172 RT_NOREF(pInfo);
1173 PRTCMDLSCOLLECTION pCollection = rtCmdLsNewCollection(pOpts, pszPath);
1174 if (!pCollection)
1175 return RTEXITCODE_FAILURE;
1176
1177 /*
1178 * Process the directory entries.
1179 */
1180 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
1181 size_t cbDirEntryAlloced = sizeof(RTDIRENTRYEX);
1182 PRTDIRENTRYEX pDirEntry = (PRTDIRENTRYEX)RTMemTmpAlloc(cbDirEntryAlloced);
1183 if (!pDirEntry)
1184 return RTMsgErrorExitFailure("Out of memory! (direntry buffer)");
1185
1186 for (;;)
1187 {
1188 /*
1189 * Read the next entry.
1190 */
1191 size_t cbDirEntry = cbDirEntryAlloced;
1192 int rc = RTVfsDirReadEx(hVfsDir, pDirEntry, &cbDirEntry, RTFSOBJATTRADD_UNIX);
1193 if (RT_FAILURE(rc))
1194 {
1195 if (rc == VERR_BUFFER_OVERFLOW)
1196 {
1197 RTMemTmpFree(pDirEntry);
1198 cbDirEntryAlloced = RT_ALIGN_Z(RT_MIN(cbDirEntry, cbDirEntryAlloced) + 64, 64);
1199 pDirEntry = (PRTDIRENTRYEX)RTMemTmpAlloc(cbDirEntryAlloced);
1200 if (pDirEntry)
1201 continue;
1202 rcExit = RTMsgErrorExitFailure("Out of memory (direntry buffer)");
1203 }
1204 else if (rc != VERR_NO_MORE_FILES)
1205 rcExit = RTMsgErrorExitFailure("RTVfsDirReadEx failed: %Rrc\n", rc);
1206 break;
1207 }
1208
1209 /*
1210 * Process the entry.
1211 */
1212 if (rtCmdLsIsFilteredOut(pOpts, pDirEntry->szName, &pDirEntry->Info))
1213 continue;
1214
1215
1216 const char *pszOwner = NULL;
1217 RTFSOBJINFO OwnerInfo;
1218 if (pDirEntry->Info.Attr.u.Unix.uid != NIL_RTUID && pOpts->fShowOwner)
1219 {
1220 rc = RTVfsDirQueryPathInfo(hVfsDir, pDirEntry->szName, &OwnerInfo, RTFSOBJATTRADD_UNIX_OWNER, RTPATH_F_ON_LINK);
1221 if (RT_SUCCESS(rc) && OwnerInfo.Attr.u.UnixOwner.szName[0])
1222 pszOwner = &OwnerInfo.Attr.u.UnixOwner.szName[0];
1223 }
1224
1225 const char *pszGroup = NULL;
1226 RTFSOBJINFO GroupInfo;
1227 if (pDirEntry->Info.Attr.u.Unix.gid != NIL_RTGID && pOpts->fShowGroup)
1228 {
1229 rc = RTVfsDirQueryPathInfo(hVfsDir, pDirEntry->szName, &GroupInfo, RTFSOBJATTRADD_UNIX_GROUP, RTPATH_F_ON_LINK);
1230 if (RT_SUCCESS(rc) && GroupInfo.Attr.u.UnixGroup.szName[0])
1231 pszGroup = &GroupInfo.Attr.u.UnixGroup.szName[0];
1232 }
1233
1234 RTEXITCODE rcExit2 = rtCmdLsAddOne(pCollection, pDirEntry->szName, &pDirEntry->Info, pszOwner, pszGroup, NULL);
1235 if (rcExit2 != RTEXITCODE_SUCCESS)
1236 rcExit = rcExit2;
1237 }
1238
1239 RTMemTmpFree(pDirEntry);
1240
1241 /*
1242 * Recurse into subdirectories if requested.
1243 */
1244 if (pOpts->fRecursive)
1245 {
1246 for (uint32_t i = 0; i < pCollection->cEntries; i++)
1247 {
1248 PRTCMDLSENTRY pEntry = pCollection->papEntries[i];
1249 if (RTFS_IS_SYMLINK(pEntry->Info.Attr.fMode))
1250 {
1251 if (!pOpts->fFollowSymlinksInDirs)
1252 continue;
1253 /** @todo implement following symbolic links in the tree. */
1254 continue;
1255 }
1256 else if ( !RTFS_IS_DIRECTORY(pEntry->Info.Attr.fMode)
1257 || ( pEntry->szName[0] == '.'
1258 && ( pEntry->szName[1] == '\0'
1259 || ( pEntry->szName[1] == '.'
1260 && pEntry->szName[2] == '\0'))) )
1261 continue;
1262
1263 /* Open subdirectory and process it. */
1264 RTVFSDIR hSubDir;
1265 int rc = RTVfsDirOpenDir(hVfsDir, pEntry->szName, 0 /*fFlags*/, &hSubDir);
1266 if (RT_SUCCESS(rc))
1267 {
1268 if (cchPath + 1 + pEntry->cchName + 1 < RTPATH_MAX)
1269 {
1270 pszPath[cchPath] = RTPATH_SLASH;
1271 memcpy(&pszPath[cchPath + 1], pEntry->szName, pEntry->cchName + 1);
1272 RTEXITCODE rcExit2 = rtCmdLsProcessDirectory(pOpts, hSubDir, pszPath,
1273 cchPath + 1 + pEntry->cchName, &pEntry->Info);
1274 if (rcExit2 != RTEXITCODE_SUCCESS)
1275 rcExit = rcExit2;
1276 pszPath[cchPath] = '\0';
1277 }
1278 else
1279 rcExit = RTMsgErrorExitFailure("Too deep recursion: %s%c%s", pszPath, RTPATH_SLASH, pEntry->szName);
1280 RTVfsDirRelease(hSubDir);
1281 }
1282 else
1283 rcExit = RTMsgErrorExitFailure("RTVfsDirOpenDir failed on %s in %s: %Rrc\n", pEntry->szName, pszPath, rc);
1284 }
1285 }
1286 return rcExit;
1287}
1288
1289
1290/**
1291 * Processes one argument.
1292 *
1293 * @returns Program exit code.
1294 * @param pOpts The options.
1295 * @param pszArg The argument.
1296 */
1297static RTEXITCODE rtCmdLsProcessArgument(PRTCMDLSOPTS pOpts, const char *pszArg)
1298{
1299 /*
1300 * Query info about the object 'pszArg' indicates.
1301 */
1302 RTERRINFOSTATIC ErrInfo;
1303 uint32_t offError;
1304 RTFSOBJINFO Info;
1305 uint32_t fPath = pOpts->fFollowSymlinkToAnyArgs ? RTPATH_F_FOLLOW_LINK : RTPATH_F_ON_LINK;
1306 int rc = RTVfsChainQueryInfo(pszArg, &Info, RTFSOBJATTRADD_UNIX, fPath, &offError, RTErrInfoInitStatic(&ErrInfo));
1307 if (RT_FAILURE(rc))
1308 return RTVfsChainMsgErrorExitFailure("RTVfsChainQueryInfo", pszArg, rc, offError, &ErrInfo.Core);
1309
1310 /* Symbolic links requires special handling of course. */
1311 if (RTFS_IS_SYMLINK(Info.Attr.fMode))
1312 {
1313 if (pOpts->fFollowSymlinkToDirArgs)
1314 {
1315 RTFSOBJINFO Info2;
1316 rc = RTVfsChainQueryInfo(pszArg, &Info2, RTFSOBJATTRADD_UNIX, RTPATH_F_FOLLOW_LINK,
1317 &offError, RTErrInfoInitStatic(&ErrInfo));
1318 if (RT_SUCCESS(rc) && !RTFS_IS_DIRECTORY(Info.Attr.fMode))
1319 Info = Info2;
1320 }
1321 }
1322
1323 /*
1324 * If it's not a directory or we've been told to process directories
1325 * without going into them, just add it to the default collection.
1326 */
1327 if ( !pOpts->fFollowDirectoryArgs
1328 || !RTFS_IS_DIRECTORY(Info.Attr.fMode))
1329 {
1330 if ( pOpts->cCollections > 0
1331 || rtCmdLsNewCollection(pOpts, "") != NULL)
1332 {
1333 const char *pszOwner = NULL;
1334 RTFSOBJINFO OwnerInfo;
1335 if (Info.Attr.u.Unix.uid != NIL_RTUID && pOpts->fShowOwner)
1336 {
1337 rc = RTVfsChainQueryInfo(pszArg, &OwnerInfo, RTFSOBJATTRADD_UNIX_OWNER, fPath, NULL, NULL);
1338 if (RT_SUCCESS(rc) && OwnerInfo.Attr.u.UnixOwner.szName[0])
1339 pszOwner = &OwnerInfo.Attr.u.UnixOwner.szName[0];
1340 }
1341
1342 const char *pszGroup = NULL;
1343 RTFSOBJINFO GroupInfo;
1344 if (Info.Attr.u.Unix.gid != NIL_RTGID && pOpts->fShowGroup)
1345 {
1346 rc = RTVfsChainQueryInfo(pszArg, &GroupInfo, RTFSOBJATTRADD_UNIX_GROUP, fPath, NULL, NULL);
1347 if (RT_SUCCESS(rc) && GroupInfo.Attr.u.UnixGroup.szName[0])
1348 pszGroup = &GroupInfo.Attr.u.UnixGroup.szName[0];
1349 }
1350
1351 return rtCmdLsAddOne(pOpts->papCollections[0], pszArg, &Info, pszOwner, pszGroup, NULL);
1352 }
1353 return RTEXITCODE_FAILURE;
1354 }
1355
1356 /*
1357 * Open the directory.
1358 */
1359 RTVFSDIR hVfsDir;
1360 rc = RTVfsChainOpenDir(pszArg, 0 /*fFlags*/, &hVfsDir, &offError, RTErrInfoInitStatic(&ErrInfo));
1361 if (RT_FAILURE(rc))
1362 return RTVfsChainMsgErrorExitFailure("RTVfsChainOpenDir", pszArg, rc, offError, &ErrInfo.Core);
1363
1364 RTEXITCODE rcExit;
1365 char szPath[RTPATH_MAX];
1366 size_t cchPath = strlen(pszArg);
1367 if (cchPath < sizeof(szPath))
1368 {
1369 memcpy(szPath, pszArg, cchPath + 1);
1370 rcExit = rtCmdLsProcessDirectory(pOpts, hVfsDir, szPath, cchPath, &Info);
1371 }
1372 else
1373 rcExit = RTMsgErrorExitFailure("Too long argument: %s", pszArg);
1374 RTVfsDirRelease(hVfsDir);
1375 return rcExit;
1376}
1377
1378
1379/**
1380 * A /bin/ls clone.
1381 *
1382 * @returns Program exit code.
1383 *
1384 * @param cArgs The number of arguments.
1385 * @param papszArgs The argument vector. (Note that this may be
1386 * reordered, so the memory must be writable.)
1387 */
1388RTR3DECL(RTEXITCODE) RTFsCmdLs(unsigned cArgs, char **papszArgs)
1389{
1390
1391 /*
1392 * Parse the command line.
1393 */
1394#define OPT_AUTHOR 1000
1395#define OPT_BLOCK_SIZE 1001
1396#define OPT_COLOR 1002
1397#define OPT_FILE_TYPE 1003
1398#define OPT_FORMAT 1004
1399#define OPT_FULL_TIME 1005
1400#define OPT_GROUP_DIRECTORIES_FIRST 1006
1401#define OPT_SI 1007
1402#define OPT_DEREFERENCE_COMMAND_LINE_SYMLINK_TO_DIR 1008
1403#define OPT_HIDE 1009
1404#define OPT_INDICATOR_STYLE 1010
1405#define OPT_MACHINE_READABLE 1011
1406#define OPT_SHOW_CONTROL_CHARS 1012
1407#define OPT_QUOTING_STYLE 1013
1408#define OPT_SORT 1014
1409#define OPT_TIME 1015
1410#define OPT_TIME_STYLE 1016
1411 static const RTGETOPTDEF s_aOptions[] =
1412 {
1413 { "--all", 'a', RTGETOPT_REQ_NOTHING },
1414 { "--almost-all", 'A', RTGETOPT_REQ_NOTHING },
1415 //{ "--author", OPT_AUTHOR, RTGETOPT_REQ_NOTHING },
1416 { "--escape", 'b', RTGETOPT_REQ_NOTHING },
1417 { "--block-size", OPT_BLOCK_SIZE, RTGETOPT_REQ_UINT32 },
1418 { "--ctime", 'c', RTGETOPT_REQ_NOTHING },
1419 //{ "--columns", 'C', RTGETOPT_REQ_NOTHING },
1420 //{ "--color", OPT_COLOR, RTGETOPT_OPT_STRING },
1421 { "--directory", 'd', RTGETOPT_REQ_NOTHING },
1422 //{ "--dired", 'D', RTGETOPT_REQ_NOTHING },
1423 { "--dash-f", 'f', RTGETOPT_REQ_NOTHING },
1424 //{ "--classify", 'F', RTGETOPT_REQ_NOTHING },
1425 //{ "--file-type", OPT_FILE_TYPE, RTGETOPT_REQ_NOTHING },
1426 { "--format", OPT_FORMAT, RTGETOPT_REQ_STRING },
1427 { "--full-time", OPT_FULL_TIME, RTGETOPT_REQ_NOTHING },
1428 { "--dash-g", 'g', RTGETOPT_REQ_NOTHING },
1429 { "--group-directories-first", OPT_GROUP_DIRECTORIES_FIRST, RTGETOPT_REQ_NOTHING },
1430 { "--no-group", 'G', RTGETOPT_REQ_NOTHING },
1431 { "--human-readable", 'h', RTGETOPT_REQ_NOTHING },
1432 { "--si", OPT_SI, RTGETOPT_REQ_NOTHING },
1433 { "--dereference-command-line", 'H', RTGETOPT_REQ_NOTHING },
1434 { "--dereference-command-line-symlink-to-dir", OPT_DEREFERENCE_COMMAND_LINE_SYMLINK_TO_DIR, RTGETOPT_REQ_NOTHING },
1435 //{ "--hide" OPT_HIDE, RTGETOPT_REQ_STRING },
1436 //{ "--indicator-style" OPT_INDICATOR_STYLE, RTGETOPT_REQ_STRING },
1437 { "--inode", 'i', RTGETOPT_REQ_NOTHING },
1438 { "--block-size-1kib", 'k', RTGETOPT_REQ_NOTHING },
1439 { "--long", 'l', RTGETOPT_REQ_NOTHING },
1440 { "--dereference", 'L', RTGETOPT_REQ_NOTHING },
1441 { "--format-commas", 'm', RTGETOPT_REQ_NOTHING },
1442 { "--machinereadable", OPT_MACHINE_READABLE, RTGETOPT_REQ_NOTHING },
1443 { "--machine-readable", OPT_MACHINE_READABLE, RTGETOPT_REQ_NOTHING },
1444 { "--numeric-uid-gid", 'n', RTGETOPT_REQ_NOTHING },
1445 { "--literal", 'N', RTGETOPT_REQ_NOTHING },
1446 { "--long-without-group-info", 'o', RTGETOPT_REQ_NOTHING },
1447 //{ "--indicator-style", 'p', RTGETOPT_REQ_STRING },
1448 { "--hide-control-chars", 'q', RTGETOPT_REQ_NOTHING },
1449 { "--show-control-chars", OPT_SHOW_CONTROL_CHARS, RTGETOPT_REQ_NOTHING },
1450 //{ "--quote-name", 'Q', RTGETOPT_REQ_NOTHING },
1451 //{ "--quoting-style", OPT_QUOTING_STYLE, RTGETOPT_REQ_STRING },
1452 { "--reverse", 'r', RTGETOPT_REQ_NOTHING },
1453 { "--recursive", 'R', RTGETOPT_REQ_NOTHING },
1454 { "--size", 's', RTGETOPT_REQ_NOTHING },
1455 { "--sort-by-size", 'S', RTGETOPT_REQ_NOTHING },
1456 { "--sort", OPT_SORT, RTGETOPT_REQ_STRING },
1457 { "--time", OPT_TIME, RTGETOPT_REQ_STRING },
1458 { "--time-style", OPT_TIME_STYLE, RTGETOPT_REQ_STRING },
1459 { "--sort-by-time", 't', RTGETOPT_REQ_NOTHING },
1460 { "--tabsize", 'T', RTGETOPT_REQ_UINT8 },
1461 { "--atime", 'u', RTGETOPT_REQ_NOTHING },
1462 { "--unsorted", 'U', RTGETOPT_REQ_NOTHING },
1463 { "--version-sort", 'v', RTGETOPT_REQ_NOTHING },
1464 { "--width", 'w', RTGETOPT_REQ_UINT32 },
1465 { "--list-by-line", 'x', RTGETOPT_REQ_NOTHING },
1466 { "--sort-by-extension", 'X', RTGETOPT_REQ_NOTHING },
1467 { "--one-file-per-line", '1', RTGETOPT_REQ_NOTHING },
1468 { "--help", '?', RTGETOPT_REQ_NOTHING },
1469 };
1470
1471 RTCMDLSOPTS Opts;
1472 Opts.fFollowSymlinksInDirs = false;
1473 Opts.fFollowSymlinkToAnyArgs = false;
1474 Opts.fFollowSymlinkToDirArgs = false;
1475 Opts.fFollowDirectoryArgs = true;
1476 Opts.fRecursive = false;
1477 Opts.fShowHidden = false;
1478 Opts.fShowDotAndDotDot = false;
1479 Opts.fShowBackups = true;
1480 Opts.enmSort = RTCMDLSSORT_NAME;
1481 Opts.fReverseSort = false;
1482 Opts.fGroupDirectoriesFirst = false;
1483 Opts.enmFormat = RTCMDLSFORMAT_COLS_VERTICAL;
1484 Opts.fEscapeNonGraphicChars = false;
1485 Opts.fEscapeControlChars = true;
1486 Opts.fHideControlChars = false;
1487 Opts.fHumanReadableSizes = false; /**< -h */
1488 Opts.fSiUnits = false;
1489 Opts.cbBlock = 0;
1490 Opts.fShowOwner = true;
1491 Opts.fShowGroup = true;
1492 Opts.fNumericalIds = false;
1493 Opts.fShowINode = false;
1494 Opts.fShowAllocatedSize = false;
1495 Opts.cchTab = 8;
1496 Opts.cchWidth = 80;
1497 Opts.enmColor = RTCMDLSCOLOR_NONE;
1498 Opts.enmTime = RTCMDLSTIME_MTIME;
1499 Opts.enmTimeStyle = RTCMDLSTIMESTYLE_LOCALE;
1500 Opts.pszTimeCustom = NULL;
1501
1502 Opts.cCollections = 0;
1503 Opts.cCollectionsAllocated = 0;
1504 Opts.papCollections = NULL;
1505
1506
1507 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
1508 unsigned cProcessed = 0;
1509 RTGETOPTSTATE GetState;
1510 int rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1,
1511 RTGETOPTINIT_FLAGS_OPTS_FIRST);
1512 if (RT_FAILURE(rc))
1513 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "RTGetOptInit: %Rrc", rc);
1514
1515 for (;;)
1516 {
1517 RTGETOPTUNION ValueUnion;
1518 int chOpt = RTGetOpt(&GetState, &ValueUnion);
1519 switch (chOpt)
1520 {
1521 case 0:
1522 /* When reaching the end of arguments without having processed any
1523 files/dirs/whatever yet, we do the current directory. */
1524 if (cProcessed > 0)
1525 {
1526 RTEXITCODE rcExit2 = rtCmdLsDisplayCollections(&Opts);
1527 if (rcExit2 != RTEXITCODE_SUCCESS)
1528 rcExit = rcExit2;
1529 rtCmdLsFreeCollections(&Opts);
1530 return rcExit;
1531 }
1532 ValueUnion.psz = ".";
1533 RT_FALL_THRU();
1534 case VINF_GETOPT_NOT_OPTION:
1535 {
1536 RTEXITCODE rcExit2 = rtCmdLsProcessArgument(&Opts, ValueUnion.psz);
1537 if (rcExit2 != RTEXITCODE_SUCCESS)
1538 rcExit = rcExit2;
1539 cProcessed++;
1540 break;
1541 }
1542
1543 case 'a':
1544 Opts.fShowHidden = true;
1545 Opts.fShowDotAndDotDot = true;
1546 break;
1547
1548 case 'A':
1549 Opts.fShowHidden = true;
1550 Opts.fShowDotAndDotDot = false;
1551 break;
1552
1553 case 'b':
1554 Opts.fEscapeNonGraphicChars = true;
1555 break;
1556
1557 case OPT_BLOCK_SIZE:
1558 if (!ValueUnion.u32)
1559 {
1560 Assert(!Opts.papCollections);
1561 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Invalid block size: %u", ValueUnion.u32);
1562 }
1563 Opts.cbBlock = ValueUnion.u32;
1564 Opts.fHumanReadableSizes = false;
1565 Opts.fSiUnits = false;
1566 break;
1567
1568 case 'c':
1569 Opts.enmTime = RTCMDLSTIME_CTIME;
1570 break;
1571
1572 case 'C':
1573 Opts.enmFormat = RTCMDLSFORMAT_COLS_VERTICAL;
1574 break;
1575
1576 case 'd':
1577 Opts.fFollowDirectoryArgs = false;
1578 Opts.fFollowSymlinkToAnyArgs = false;
1579 Opts.fFollowSymlinkToDirArgs = false;
1580 Opts.fRecursive = false;
1581 break;
1582
1583 case 'f':
1584 Opts.fShowHidden = true;
1585 Opts.fShowDotAndDotDot = true;
1586 if (Opts.enmFormat == RTCMDLSFORMAT_LONG)
1587 Opts.enmFormat = RTCMDLSFORMAT_COLS_VERTICAL;
1588 Opts.enmColor = RTCMDLSCOLOR_NONE;
1589 Opts.enmSort = RTCMDLSSORT_NONE;
1590 break;
1591
1592 case OPT_FORMAT:
1593 if ( strcmp(ValueUnion.psz, "across") == 0
1594 || strcmp(ValueUnion.psz, "horizontal") == 0)
1595 Opts.enmFormat = RTCMDLSFORMAT_COLS_HORIZONTAL;
1596 else if (strcmp(ValueUnion.psz, "commas") == 0)
1597 Opts.enmFormat = RTCMDLSFORMAT_COMMAS;
1598 else if ( strcmp(ValueUnion.psz, "long") == 0
1599 || strcmp(ValueUnion.psz, "verbose") == 0)
1600 Opts.enmFormat = RTCMDLSFORMAT_LONG;
1601 else if (strcmp(ValueUnion.psz, "single-column") == 0)
1602 Opts.enmFormat = RTCMDLSFORMAT_SINGLE;
1603 else if (strcmp(ValueUnion.psz, "vertical") == 0)
1604 Opts.enmFormat = RTCMDLSFORMAT_COLS_VERTICAL;
1605 else if (strcmp(ValueUnion.psz, "machine-readable") == 0)
1606 Opts.enmFormat = RTCMDLSFORMAT_MACHINE_READABLE;
1607 else
1608 {
1609 Assert(!Opts.papCollections);
1610 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown format: %s", ValueUnion.psz);
1611 }
1612 break;
1613
1614 case OPT_FULL_TIME:
1615 Opts.enmFormat = RTCMDLSFORMAT_LONG;
1616 Opts.enmTimeStyle = RTCMDLSTIMESTYLE_FULL_ISO;
1617 break;
1618
1619 case 'g':
1620 Opts.enmFormat = RTCMDLSFORMAT_LONG;
1621 Opts.fShowOwner = false;
1622 break;
1623
1624 case OPT_GROUP_DIRECTORIES_FIRST:
1625 Opts.fGroupDirectoriesFirst = true;
1626 break;
1627
1628 case 'G':
1629 Opts.fShowGroup = false;
1630 break;
1631
1632 case 'h':
1633 Opts.fHumanReadableSizes = true;
1634 Opts.fSiUnits = false;
1635 break;
1636
1637 case OPT_SI:
1638 Opts.fHumanReadableSizes = true;
1639 Opts.fSiUnits = true;
1640 break;
1641
1642 case 'H':
1643 Opts.fFollowSymlinkToAnyArgs = true;
1644 Opts.fFollowSymlinkToDirArgs = true;
1645 break;
1646
1647 case OPT_DEREFERENCE_COMMAND_LINE_SYMLINK_TO_DIR:
1648 Opts.fFollowSymlinkToAnyArgs = false;
1649 Opts.fFollowSymlinkToDirArgs = true;
1650 break;
1651
1652 case 'i':
1653 Opts.fShowINode = true;
1654 break;
1655
1656 case 'k':
1657 Opts.cbBlock = _1K;
1658 Opts.fHumanReadableSizes = false;
1659 Opts.fSiUnits = false;
1660 break;
1661
1662 case 'l':
1663 Opts.enmFormat = RTCMDLSFORMAT_LONG;
1664 break;
1665
1666 case 'L':
1667 Opts.fFollowSymlinksInDirs = true;
1668 Opts.fFollowSymlinkToAnyArgs = true;
1669 Opts.fFollowSymlinkToDirArgs = true;
1670 break;
1671
1672 case 'm':
1673 Opts.enmFormat = RTCMDLSFORMAT_COMMAS;
1674 break;
1675
1676 case OPT_MACHINE_READABLE:
1677 Opts.enmFormat = RTCMDLSFORMAT_MACHINE_READABLE;
1678 break;
1679
1680 case 'n':
1681 Opts.fNumericalIds = true;
1682 break;
1683
1684 case 'N':
1685 Opts.fEscapeNonGraphicChars = false;
1686 Opts.fEscapeControlChars = false;
1687 Opts.fHideControlChars = false;
1688 break;
1689
1690 case 'o':
1691 Opts.enmFormat = RTCMDLSFORMAT_LONG;
1692 Opts.fShowGroup = false;
1693 break;
1694
1695 case 'q':
1696 Opts.fHideControlChars = true;
1697 break;
1698
1699 case OPT_SHOW_CONTROL_CHARS:
1700 Opts.fHideControlChars = true;
1701 break;
1702
1703 case 'r':
1704 Opts.fReverseSort = true;
1705 break;
1706
1707 case 'R':
1708 Opts.fRecursive = true;
1709 break;
1710
1711 case 's':
1712 Opts.fShowAllocatedSize = true;
1713 break;
1714
1715 case 'S':
1716 Opts.enmSort = RTCMDLSSORT_SIZE;
1717 break;
1718
1719 case OPT_SORT:
1720 if (strcmp(ValueUnion.psz, "none") == 0)
1721 Opts.enmSort = RTCMDLSSORT_NONE;
1722 else if (strcmp(ValueUnion.psz, "extension") == 0)
1723 Opts.enmSort = RTCMDLSSORT_EXTENSION;
1724 else if (strcmp(ValueUnion.psz, "size") == 0)
1725 Opts.enmSort = RTCMDLSSORT_SIZE;
1726 else if (strcmp(ValueUnion.psz, "time") == 0)
1727 Opts.enmSort = RTCMDLSSORT_TIME;
1728 else if (strcmp(ValueUnion.psz, "version") == 0)
1729 Opts.enmSort = RTCMDLSSORT_VERSION;
1730 else
1731 {
1732 Assert(!Opts.papCollections);
1733 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown sort by: %s", ValueUnion.psz);
1734 }
1735 break;
1736
1737 case OPT_TIME:
1738 if ( strcmp(ValueUnion.psz, "btime") == 0
1739 || strcmp(ValueUnion.psz, "birth") == 0)
1740 Opts.enmTime = RTCMDLSTIME_BTIME;
1741 else if ( strcmp(ValueUnion.psz, "ctime") == 0
1742 || strcmp(ValueUnion.psz, "status") == 0)
1743 Opts.enmTime = RTCMDLSTIME_CTIME;
1744 else if ( strcmp(ValueUnion.psz, "mtime") == 0
1745 || strcmp(ValueUnion.psz, "write") == 0
1746 || strcmp(ValueUnion.psz, "modify") == 0)
1747 Opts.enmTime = RTCMDLSTIME_MTIME;
1748 else if ( strcmp(ValueUnion.psz, "atime") == 0
1749 || strcmp(ValueUnion.psz, "access") == 0
1750 || strcmp(ValueUnion.psz, "use") == 0)
1751 Opts.enmTime = RTCMDLSTIME_ATIME;
1752 else
1753 {
1754 Assert(!Opts.papCollections);
1755 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown time attribute: %s", ValueUnion.psz);
1756 }
1757 break;
1758
1759 case OPT_TIME_STYLE:
1760 if (strcmp(ValueUnion.psz, "full-iso") == 0)
1761 Opts.enmTimeStyle = RTCMDLSTIMESTYLE_FULL_ISO;
1762 else if (strcmp(ValueUnion.psz, "long-iso") == 0)
1763 Opts.enmTimeStyle = RTCMDLSTIMESTYLE_LONG_ISO;
1764 else if (strcmp(ValueUnion.psz, "iso") == 0)
1765 Opts.enmTimeStyle = RTCMDLSTIMESTYLE_ISO;
1766 else if (strcmp(ValueUnion.psz, "locale") == 0)
1767 Opts.enmTimeStyle = RTCMDLSTIMESTYLE_LOCALE;
1768 else if (*ValueUnion.psz == '+')
1769 {
1770 Opts.enmTimeStyle = RTCMDLSTIMESTYLE_CUSTOM;
1771 Opts.pszTimeCustom = ValueUnion.psz;
1772 }
1773 else
1774 {
1775 Assert(!Opts.papCollections);
1776 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown sort by: %s", ValueUnion.psz);
1777 }
1778 break;
1779
1780 case 't':
1781 Opts.enmSort = RTCMDLSSORT_TIME;
1782 break;
1783
1784 case 'T':
1785 Opts.cchTab = ValueUnion.u8;
1786 break;
1787
1788 case 'u':
1789 Opts.enmTime = RTCMDLSTIME_ATIME;
1790 break;
1791
1792 case 'U':
1793 Opts.enmSort = RTCMDLSSORT_NONE;
1794 break;
1795
1796 case 'v':
1797 Opts.enmSort = RTCMDLSSORT_VERSION;
1798 break;
1799
1800 case 'w':
1801 Opts.cchWidth = ValueUnion.u32;
1802 break;
1803
1804 case 'x':
1805 Opts.enmFormat = RTCMDLSFORMAT_COLS_HORIZONTAL;
1806 break;
1807
1808 case 'X':
1809 Opts.enmSort = RTCMDLSSORT_EXTENSION;
1810 break;
1811
1812 case '1':
1813 Opts.enmFormat = RTCMDLSFORMAT_SINGLE;
1814 break;
1815
1816 case '?':
1817 RTPrintf("Usage: to be written\nOpts.on dump:\n");
1818 for (unsigned i = 0; i < RT_ELEMENTS(s_aOptions); i++)
1819 RTPrintf(" -%c,%s\n", s_aOptions[i].iShort, s_aOptions[i].pszLong);
1820 Assert(!Opts.papCollections);
1821 return RTEXITCODE_SUCCESS;
1822
1823 case 'V':
1824 RTPrintf("%sr%d\n", RTBldCfgVersion(), RTBldCfgRevision());
1825 Assert(!Opts.papCollections);
1826 return RTEXITCODE_SUCCESS;
1827
1828 default:
1829 Assert(!Opts.papCollections);
1830 return RTGetOptPrintError(chOpt, &ValueUnion);
1831 }
1832 }
1833}
1834
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