VirtualBox

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

Last change on this file was 98103, checked in by vboxsync, 16 months ago

Copyright year updates by scm.

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

© 2023 Oracle
ContactPrivacy policyTerms of Use