VirtualBox

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

Last change on this file since 84207 was 82968, checked in by vboxsync, 5 years ago

Copyright year updates by scm.

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