diff options
Diffstat (limited to '')
-rw-r--r-- | src/VBox/Runtime/common/fs/RTFsCmdLs.cpp | 1836 |
1 files changed, 1836 insertions, 0 deletions
diff --git a/src/VBox/Runtime/common/fs/RTFsCmdLs.cpp b/src/VBox/Runtime/common/fs/RTFsCmdLs.cpp new file mode 100644 index 00000000..1eb8e122 --- /dev/null +++ b/src/VBox/Runtime/common/fs/RTFsCmdLs.cpp @@ -0,0 +1,1836 @@ +/* $Id: RTFsCmdLs.cpp $ */ +/** @file + * IPRT - /bin/ls like utility for testing the VFS code. + */ + +/* + * Copyright (C) 2017-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL) only, as it comes in the "COPYING.CDDL" file of the + * VirtualBox OSE distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/vfs.h> + +#include <iprt/buildconfig.h> +#include <iprt/err.h> +#include <iprt/file.h> +#include <iprt/getopt.h> +#include <iprt/initterm.h> +#include <iprt/mem.h> +#include <iprt/message.h> +#include <iprt/param.h> +#include <iprt/path.h> +#include <iprt/sort.h> +#include <iprt/stream.h> +#include <iprt/string.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Display entry. + */ +typedef struct RTCMDLSENTRY +{ + /** The information about the entry. */ + RTFSOBJINFO Info; + /** Symbolic link target (allocated after the name). */ + const char *pszTarget; + /** Owner if applicable(allocated after the name). */ + const char *pszOwner; + /** Group if applicable (allocated after the name). */ + const char *pszGroup; + /** The length of szName. */ + size_t cchName; + /** The entry name. */ + char szName[RT_FLEXIBLE_ARRAY]; +} RTCMDLSENTRY; +/** Pointer to a ls display entry. */ +typedef RTCMDLSENTRY *PRTCMDLSENTRY; +/** Pointer to a ls display entry pointer. */ +typedef PRTCMDLSENTRY *PPRTCMDLSENTRY; + + +/** + * Collection of display entries. + */ +typedef struct RTCMDLSCOLLECTION +{ + /** Current size of papEntries. */ + size_t cEntries; + /** Memory allocated for papEntries. */ + size_t cEntriesAllocated; + /** Current entries pending sorting and display. */ + PPRTCMDLSENTRY papEntries; + + /** Total number of bytes allocated for the above entries. */ + uint64_t cbTotalAllocated; + /** Total number of file content bytes. */ + uint64_t cbTotalFiles; + + /** The collection name (path). */ + char szName[RT_FLEXIBLE_ARRAY]; +} RTCMDLSCOLLECTION; +/** Pointer to a display entry collection. */ +typedef RTCMDLSCOLLECTION *PRTCMDLSCOLLECTION; +/** Pointer to a display entry collection pointer. */ +typedef PRTCMDLSCOLLECTION *PPRTCMDLSCOLLECTION; + + +/** Sorting. */ +typedef enum RTCMDLSSORT +{ + RTCMDLSSORT_INVALID = 0, + RTCMDLSSORT_NONE, + RTCMDLSSORT_NAME, + RTCMDLSSORT_EXTENSION, + RTCMDLSSORT_SIZE, + RTCMDLSSORT_TIME, + RTCMDLSSORT_VERSION +} RTCMDLSSORT; + +/** Time selection. */ +typedef enum RTCMDLSTIME +{ + RTCMDLSTIME_INVALID = 0, + RTCMDLSTIME_BTIME, + RTCMDLSTIME_CTIME, + RTCMDLSTIME_MTIME, + RTCMDLSTIME_ATIME +} RTCMDLSTIME; + +/** Time display style. */ +typedef enum RTCMDLSTIMESTYLE +{ + RTCMDLSTIMESTYLE_INVALID = 0, + RTCMDLSTIMESTYLE_FULL_ISO, + RTCMDLSTIMESTYLE_LONG_ISO, + RTCMDLSTIMESTYLE_ISO, + RTCMDLSTIMESTYLE_LOCALE, + RTCMDLSTIMESTYLE_CUSTOM +} RTCMDLSTIMESTYLE; + +/** Coloring selection. */ +typedef enum RTCMDLSCOLOR +{ + RTCMDLSCOLOR_INVALID = 0, + RTCMDLSCOLOR_NONE +} RTCMDLSCOLOR; + +/** Formatting. */ +typedef enum RTCMDLSFORMAT +{ + RTCMDLSFORMAT_INVALID = 0, + RTCMDLSFORMAT_COLS_VERTICAL, /**< -C/default */ + RTCMDLSFORMAT_COLS_HORIZONTAL, /**< -x */ + RTCMDLSFORMAT_COMMAS, /**< -m */ + RTCMDLSFORMAT_SINGLE, /**< -1 */ + RTCMDLSFORMAT_LONG, /**< -l */ + RTCMDLSFORMAT_MACHINE_READABLE /**< --machine-readable */ +} RTCMDLSFORMAT; + + +/** + * LS command options and state. + */ +typedef struct RTCMDLSOPTS +{ + /** @name Traversal. + * @{ */ + bool fFollowSymlinksInDirs; /**< -L */ + bool fFollowSymlinkToAnyArgs; + bool fFollowSymlinkToDirArgs; + bool fFollowDirectoryArgs; /**< Inverse -d/--directory. */ + bool fRecursive; /**< -R */ + /** @} */ + + + /** @name Filtering. + * @{ */ + bool fShowHidden; /**< -a/--all or -A/--almost-all */ + bool fShowDotAndDotDot; /**< -a vs -A */ + bool fShowBackups; /**< Inverse -B/--ignore-backups (*~). */ + /** @} */ + + /** @name Sorting + * @{ */ + RTCMDLSSORT enmSort; /**< --sort */ + bool fReverseSort; /**< -r */ + bool fGroupDirectoriesFirst; /**< fGroupDirectoriesFirst */ + /** @} */ + + /** @name Formatting + * @{ */ + RTCMDLSFORMAT enmFormat; /**< --format */ + + bool fEscapeNonGraphicChars; /**< -b, --escape */ + bool fEscapeControlChars; + bool fHideControlChars; /**< -q/--hide-control-chars, --show-control-chars */ + + bool fHumanReadableSizes; /**< -h */ + bool fSiUnits; /**< --si */ + uint32_t cbBlock; /**< --block-size=N, -k */ + + bool fShowOwner; + bool fShowGroup; + bool fNumericalIds; /**< -n */ + bool fShowINode; + bool fShowAllocatedSize; /**< -s */ + uint8_t cchTab; /**< -T */ + uint32_t cchWidth; /**< -w */ + + RTCMDLSCOLOR enmColor; /**< --color */ + + RTCMDLSTIME enmTime; /**< --time */ + RTCMDLSTIMESTYLE enmTimeStyle; /**< --time-style, --full-time */ + const char *pszTimeCustom; /**< --time-style=+xxx */ + /** @} */ + + /** @name State + * @{ */ + /** Current size of papCollections. */ + size_t cCollections; + /** Memory allocated for papCollections. */ + size_t cCollectionsAllocated; + /** Current entry collection pending display, the last may also be pending + * sorting. */ + PPRTCMDLSCOLLECTION papCollections; + /** @} */ +} RTCMDLSOPTS; +/** Pointer to ls options and state. */ +typedef RTCMDLSOPTS *PRTCMDLSOPTS; + + + + +/** @callback_method_impl{FNRTSORTCMP, Dirs first + Unsorted} */ +static DECLCALLBACK(int) rtCmdLsEntryCmpDirFirstUnsorted(void const *pvElement1, void const *pvElement2, void *pvUser) +{ + RT_NOREF(pvUser); + PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1; + PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2; + return !RTFS_IS_DIRECTORY(pEntry1->Info.Attr.fMode) - !RTFS_IS_DIRECTORY(pEntry2->Info.Attr.fMode); +} + + +/** @callback_method_impl{FNRTSORTCMP, Name} */ +static DECLCALLBACK(int) rtCmdLsEntryCmpName(void const *pvElement1, void const *pvElement2, void *pvUser) +{ + RT_NOREF(pvUser); + PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1; + PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2; + return RTStrCmp(pEntry1->szName, pEntry2->szName); +} + + +/** @callback_method_impl{FNRTSORTCMP, Dirs first + Name} */ +static DECLCALLBACK(int) rtCmdLsEntryCmpDirFirstName(void const *pvElement1, void const *pvElement2, void *pvUser) +{ + RT_NOREF(pvUser); + PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1; + PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2; + int iDiff = !RTFS_IS_DIRECTORY(pEntry1->Info.Attr.fMode) - !RTFS_IS_DIRECTORY(pEntry2->Info.Attr.fMode); + if (!iDiff) + iDiff = rtCmdLsEntryCmpName(pEntry1, pEntry2, pvUser); + return iDiff; +} + + +/** @callback_method_impl{FNRTSORTCMP, extension} */ +static DECLCALLBACK(int) rtCmdLsEntryCmpExtension(void const *pvElement1, void const *pvElement2, void *pvUser) +{ + RT_NOREF(pvUser); + PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1; + PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2; + int iDiff = RTStrCmp(RTPathSuffix(pEntry1->szName), RTPathSuffix(pEntry2->szName)); + if (!iDiff) + iDiff = RTStrCmp(pEntry1->szName, pEntry2->szName); + return iDiff; +} + + +/** @callback_method_impl{FNRTSORTCMP, Dirs first + Ext + Name} */ +static DECLCALLBACK(int) rtCmdLsEntryCmpDirFirstExtension(void const *pvElement1, void const *pvElement2, void *pvUser) +{ + RT_NOREF(pvUser); + PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1; + PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2; + int iDiff = !RTFS_IS_DIRECTORY(pEntry1->Info.Attr.fMode) - !RTFS_IS_DIRECTORY(pEntry2->Info.Attr.fMode); + if (!iDiff) + iDiff = rtCmdLsEntryCmpExtension(pEntry1, pEntry2, pvUser); + return iDiff; +} + + +/** @callback_method_impl{FNRTSORTCMP, Allocated size + Name} */ +static DECLCALLBACK(int) rtCmdLsEntryCmpAllocated(void const *pvElement1, void const *pvElement2, void *pvUser) +{ + RT_NOREF(pvUser); + PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1; + PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2; + if (pEntry1->Info.cbAllocated == pEntry2->Info.cbAllocated) + return rtCmdLsEntryCmpName(pEntry1, pEntry2, pvUser); + return pEntry1->Info.cbAllocated < pEntry2->Info.cbAllocated ? -1 : 1; +} + + +/** @callback_method_impl{FNRTSORTCMP, Dirs first + Allocated size + Name} */ +static DECLCALLBACK(int) rtCmdLsEntryCmpDirFirstAllocated(void const *pvElement1, void const *pvElement2, void *pvUser) +{ + RT_NOREF(pvUser); + PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1; + PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2; + int iDiff = !RTFS_IS_DIRECTORY(pEntry1->Info.Attr.fMode) - !RTFS_IS_DIRECTORY(pEntry2->Info.Attr.fMode); + if (!iDiff) + iDiff = rtCmdLsEntryCmpAllocated(pEntry1, pEntry2, pvUser); + return iDiff; +} + + +/** @callback_method_impl{FNRTSORTCMP, Content size + Name} */ +static DECLCALLBACK(int) rtCmdLsEntryCmpSize(void const *pvElement1, void const *pvElement2, void *pvUser) +{ + RT_NOREF(pvUser); + PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1; + PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2; + if (pEntry1->Info.cbObject == pEntry2->Info.cbObject) + return rtCmdLsEntryCmpName(pEntry1, pEntry2, pvUser); + return pEntry1->Info.cbObject < pEntry2->Info.cbObject ? -1 : 1; +} + + +/** @callback_method_impl{FNRTSORTCMP, Dirs first + Content size + Name} */ +static DECLCALLBACK(int) rtCmdLsEntryCmpDirFirstSize(void const *pvElement1, void const *pvElement2, void *pvUser) +{ + PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1; + PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2; + int iDiff = !RTFS_IS_DIRECTORY(pEntry1->Info.Attr.fMode) - !RTFS_IS_DIRECTORY(pEntry2->Info.Attr.fMode); + if (!iDiff) + iDiff = rtCmdLsEntryCmpSize(pEntry1, pEntry2, pvUser); + return iDiff; +} + + +/** @callback_method_impl{FNRTSORTCMP, Modification time + name} */ +static DECLCALLBACK(int) rtCmdLsEntryCmpMTime(void const *pvElement1, void const *pvElement2, void *pvUser) +{ + PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1; + PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2; + int iDiff = RTTimeSpecCompare(&pEntry1->Info.ModificationTime, &pEntry2->Info.ModificationTime); + if (!iDiff) + iDiff = rtCmdLsEntryCmpName(pEntry1, pEntry2, pvUser); + return iDiff; +} + + +/** @callback_method_impl{FNRTSORTCMP, Dirs first + Modification time + Name} */ +static DECLCALLBACK(int) rtCmdLsEntryCmpDirFirstMTime(void const *pvElement1, void const *pvElement2, void *pvUser) +{ + RT_NOREF(pvUser); + PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1; + PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2; + int iDiff = !RTFS_IS_DIRECTORY(pEntry1->Info.Attr.fMode) - !RTFS_IS_DIRECTORY(pEntry2->Info.Attr.fMode); + if (!iDiff) + iDiff = rtCmdLsEntryCmpMTime(pEntry1, pEntry2, pvUser); + return iDiff; +} + + +/** @callback_method_impl{FNRTSORTCMP, Birth time + name} */ +static DECLCALLBACK(int) rtCmdLsEntryCmpBTime(void const *pvElement1, void const *pvElement2, void *pvUser) +{ + PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1; + PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2; + int iDiff = RTTimeSpecCompare(&pEntry1->Info.BirthTime, &pEntry2->Info.BirthTime); + if (!iDiff) + iDiff = rtCmdLsEntryCmpName(pEntry1, pEntry2, pvUser); + return iDiff; +} + + +/** @callback_method_impl{FNRTSORTCMP, Dirs first + Birth time + Name} */ +static DECLCALLBACK(int) rtCmdLsEntryCmpDirFirstBTime(void const *pvElement1, void const *pvElement2, void *pvUser) +{ + RT_NOREF(pvUser); + PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1; + PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2; + int iDiff = !RTFS_IS_DIRECTORY(pEntry1->Info.Attr.fMode) - !RTFS_IS_DIRECTORY(pEntry2->Info.Attr.fMode); + if (!iDiff) + iDiff = rtCmdLsEntryCmpBTime(pEntry1, pEntry2, pvUser); + return iDiff; +} + + +/** @callback_method_impl{FNRTSORTCMP, Change time + name} */ +static DECLCALLBACK(int) rtCmdLsEntryCmpCTime(void const *pvElement1, void const *pvElement2, void *pvUser) +{ + PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1; + PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2; + int iDiff = RTTimeSpecCompare(&pEntry1->Info.ChangeTime, &pEntry2->Info.ChangeTime); + if (!iDiff) + iDiff = rtCmdLsEntryCmpName(pEntry1, pEntry2, pvUser); + return iDiff; +} + + +/** @callback_method_impl{FNRTSORTCMP, Dirs first + Change time + Name} */ +static DECLCALLBACK(int) rtCmdLsEntryCmpDirFirstCTime(void const *pvElement1, void const *pvElement2, void *pvUser) +{ + RT_NOREF(pvUser); + PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1; + PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2; + int iDiff = !RTFS_IS_DIRECTORY(pEntry1->Info.Attr.fMode) - !RTFS_IS_DIRECTORY(pEntry2->Info.Attr.fMode); + if (!iDiff) + iDiff = rtCmdLsEntryCmpCTime(pEntry1, pEntry2, pvUser); + return iDiff; +} + + +/** @callback_method_impl{FNRTSORTCMP, Accessed time + name} */ +static DECLCALLBACK(int) rtCmdLsEntryCmpATime(void const *pvElement1, void const *pvElement2, void *pvUser) +{ + PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1; + PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2; + int iDiff = RTTimeSpecCompare(&pEntry1->Info.AccessTime, &pEntry2->Info.AccessTime); + if (!iDiff) + iDiff = rtCmdLsEntryCmpName(pEntry1, pEntry2, pvUser); + return iDiff; +} + + +/** @callback_method_impl{FNRTSORTCMP, Dirs first + Accessed time + Name} */ +static DECLCALLBACK(int) rtCmdLsEntryCmpDirFirstATime(void const *pvElement1, void const *pvElement2, void *pvUser) +{ + RT_NOREF(pvUser); + PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1; + PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2; + int iDiff = !RTFS_IS_DIRECTORY(pEntry1->Info.Attr.fMode) - !RTFS_IS_DIRECTORY(pEntry2->Info.Attr.fMode); + if (!iDiff) + iDiff = rtCmdLsEntryCmpATime(pEntry1, pEntry2, pvUser); + return iDiff; +} + + +/** @callback_method_impl{FNRTSORTCMP, Name as version} */ +static DECLCALLBACK(int) rtCmdLsEntryCmpVersion(void const *pvElement1, void const *pvElement2, void *pvUser) +{ + RT_NOREF(pvUser); + PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1; + PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2; + return RTStrVersionCompare(pEntry1->szName, pEntry2->szName); +} + + +/** @callback_method_impl{FNRTSORTCMP, Dirs first + Name as version} */ +static DECLCALLBACK(int) rtCmdLsEntryCmpDirFirstVersion(void const *pvElement1, void const *pvElement2, void *pvUser) +{ + RT_NOREF(pvUser); + PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1; + PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2; + int iDiff = !RTFS_IS_DIRECTORY(pEntry1->Info.Attr.fMode) - !RTFS_IS_DIRECTORY(pEntry2->Info.Attr.fMode); + if (!iDiff) + iDiff = rtCmdLsEntryCmpVersion(pEntry1, pEntry2, pvUser); + return iDiff; +} + + +/** + * Sorts the entries in the collections according the sorting options. + * + * @param pOpts The options and state. + */ +static void rtCmdLsSortCollections(PRTCMDLSOPTS pOpts) +{ + /* + * Sort the entries in each collection. + */ + PFNRTSORTCMP pfnCmp; + switch (pOpts->enmSort) + { + case RTCMDLSSORT_NONE: + pfnCmp = pOpts->fGroupDirectoriesFirst ? rtCmdLsEntryCmpDirFirstUnsorted : NULL; + break; + default: AssertFailed(); RT_FALL_THRU(); + case RTCMDLSSORT_NAME: + pfnCmp = pOpts->fGroupDirectoriesFirst ? rtCmdLsEntryCmpDirFirstName : rtCmdLsEntryCmpName; + break; + case RTCMDLSSORT_EXTENSION: + pfnCmp = pOpts->fGroupDirectoriesFirst ? rtCmdLsEntryCmpDirFirstExtension : rtCmdLsEntryCmpExtension; + break; + case RTCMDLSSORT_SIZE: + if (pOpts->fShowAllocatedSize) + pfnCmp = pOpts->fGroupDirectoriesFirst ? rtCmdLsEntryCmpDirFirstAllocated : rtCmdLsEntryCmpAllocated; + else + pfnCmp = pOpts->fGroupDirectoriesFirst ? rtCmdLsEntryCmpDirFirstSize : rtCmdLsEntryCmpSize; + break; + case RTCMDLSSORT_TIME: + switch (pOpts->enmTime) + { + default: AssertFailed(); RT_FALL_THRU(); + case RTCMDLSTIME_MTIME: pfnCmp = pOpts->fGroupDirectoriesFirst ? rtCmdLsEntryCmpDirFirstMTime : rtCmdLsEntryCmpMTime; break; + case RTCMDLSTIME_BTIME: pfnCmp = pOpts->fGroupDirectoriesFirst ? rtCmdLsEntryCmpDirFirstBTime : rtCmdLsEntryCmpBTime; break; + case RTCMDLSTIME_CTIME: pfnCmp = pOpts->fGroupDirectoriesFirst ? rtCmdLsEntryCmpDirFirstCTime : rtCmdLsEntryCmpCTime; break; + case RTCMDLSTIME_ATIME: pfnCmp = pOpts->fGroupDirectoriesFirst ? rtCmdLsEntryCmpDirFirstATime : rtCmdLsEntryCmpATime; break; + } + break; + case RTCMDLSSORT_VERSION: + pfnCmp = pOpts->fGroupDirectoriesFirst ? rtCmdLsEntryCmpDirFirstVersion : rtCmdLsEntryCmpVersion; + break; + } + if (pfnCmp) + { + /* + * Walk thru the collections and sort their entries. + */ + size_t i = pOpts->cCollections; + while (i-- > 0) + { + PRTCMDLSCOLLECTION pCollection = pOpts->papCollections[i]; + RTSortApvShell((void **)pCollection->papEntries, pCollection->cEntries, pfnCmp, NULL); + + if (pOpts->fReverseSort) + { + PPRTCMDLSENTRY papEntries = pCollection->papEntries; + size_t iHead = 0; + size_t iTail = pCollection->cEntries; + while (iHead < iTail) + { + PRTCMDLSENTRY pTmp = papEntries[iHead]; + papEntries[iHead] = papEntries[iTail]; + papEntries[iTail] = pTmp; + iHead++; + iTail--; + } + } + } + } + + /** @todo sort the collections too, except for the first one. */ +} + + +/** + * Format human readable size. + */ +static const char *rtCmdLsFormatSizeHumanReadable(PRTCMDLSOPTS pOpts, uint64_t cb, char *pszDst, size_t cbDst) +{ + if (pOpts->fHumanReadableSizes) + { + if (!pOpts->fSiUnits) + { + size_t cch = RTStrPrintf(pszDst, cbDst, "%Rhub", cb); + if (pszDst[cch - 1] == 'i') + pszDst[cch - 1] = '\0'; /* drop the trailing 'i' */ + } + else + RTStrPrintf(pszDst, cbDst, "%Rhui", cb); + } + else if (pOpts->cbBlock) + RTStrFormatU64(pszDst, cbDst, (cb + pOpts->cbBlock - 1) / pOpts->cbBlock, 10, 0, 0, 0); + else + RTStrFormatU64(pszDst, cbDst, cb, 10, 0, 0, 0); + return pszDst; +} + + +/** + * Format block count. + */ +static const char *rtCmdLsFormatBlocks(PRTCMDLSOPTS pOpts, uint64_t cb, char *pszDst, size_t cbDst) +{ + if (pOpts->fHumanReadableSizes) + return rtCmdLsFormatSizeHumanReadable(pOpts, cb, pszDst, cbDst); + + uint32_t cbBlock = pOpts->cbBlock; + if (cbBlock == 0) + cbBlock = _1K; + RTStrFormatU64(pszDst, cbDst, (cb + cbBlock / 2 - 1) / cbBlock, 10, 0, 0, 0); + return pszDst; +} + + +/** + * Format file size. + */ +static const char *rtCmdLsFormatSize(PRTCMDLSOPTS pOpts, uint64_t cb, char *pszDst, size_t cbDst) +{ + if (pOpts->fHumanReadableSizes) + return rtCmdLsFormatSizeHumanReadable(pOpts, cb, pszDst, cbDst); + if (pOpts->cbBlock > 0) + return rtCmdLsFormatBlocks(pOpts, cb, pszDst, cbDst); + RTStrFormatU64(pszDst, cbDst, cb, 10, 0, 0, 0); + return pszDst; +} + + +/** + * Format name, i.e. escape, hide, quote stuff. + */ +static const char *rtCmdLsFormatName(PRTCMDLSOPTS pOpts, const char *pszName, char *pszDst, size_t cbDst) +{ + if ( !pOpts->fEscapeNonGraphicChars + && !pOpts->fEscapeControlChars + && !pOpts->fHideControlChars) + return pszName; + /** @todo implement name formatting. */ + RT_NOREF(pszDst, cbDst); + return pszName; +} + + +/** + * Figures out the length for a 32-bit number when formatted as decimal. + * @returns Number of digits. + * @param uValue The number. + */ +DECLINLINE(size_t) rtCmdLsDecimalFormatLengthU32(uint32_t uValue) +{ + if (uValue < 10) + return 1; + if (uValue < 100) + return 2; + if (uValue < 1000) + return 3; + if (uValue < 10000) + return 4; + if (uValue < 100000) + return 5; + if (uValue < 1000000) + return 6; + if (uValue < 10000000) + return 7; + if (uValue < 100000000) + return 8; + if (uValue < 1000000000) + return 9; + return 10; +} + + +/** + * Formats the given group ID according to the specified options. + * + * @returns pszDst + * @param pOpts The options and state. + * @param gid The GID to format. + * @param pszOwner The owner returned by the FS. + * @param pszDst The output buffer. + * @param cbDst The output buffer size. + */ +static const char *rtCmdLsDecimalFormatGroup(PRTCMDLSOPTS pOpts, RTGID gid, const char *pszGroup, char *pszDst, size_t cbDst) +{ + if (!pOpts->fNumericalIds) + { + if (pszGroup) + { + RTStrCopy(pszDst, cbDst, pszGroup); + return pszDst; + } + if (gid == NIL_RTGID) + return "<Nil>"; + } + RTStrFormatU64(pszDst, cbDst, gid, 10, 0, 0, 0); + return pszDst; +} + + +/** + * Formats the given user ID according to the specified options. + * + * @returns pszDst + * @param pOpts The options and state. + * @param uid The UID to format. + * @param pszOwner The owner returned by the FS. + * @param pszDst The output buffer. + * @param cbDst The output buffer size. + */ +static const char *rtCmdLsDecimalFormatOwner(PRTCMDLSOPTS pOpts, RTUID uid, const char *pszOwner, char *pszDst, size_t cbDst) +{ + if (!pOpts->fNumericalIds) + { + if (pszOwner) + { + RTStrCopy(pszDst, cbDst, pszOwner); + return pszDst; + } + if (uid == NIL_RTUID) + return "<Nil>"; + } + RTStrFormatU64(pszDst, cbDst, uid, 10, 0, 0, 0); + return pszDst; +} + + +/** + * Formats the given timestamp according to the desired --time-style. + * + * @returns pszDst + * @param pOpts The options and state. + * @param pTimestamp The timestamp. + * @param pszDst The output buffer. + * @param cbDst The output buffer size. + */ +static const char *rtCmdLsFormatTimestamp(PRTCMDLSOPTS pOpts, PCRTTIMESPEC pTimestamp, char *pszDst, size_t cbDst) +{ + /** @todo timestamp formatting according to the given style. */ + RT_NOREF(pOpts); + return RTTimeSpecToString(pTimestamp, pszDst, cbDst); +} + + + +/** + * RTCMDLSFORMAT_MACHINE_READABLE: --machine-readable + */ +static RTEXITCODE rtCmdLsDisplayCollectionInMachineReadableFormat(PRTCMDLSOPTS pOpts, PRTCMDLSCOLLECTION pCollection, + char *pszTmp, size_t cbTmp) +{ + RT_NOREF(pOpts, pCollection, pszTmp, cbTmp); + RTMsgError("Machine readable format not implemented\n"); + return RTEXITCODE_FAILURE; +} + + +/** + * RTCMDLSFORMAT_COMMAS: -m + */ +static RTEXITCODE rtCmdLsDisplayCollectionInCvsFormat(PRTCMDLSOPTS pOpts, PRTCMDLSCOLLECTION pCollection, + char *pszTmp, size_t cbTmp) +{ + RT_NOREF(pOpts, pCollection, pszTmp, cbTmp); + RTMsgError("Table output formats not implemented\n"); + return RTEXITCODE_FAILURE; +} + + +/** + * RTCMDLSFORMAT_LONG: -l + */ +static RTEXITCODE rtCmdLsDisplayCollectionInLongFormat(PRTCMDLSOPTS pOpts, PRTCMDLSCOLLECTION pCollection, + char *pszTmp, size_t cbTmp, size_t cchAllocatedCol) +{ + /* + * Figure the width of the size, the link count, the uid, the gid, and the inode columns. + */ + size_t cchSizeCol = 1; + size_t cchLinkCol = 1; + size_t cchUidCol = pOpts->fShowOwner ? 1 : 0; + size_t cchGidCol = pOpts->fShowGroup ? 1 : 0; + size_t cchINodeCol = pOpts->fShowINode ? 1 : 0; + + size_t i = pCollection->cEntries; + while (i-- > 0) + { + PRTCMDLSENTRY pEntry = pCollection->papEntries[i]; + + rtCmdLsFormatSize(pOpts, pEntry->Info.cbObject, pszTmp, cbTmp); + size_t cchTmp = strlen(pszTmp); + if (cchTmp > cchSizeCol) + cchSizeCol = cchTmp; + + cchTmp = rtCmdLsDecimalFormatLengthU32(pEntry->Info.Attr.u.Unix.cHardlinks) + 1; + if (cchTmp > cchLinkCol) + cchLinkCol = cchTmp; + + if (pOpts->fShowOwner) + { + rtCmdLsDecimalFormatOwner(pOpts, pEntry->Info.Attr.u.Unix.uid, pEntry->pszOwner, pszTmp, cbTmp); + cchTmp = strlen(pszTmp); + if (cchTmp > cchUidCol) + cchUidCol = cchTmp; + } + + if (pOpts->fShowGroup) + { + rtCmdLsDecimalFormatGroup(pOpts, pEntry->Info.Attr.u.Unix.gid, pEntry->pszGroup, pszTmp, cbTmp); + cchTmp = strlen(pszTmp); + if (cchTmp > cchGidCol) + cchGidCol = cchTmp; + } + + if (pOpts->fShowINode) + { + cchTmp = RTStrFormatU64(pszTmp, cchTmp, pEntry->Info.Attr.u.Unix.INodeId, 10, 0, 0, 0); + if (cchTmp > cchINodeCol) + cchINodeCol = cchTmp; + } + } + + /* + * Determin time member offset. + */ + size_t offTime; + switch (pOpts->enmTime) + { + default: AssertFailed(); RT_FALL_THRU(); + case RTCMDLSTIME_MTIME: offTime = RT_UOFFSETOF(RTCMDLSENTRY, Info.ModificationTime); break; + case RTCMDLSTIME_BTIME: offTime = RT_UOFFSETOF(RTCMDLSENTRY, Info.BirthTime); break; + case RTCMDLSTIME_CTIME: offTime = RT_UOFFSETOF(RTCMDLSENTRY, Info.ChangeTime); break; + case RTCMDLSTIME_ATIME: offTime = RT_UOFFSETOF(RTCMDLSENTRY, Info.AccessTime); break; + } + + /* + * Display the entries. + */ + for (i = 0; i < pCollection->cEntries; i++) + { + PRTCMDLSENTRY pEntry = pCollection->papEntries[i]; + + if (cchINodeCol) + RTPrintf("%*RU64 ", cchINodeCol, pEntry->Info.Attr.u.Unix.INodeId); + if (cchAllocatedCol) + RTPrintf("%*s ", cchAllocatedCol, rtCmdLsFormatBlocks(pOpts, pEntry->Info.cbAllocated, pszTmp, cbTmp)); + + RTFMODE fMode = pEntry->Info.Attr.fMode; + switch (fMode & RTFS_TYPE_MASK) + { + case RTFS_TYPE_FIFO: RTPrintf("f"); break; + case RTFS_TYPE_DEV_CHAR: RTPrintf("c"); break; + case RTFS_TYPE_DIRECTORY: RTPrintf("d"); break; + case RTFS_TYPE_DEV_BLOCK: RTPrintf("b"); break; + case RTFS_TYPE_FILE: RTPrintf("-"); break; + case RTFS_TYPE_SYMLINK: RTPrintf("l"); break; + case RTFS_TYPE_SOCKET: RTPrintf("s"); break; + case RTFS_TYPE_WHITEOUT: RTPrintf("w"); break; + default: RTPrintf("?"); AssertFailed(); break; + } + /** @todo sticy bits++ */ + RTPrintf("%c%c%c", + fMode & RTFS_UNIX_IRUSR ? 'r' : '-', + fMode & RTFS_UNIX_IWUSR ? 'w' : '-', + fMode & RTFS_UNIX_IXUSR ? 'x' : '-'); + RTPrintf("%c%c%c", + fMode & RTFS_UNIX_IRGRP ? 'r' : '-', + fMode & RTFS_UNIX_IWGRP ? 'w' : '-', + fMode & RTFS_UNIX_IXGRP ? 'x' : '-'); + RTPrintf("%c%c%c", + fMode & RTFS_UNIX_IROTH ? 'r' : '-', + fMode & RTFS_UNIX_IWOTH ? 'w' : '-', + fMode & RTFS_UNIX_IXOTH ? 'x' : '-'); + if (1) + { + RTPrintf(" %c%c%c%c%c%c%c%c%c%c%c%c%c%c", + fMode & RTFS_DOS_READONLY ? 'R' : '-', + fMode & RTFS_DOS_HIDDEN ? 'H' : '-', + fMode & RTFS_DOS_SYSTEM ? 'S' : '-', + fMode & RTFS_DOS_DIRECTORY ? 'D' : '-', + fMode & RTFS_DOS_ARCHIVED ? 'A' : '-', + fMode & RTFS_DOS_NT_DEVICE ? 'd' : '-', + fMode & RTFS_DOS_NT_NORMAL ? 'N' : '-', + fMode & RTFS_DOS_NT_TEMPORARY ? 'T' : '-', + fMode & RTFS_DOS_NT_SPARSE_FILE ? 'P' : '-', + fMode & RTFS_DOS_NT_REPARSE_POINT ? 'J' : '-', + fMode & RTFS_DOS_NT_COMPRESSED ? 'C' : '-', + fMode & RTFS_DOS_NT_OFFLINE ? 'O' : '-', + fMode & RTFS_DOS_NT_NOT_CONTENT_INDEXED ? 'I' : '-', + fMode & RTFS_DOS_NT_ENCRYPTED ? 'E' : '-'); + } + RTPrintf(" %*u", cchLinkCol, pEntry->Info.Attr.u.Unix.cHardlinks); + if (cchUidCol) + RTPrintf(" %*s", cchUidCol, + rtCmdLsDecimalFormatOwner(pOpts, pEntry->Info.Attr.u.Unix.uid, pEntry->pszOwner, pszTmp, cbTmp)); + if (cchGidCol) + RTPrintf(" %*s", cchGidCol, + rtCmdLsDecimalFormatGroup(pOpts, pEntry->Info.Attr.u.Unix.gid, pEntry->pszGroup, pszTmp, cbTmp)); + RTPrintf(" %*s", cchSizeCol, rtCmdLsFormatSize(pOpts, pEntry->Info.cbObject, pszTmp, cbTmp)); + + PCRTTIMESPEC pTime = (PCRTTIMESPEC)((uintptr_t)pEntry + offTime); + RTPrintf(" %s", rtCmdLsFormatTimestamp(pOpts, pTime, pszTmp, cbTmp)); + + RTPrintf(" %s\n", rtCmdLsFormatName(pOpts, pEntry->szName, pszTmp, cbTmp)); + } + + return RTEXITCODE_SUCCESS; +} + + +/** + * RTCMDLSFORMAT_SINGLE: -1 + */ +static RTEXITCODE rtCmdLsDisplayCollectionInSingleFormat(PRTCMDLSOPTS pOpts, PRTCMDLSCOLLECTION pCollection, + char *pszTmp, size_t cbTmp, size_t cchAllocatedCol) +{ + if (cchAllocatedCol > 0) + for (size_t i = 0; i < pCollection->cEntries; i++) + { + PRTCMDLSENTRY pEntry = pCollection->papEntries[i]; + RTPrintf("%*s %s\n", + cchAllocatedCol, rtCmdLsFormatBlocks(pOpts, pEntry->Info.cbAllocated, pszTmp, cbTmp / 4), + rtCmdLsFormatName(pOpts, pEntry->szName, &pszTmp[cbTmp / 4], cbTmp / 4 * 3)); + } + else + for (size_t i = 0; i < pCollection->cEntries; i++) + { + PRTCMDLSENTRY pEntry = pCollection->papEntries[i]; + RTPrintf("%s\n", rtCmdLsFormatName(pOpts, pEntry->szName, pszTmp, cbTmp)); + } + + return RTEXITCODE_SUCCESS; +} + + +/** + * RTCMDLSFORMAT_COLS_VERTICAL: default, -C; RTCMDLSFORMAT_COLS_HORIZONTAL: -x + */ +static RTEXITCODE rtCmdLsDisplayCollectionInTableFormat(PRTCMDLSOPTS pOpts, PRTCMDLSCOLLECTION pCollection, + char *pszTmp, size_t cbTmp, size_t cchAllocatedCol) +{ + RT_NOREF(pOpts, pCollection, pszTmp, cbTmp, cchAllocatedCol); + RTMsgError("Table output formats not implemented\n"); + return RTEXITCODE_FAILURE; +} + + +/** + * Does the actual displaying of the entry collections. + * + * @returns Program exit code. + * @param pOpts The options and state. + */ +static RTEXITCODE rtCmdLsDisplayCollections(PRTCMDLSOPTS pOpts) +{ + rtCmdLsSortCollections(pOpts); + + bool const fNeedCollectionName = pOpts->cCollections > 2 + || ( pOpts->cCollections == 2 + && pOpts->papCollections[0]->cEntries > 0); + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + for (size_t iCollection = 0; iCollection < pOpts->cCollections; iCollection++) + { + PRTCMDLSCOLLECTION pCollection = pOpts->papCollections[iCollection]; + char szTmp[RTPATH_MAX*2]; + + /* The header. */ + if (iCollection != 0) + { + if ( iCollection > 1 + || pOpts->papCollections[0]->cEntries > 0) + RTPrintf("\n"); + if (fNeedCollectionName) + RTPrintf("%s:\n", rtCmdLsFormatName(pOpts, pCollection->szName, szTmp, sizeof(szTmp))); + RTPrintf("total %s\n", rtCmdLsFormatBlocks(pOpts, pCollection->cbTotalAllocated, szTmp, sizeof(szTmp))); + } + + /* Format the entries. */ + RTEXITCODE rcExit2; + if (pOpts->enmFormat == RTCMDLSFORMAT_MACHINE_READABLE) + rcExit2 = rtCmdLsDisplayCollectionInMachineReadableFormat(pOpts, pCollection, szTmp, sizeof(szTmp)); + else if (pOpts->enmFormat == RTCMDLSFORMAT_COMMAS) + rcExit2 = rtCmdLsDisplayCollectionInCvsFormat(pOpts, pCollection, szTmp, sizeof(szTmp)); + else + { + /* If the allocated size is requested, calculate the column width. */ + size_t cchAllocatedCol = 0; + if (pOpts->fShowAllocatedSize) + { + size_t i = pCollection->cEntries; + while (i-- > 0) + { + rtCmdLsFormatBlocks(pOpts, pCollection->papEntries[i]->Info.cbAllocated, szTmp, sizeof(szTmp)); + size_t cchTmp = strlen(szTmp); + if (cchTmp > cchAllocatedCol) + cchAllocatedCol = cchTmp; + } + } + + /* Do the individual formatting. */ + if (pOpts->enmFormat == RTCMDLSFORMAT_LONG) + rcExit2 = rtCmdLsDisplayCollectionInLongFormat(pOpts, pCollection, szTmp, sizeof(szTmp), cchAllocatedCol); + else if (pOpts->enmFormat == RTCMDLSFORMAT_SINGLE) + rcExit2 = rtCmdLsDisplayCollectionInSingleFormat(pOpts, pCollection, szTmp, sizeof(szTmp), cchAllocatedCol); + else + rcExit2 = rtCmdLsDisplayCollectionInTableFormat(pOpts, pCollection, szTmp, sizeof(szTmp), cchAllocatedCol); + } + if (rcExit2 != RTEXITCODE_SUCCESS) + rcExit = rcExit2; + } + return rcExit; +} + + +/** + * Frees all collections and their entries. + * @param pOpts The options and state. + */ +static void rtCmdLsFreeCollections(PRTCMDLSOPTS pOpts) +{ + size_t i = pOpts->cCollections; + while (i-- > 0) + { + PRTCMDLSCOLLECTION pCollection = pOpts->papCollections[i]; + PPRTCMDLSENTRY papEntries = pCollection->papEntries; + size_t j = pCollection->cEntries; + while (j-- > 0) + { + RTMemFree(papEntries[j]); + papEntries[j] = NULL; + } + RTMemFree(papEntries); + pCollection->papEntries = NULL; + pCollection->cEntries = 0; + pCollection->cEntriesAllocated = 0; + RTMemFree(pCollection); + pOpts->papCollections[i] = NULL; + } + + RTMemFree(pOpts->papCollections); + pOpts->papCollections = NULL; + pOpts->cCollections = 0; + pOpts->cCollectionsAllocated = 0; +} + + +/** + * Allocates a new collection. + * + * @returns Pointer to the collection. + * @param pOpts The options and state. + * @param pszName The collection name. Empty for special first + * collection. + */ +static PRTCMDLSCOLLECTION rtCmdLsNewCollection(PRTCMDLSOPTS pOpts, const char *pszName) +{ + /* Grow the pointer table? */ + if (pOpts->cCollections >= pOpts->cCollectionsAllocated) + { + size_t cNew = pOpts->cCollectionsAllocated ? pOpts->cCollectionsAllocated * 2 : 16; + void *pvNew = RTMemRealloc(pOpts->papCollections, cNew * sizeof(pOpts->papCollections[0])); + if (!pvNew) + { + RTMsgError("Out of memory! (resize collections)"); + return NULL; + } + pOpts->cCollectionsAllocated = cNew; + pOpts->papCollections = (PPRTCMDLSCOLLECTION)pvNew; + + /* If this is the first time and pszName isn't empty, add the zero'th + entry for the command line stuff (hardcoded first collection). */ + if ( pOpts->cCollections == 0 + && *pszName) + { + PRTCMDLSCOLLECTION pCollection = (PRTCMDLSCOLLECTION)RTMemAllocZ(RT_UOFFSETOF(RTCMDLSCOLLECTION, szName[1])); + if (!pCollection) + { + RTMsgError("Out of memory! (collection)"); + return NULL; + } + pOpts->papCollections[0] = pCollection; + pOpts->cCollections = 1; + } + } + + /* Add new collection. */ + size_t cbName = strlen(pszName) + 1; + PRTCMDLSCOLLECTION pCollection = (PRTCMDLSCOLLECTION)RTMemAllocZ(RT_UOFFSETOF_DYN(RTCMDLSCOLLECTION, szName[cbName])); + if (pCollection) + { + memcpy(pCollection->szName, pszName, cbName); + pOpts->papCollections[pOpts->cCollections++] = pCollection; + } + else + RTMsgError("Out of memory! (collection)"); + return pCollection; +} + + +/** + * Adds one entry to a collection. + * @returns Program exit code + * @param pCollection The collection. + * @param pszEntry The entry name. + * @param pInfo The entry info. + * @param pszOwner The owner name if available, otherwise NULL. + * @param pszGroup The group anme if available, otherwise NULL. + * @param pszTarget The symbolic link target if applicable and + * available, otherwise NULL. + */ +static RTEXITCODE rtCmdLsAddOne(PRTCMDLSCOLLECTION pCollection, const char *pszEntry, PRTFSOBJINFO pInfo, + const char *pszOwner, const char *pszGroup, const char *pszTarget) +{ + + /* Make sure there is space in the collection for the new entry. */ + if (pCollection->cEntries >= pCollection->cEntriesAllocated) + { + size_t cNew = pCollection->cEntriesAllocated ? pCollection->cEntriesAllocated * 2 : 16; + void *pvNew = RTMemRealloc(pCollection->papEntries, cNew * sizeof(pCollection->papEntries[0])); + if (!pvNew) + return RTMsgErrorExitFailure("Out of memory! (resize entries)"); + pCollection->papEntries = (PPRTCMDLSENTRY)pvNew; + pCollection->cEntriesAllocated = cNew; + } + + /* Create and insert a new entry. */ + size_t const cchEntry = strlen(pszEntry); + size_t const cbOwner = pszOwner ? strlen(pszOwner) + 1 : 0; + size_t const cbGroup = pszGroup ? strlen(pszGroup) + 1 : 0; + size_t const cbTarget = pszTarget ? strlen(pszTarget) + 1 : 0; + size_t const cbEntry = RT_UOFFSETOF_DYN(RTCMDLSENTRY, szName[cchEntry + 1 + cbOwner + cbGroup + cbTarget]); + PRTCMDLSENTRY pEntry = (PRTCMDLSENTRY)RTMemAlloc(cbEntry); + if (pEntry) + { + pEntry->Info = *pInfo; + pEntry->pszTarget = NULL; /** @todo symbolic links. */ + pEntry->pszOwner = NULL; + pEntry->pszGroup = NULL; + pEntry->cchName = cchEntry; + memcpy(pEntry->szName, pszEntry, cchEntry); + pEntry->szName[cchEntry] = '\0'; + + char *psz = &pEntry->szName[cchEntry + 1]; + if (pszTarget) + { + pEntry->pszTarget = psz; + memcpy(psz, pszTarget, cbTarget); + psz += cbTarget; + } + if (pszOwner) + { + pEntry->pszOwner = psz; + memcpy(psz, pszOwner, cbOwner); + psz += cbOwner; + } + if (pszGroup) + { + pEntry->pszGroup = psz; + memcpy(psz, pszGroup, cbGroup); + } + + pCollection->papEntries[pCollection->cEntries++] = pEntry; + pCollection->cbTotalAllocated += pEntry->Info.cbAllocated; + pCollection->cbTotalFiles += pEntry->Info.cbObject; + return RTEXITCODE_SUCCESS; + } + return RTMsgErrorExitFailure("Out of memory! (entry)"); +} + + +/** + * Checks if the entry is to be filtered out. + * + * @returns true if filtered out, false if included. + * @param pOpts The options and state. + * @param pszEntry The entry name. + * @param pInfo The entry info. + */ +static bool rtCmdLsIsFilteredOut(PRTCMDLSOPTS pOpts, const char *pszEntry, PCRTFSOBJINFO pInfo) +{ + /* + * Should we filter out this entry? + */ + if ( !pOpts->fShowHidden + && (pInfo->Attr.fMode & RTFS_DOS_HIDDEN)) + return true; + + size_t const cchEntry = strlen(pszEntry); + if ( !pOpts->fShowDotAndDotDot + && cchEntry <= 2 + && pszEntry[0] == '.' + && ( cchEntry == 1 + || pszEntry[1] == '.' )) + return true; + + if ( !pOpts->fShowBackups + && pszEntry[cchEntry - 1] == '~') + return true; + return false; +} + + +/** + * Processes a directory, recursing into subdirectories if desired. + * + * @returns Program exit code. + * @param pOpts The options. + * @param hVfsDir The directory. + * @param pszPath Path buffer, RTPATH_MAX in size. + * @param cchPath The length of the current path. + * @param pInfo The parent information. + */ +static RTEXITCODE rtCmdLsProcessDirectory(PRTCMDLSOPTS pOpts, RTVFSDIR hVfsDir, char *pszPath, size_t cchPath, PCRTFSOBJINFO pInfo) +{ + /* + * Create a new collection for this directory. + */ + RT_NOREF(pInfo); + PRTCMDLSCOLLECTION pCollection = rtCmdLsNewCollection(pOpts, pszPath); + if (!pCollection) + return RTEXITCODE_FAILURE; + + /* + * Process the directory entries. + */ + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + size_t cbDirEntryAlloced = sizeof(RTDIRENTRYEX); + PRTDIRENTRYEX pDirEntry = (PRTDIRENTRYEX)RTMemTmpAlloc(cbDirEntryAlloced); + if (!pDirEntry) + return RTMsgErrorExitFailure("Out of memory! (direntry buffer)"); + + for (;;) + { + /* + * Read the next entry. + */ + size_t cbDirEntry = cbDirEntryAlloced; + int rc = RTVfsDirReadEx(hVfsDir, pDirEntry, &cbDirEntry, RTFSOBJATTRADD_UNIX); + if (RT_FAILURE(rc)) + { + if (rc == VERR_BUFFER_OVERFLOW) + { + RTMemTmpFree(pDirEntry); + cbDirEntryAlloced = RT_ALIGN_Z(RT_MIN(cbDirEntry, cbDirEntryAlloced) + 64, 64); + pDirEntry = (PRTDIRENTRYEX)RTMemTmpAlloc(cbDirEntryAlloced); + if (pDirEntry) + continue; + rcExit = RTMsgErrorExitFailure("Out of memory (direntry buffer)"); + } + else if (rc != VERR_NO_MORE_FILES) + rcExit = RTMsgErrorExitFailure("RTVfsDirReadEx failed: %Rrc\n", rc); + break; + } + + /* + * Process the entry. + */ + if (rtCmdLsIsFilteredOut(pOpts, pDirEntry->szName, &pDirEntry->Info)) + continue; + + + const char *pszOwner = NULL; + RTFSOBJINFO OwnerInfo; + if (pDirEntry->Info.Attr.u.Unix.uid != NIL_RTUID && pOpts->fShowOwner) + { + rc = RTVfsDirQueryPathInfo(hVfsDir, pDirEntry->szName, &OwnerInfo, RTFSOBJATTRADD_UNIX_OWNER, RTPATH_F_ON_LINK); + if (RT_SUCCESS(rc) && OwnerInfo.Attr.u.UnixOwner.szName[0]) + pszOwner = &OwnerInfo.Attr.u.UnixOwner.szName[0]; + } + + const char *pszGroup = NULL; + RTFSOBJINFO GroupInfo; + if (pDirEntry->Info.Attr.u.Unix.gid != NIL_RTGID && pOpts->fShowGroup) + { + rc = RTVfsDirQueryPathInfo(hVfsDir, pDirEntry->szName, &GroupInfo, RTFSOBJATTRADD_UNIX_GROUP, RTPATH_F_ON_LINK); + if (RT_SUCCESS(rc) && GroupInfo.Attr.u.UnixGroup.szName[0]) + pszGroup = &GroupInfo.Attr.u.UnixGroup.szName[0]; + } + + RTEXITCODE rcExit2 = rtCmdLsAddOne(pCollection, pDirEntry->szName, &pDirEntry->Info, pszOwner, pszGroup, NULL); + if (rcExit2 != RTEXITCODE_SUCCESS) + rcExit = rcExit2; + } + + RTMemTmpFree(pDirEntry); + + /* + * Recurse into subdirectories if requested. + */ + if (pOpts->fRecursive) + { + for (uint32_t i = 0; i < pCollection->cEntries; i++) + { + PRTCMDLSENTRY pEntry = pCollection->papEntries[i]; + if (RTFS_IS_SYMLINK(pEntry->Info.Attr.fMode)) + { + if (!pOpts->fFollowSymlinksInDirs) + continue; + /** @todo implement following symbolic links in the tree. */ + continue; + } + else if ( !RTFS_IS_DIRECTORY(pEntry->Info.Attr.fMode) + || ( pEntry->szName[0] == '.' + && ( pEntry->szName[1] == '\0' + || ( pEntry->szName[1] == '.' + && pEntry->szName[2] == '\0'))) ) + continue; + + /* Open subdirectory and process it. */ + RTVFSDIR hSubDir; + int rc = RTVfsDirOpenDir(hVfsDir, pEntry->szName, 0 /*fFlags*/, &hSubDir); + if (RT_SUCCESS(rc)) + { + if (cchPath + 1 + pEntry->cchName + 1 < RTPATH_MAX) + { + pszPath[cchPath] = RTPATH_SLASH; + memcpy(&pszPath[cchPath + 1], pEntry->szName, pEntry->cchName + 1); + RTEXITCODE rcExit2 = rtCmdLsProcessDirectory(pOpts, hSubDir, pszPath, + cchPath + 1 + pEntry->cchName, &pEntry->Info); + if (rcExit2 != RTEXITCODE_SUCCESS) + rcExit = rcExit2; + pszPath[cchPath] = '\0'; + } + else + rcExit = RTMsgErrorExitFailure("Too deep recursion: %s%c%s", pszPath, RTPATH_SLASH, pEntry->szName); + RTVfsDirRelease(hSubDir); + } + else + rcExit = RTMsgErrorExitFailure("RTVfsDirOpenDir failed on %s in %s: %Rrc\n", pEntry->szName, pszPath, rc); + } + } + return rcExit; +} + + +/** + * Processes one argument. + * + * @returns Program exit code. + * @param pOpts The options. + * @param pszArg The argument. + */ +static RTEXITCODE rtCmdLsProcessArgument(PRTCMDLSOPTS pOpts, const char *pszArg) +{ + /* + * Query info about the object 'pszArg' indicates. + */ + RTERRINFOSTATIC ErrInfo; + uint32_t offError; + RTFSOBJINFO Info; + uint32_t fPath = pOpts->fFollowSymlinkToAnyArgs ? RTPATH_F_FOLLOW_LINK : RTPATH_F_ON_LINK; + int rc = RTVfsChainQueryInfo(pszArg, &Info, RTFSOBJATTRADD_UNIX, fPath, &offError, RTErrInfoInitStatic(&ErrInfo)); + if (RT_FAILURE(rc)) + return RTVfsChainMsgErrorExitFailure("RTVfsChainQueryInfo", pszArg, rc, offError, &ErrInfo.Core); + + /* Symbolic links requires special handling of course. */ + if (RTFS_IS_SYMLINK(Info.Attr.fMode)) + { + if (pOpts->fFollowSymlinkToDirArgs) + { + RTFSOBJINFO Info2; + rc = RTVfsChainQueryInfo(pszArg, &Info2, RTFSOBJATTRADD_UNIX, RTPATH_F_FOLLOW_LINK, + &offError, RTErrInfoInitStatic(&ErrInfo)); + if (RT_SUCCESS(rc) && !RTFS_IS_DIRECTORY(Info.Attr.fMode)) + Info = Info2; + } + } + + /* + * If it's not a directory or we've been told to process directories + * without going into them, just add it to the default collection. + */ + if ( !pOpts->fFollowDirectoryArgs + || !RTFS_IS_DIRECTORY(Info.Attr.fMode)) + { + if ( pOpts->cCollections > 0 + || rtCmdLsNewCollection(pOpts, "") != NULL) + { + const char *pszOwner = NULL; + RTFSOBJINFO OwnerInfo; + if (Info.Attr.u.Unix.uid != NIL_RTUID && pOpts->fShowOwner) + { + rc = RTVfsChainQueryInfo(pszArg, &OwnerInfo, RTFSOBJATTRADD_UNIX_OWNER, fPath, NULL, NULL); + if (RT_SUCCESS(rc) && OwnerInfo.Attr.u.UnixOwner.szName[0]) + pszOwner = &OwnerInfo.Attr.u.UnixOwner.szName[0]; + } + + const char *pszGroup = NULL; + RTFSOBJINFO GroupInfo; + if (Info.Attr.u.Unix.gid != NIL_RTGID && pOpts->fShowGroup) + { + rc = RTVfsChainQueryInfo(pszArg, &GroupInfo, RTFSOBJATTRADD_UNIX_GROUP, fPath, NULL, NULL); + if (RT_SUCCESS(rc) && GroupInfo.Attr.u.UnixGroup.szName[0]) + pszGroup = &GroupInfo.Attr.u.UnixGroup.szName[0]; + } + + return rtCmdLsAddOne(pOpts->papCollections[0], pszArg, &Info, pszOwner, pszGroup, NULL); + } + return RTEXITCODE_FAILURE; + } + + /* + * Open the directory. + */ + RTVFSDIR hVfsDir; + rc = RTVfsChainOpenDir(pszArg, 0 /*fFlags*/, &hVfsDir, &offError, RTErrInfoInitStatic(&ErrInfo)); + if (RT_FAILURE(rc)) + return RTVfsChainMsgErrorExitFailure("RTVfsChainOpenDir", pszArg, rc, offError, &ErrInfo.Core); + + RTEXITCODE rcExit; + char szPath[RTPATH_MAX]; + size_t cchPath = strlen(pszArg); + if (cchPath < sizeof(szPath)) + { + memcpy(szPath, pszArg, cchPath + 1); + rcExit = rtCmdLsProcessDirectory(pOpts, hVfsDir, szPath, cchPath, &Info); + } + else + rcExit = RTMsgErrorExitFailure("Too long argument: %s", pszArg); + RTVfsDirRelease(hVfsDir); + return rcExit; +} + + +/** + * A /bin/ls clone. + * + * @returns Program exit code. + * + * @param cArgs The number of arguments. + * @param papszArgs The argument vector. (Note that this may be + * reordered, so the memory must be writable.) + */ +RTR3DECL(RTEXITCODE) RTFsCmdLs(unsigned cArgs, char **papszArgs) +{ + + /* + * Parse the command line. + */ +#define OPT_AUTHOR 1000 +#define OPT_BLOCK_SIZE 1001 +#define OPT_COLOR 1002 +#define OPT_FILE_TYPE 1003 +#define OPT_FORMAT 1004 +#define OPT_FULL_TIME 1005 +#define OPT_GROUP_DIRECTORIES_FIRST 1006 +#define OPT_SI 1007 +#define OPT_DEREFERENCE_COMMAND_LINE_SYMLINK_TO_DIR 1008 +#define OPT_HIDE 1009 +#define OPT_INDICATOR_STYLE 1010 +#define OPT_MACHINE_READABLE 1011 +#define OPT_SHOW_CONTROL_CHARS 1012 +#define OPT_QUOTING_STYLE 1013 +#define OPT_SORT 1014 +#define OPT_TIME 1015 +#define OPT_TIME_STYLE 1016 + static const RTGETOPTDEF s_aOptions[] = + { + { "--all", 'a', RTGETOPT_REQ_NOTHING }, + { "--almost-all", 'A', RTGETOPT_REQ_NOTHING }, + //{ "--author", OPT_AUTHOR, RTGETOPT_REQ_NOTHING }, + { "--escape", 'b', RTGETOPT_REQ_NOTHING }, + { "--block-size", OPT_BLOCK_SIZE, RTGETOPT_REQ_UINT32 }, + { "--ctime", 'c', RTGETOPT_REQ_NOTHING }, + //{ "--columns", 'C', RTGETOPT_REQ_NOTHING }, + //{ "--color", OPT_COLOR, RTGETOPT_OPT_STRING }, + { "--directory", 'd', RTGETOPT_REQ_NOTHING }, + //{ "--dired", 'D', RTGETOPT_REQ_NOTHING }, + { "--dash-f", 'f', RTGETOPT_REQ_NOTHING }, + //{ "--classify", 'F', RTGETOPT_REQ_NOTHING }, + //{ "--file-type", OPT_FILE_TYPE, RTGETOPT_REQ_NOTHING }, + { "--format", OPT_FORMAT, RTGETOPT_REQ_STRING }, + { "--full-time", OPT_FULL_TIME, RTGETOPT_REQ_NOTHING }, + { "--dash-g", 'g', RTGETOPT_REQ_NOTHING }, + { "--group-directories-first", OPT_GROUP_DIRECTORIES_FIRST, RTGETOPT_REQ_NOTHING }, + { "--no-group", 'G', RTGETOPT_REQ_NOTHING }, + { "--human-readable", 'h', RTGETOPT_REQ_NOTHING }, + { "--si", OPT_SI, RTGETOPT_REQ_NOTHING }, + { "--dereference-command-line", 'H', RTGETOPT_REQ_NOTHING }, + { "--dereference-command-line-symlink-to-dir", OPT_DEREFERENCE_COMMAND_LINE_SYMLINK_TO_DIR, RTGETOPT_REQ_NOTHING }, + //{ "--hide" OPT_HIDE, RTGETOPT_REQ_STRING }, + //{ "--indicator-style" OPT_INDICATOR_STYLE, RTGETOPT_REQ_STRING }, + { "--inode", 'i', RTGETOPT_REQ_NOTHING }, + { "--block-size-1kib", 'k', RTGETOPT_REQ_NOTHING }, + { "--long", 'l', RTGETOPT_REQ_NOTHING }, + { "--dereference", 'L', RTGETOPT_REQ_NOTHING }, + { "--format-commas", 'm', RTGETOPT_REQ_NOTHING }, + { "--machinereadable", OPT_MACHINE_READABLE, RTGETOPT_REQ_NOTHING }, + { "--machine-readable", OPT_MACHINE_READABLE, RTGETOPT_REQ_NOTHING }, + { "--numeric-uid-gid", 'n', RTGETOPT_REQ_NOTHING }, + { "--literal", 'N', RTGETOPT_REQ_NOTHING }, + { "--long-without-group-info", 'o', RTGETOPT_REQ_NOTHING }, + //{ "--indicator-style", 'p', RTGETOPT_REQ_STRING }, + { "--hide-control-chars", 'q', RTGETOPT_REQ_NOTHING }, + { "--show-control-chars", OPT_SHOW_CONTROL_CHARS, RTGETOPT_REQ_NOTHING }, + //{ "--quote-name", 'Q', RTGETOPT_REQ_NOTHING }, + //{ "--quoting-style", OPT_QUOTING_STYLE, RTGETOPT_REQ_STRING }, + { "--reverse", 'r', RTGETOPT_REQ_NOTHING }, + { "--recursive", 'R', RTGETOPT_REQ_NOTHING }, + { "--size", 's', RTGETOPT_REQ_NOTHING }, + { "--sort-by-size", 'S', RTGETOPT_REQ_NOTHING }, + { "--sort", OPT_SORT, RTGETOPT_REQ_STRING }, + { "--time", OPT_TIME, RTGETOPT_REQ_STRING }, + { "--time-style", OPT_TIME_STYLE, RTGETOPT_REQ_STRING }, + { "--sort-by-time", 't', RTGETOPT_REQ_NOTHING }, + { "--tabsize", 'T', RTGETOPT_REQ_UINT8 }, + { "--atime", 'u', RTGETOPT_REQ_NOTHING }, + { "--unsorted", 'U', RTGETOPT_REQ_NOTHING }, + { "--version-sort", 'v', RTGETOPT_REQ_NOTHING }, + { "--width", 'w', RTGETOPT_REQ_UINT32 }, + { "--list-by-line", 'x', RTGETOPT_REQ_NOTHING }, + { "--sort-by-extension", 'X', RTGETOPT_REQ_NOTHING }, + { "--one-file-per-line", '1', RTGETOPT_REQ_NOTHING }, + { "--help", '?', RTGETOPT_REQ_NOTHING }, + }; + + RTCMDLSOPTS Opts; + Opts.fFollowSymlinksInDirs = false; + Opts.fFollowSymlinkToAnyArgs = false; + Opts.fFollowSymlinkToDirArgs = false; + Opts.fFollowDirectoryArgs = true; + Opts.fRecursive = false; + Opts.fShowHidden = false; + Opts.fShowDotAndDotDot = false; + Opts.fShowBackups = true; + Opts.enmSort = RTCMDLSSORT_NAME; + Opts.fReverseSort = false; + Opts.fGroupDirectoriesFirst = false; + Opts.enmFormat = RTCMDLSFORMAT_COLS_VERTICAL; + Opts.fEscapeNonGraphicChars = false; + Opts.fEscapeControlChars = true; + Opts.fHideControlChars = false; + Opts.fHumanReadableSizes = false; /**< -h */ + Opts.fSiUnits = false; + Opts.cbBlock = 0; + Opts.fShowOwner = true; + Opts.fShowGroup = true; + Opts.fNumericalIds = false; + Opts.fShowINode = false; + Opts.fShowAllocatedSize = false; + Opts.cchTab = 8; + Opts.cchWidth = 80; + Opts.enmColor = RTCMDLSCOLOR_NONE; + Opts.enmTime = RTCMDLSTIME_MTIME; + Opts.enmTimeStyle = RTCMDLSTIMESTYLE_LOCALE; + Opts.pszTimeCustom = NULL; + + Opts.cCollections = 0; + Opts.cCollectionsAllocated = 0; + Opts.papCollections = NULL; + + + RTEXITCODE rcExit = RTEXITCODE_SUCCESS; + unsigned cProcessed = 0; + RTGETOPTSTATE GetState; + int rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1, + RTGETOPTINIT_FLAGS_OPTS_FIRST); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "RTGetOptInit: %Rrc", rc); + + for (;;) + { + RTGETOPTUNION ValueUnion; + int chOpt = RTGetOpt(&GetState, &ValueUnion); + switch (chOpt) + { + case 0: + /* When reaching the end of arguments without having processed any + files/dirs/whatever yet, we do the current directory. */ + if (cProcessed > 0) + { + RTEXITCODE rcExit2 = rtCmdLsDisplayCollections(&Opts); + if (rcExit2 != RTEXITCODE_SUCCESS) + rcExit = rcExit2; + rtCmdLsFreeCollections(&Opts); + return rcExit; + } + ValueUnion.psz = "."; + RT_FALL_THRU(); + case VINF_GETOPT_NOT_OPTION: + { + RTEXITCODE rcExit2 = rtCmdLsProcessArgument(&Opts, ValueUnion.psz); + if (rcExit2 != RTEXITCODE_SUCCESS) + rcExit = rcExit2; + cProcessed++; + break; + } + + case 'a': + Opts.fShowHidden = true; + Opts.fShowDotAndDotDot = true; + break; + + case 'A': + Opts.fShowHidden = true; + Opts.fShowDotAndDotDot = false; + break; + + case 'b': + Opts.fEscapeNonGraphicChars = true; + break; + + case OPT_BLOCK_SIZE: + if (!ValueUnion.u32) + { + Assert(!Opts.papCollections); + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Invalid block size: %u", ValueUnion.u32); + } + Opts.cbBlock = ValueUnion.u32; + Opts.fHumanReadableSizes = false; + Opts.fSiUnits = false; + break; + + case 'c': + Opts.enmTime = RTCMDLSTIME_CTIME; + break; + + case 'C': + Opts.enmFormat = RTCMDLSFORMAT_COLS_VERTICAL; + break; + + case 'd': + Opts.fFollowDirectoryArgs = false; + Opts.fFollowSymlinkToAnyArgs = false; + Opts.fFollowSymlinkToDirArgs = false; + Opts.fRecursive = false; + break; + + case 'f': + Opts.fShowHidden = true; + Opts.fShowDotAndDotDot = true; + if (Opts.enmFormat == RTCMDLSFORMAT_LONG) + Opts.enmFormat = RTCMDLSFORMAT_COLS_VERTICAL; + Opts.enmColor = RTCMDLSCOLOR_NONE; + Opts.enmSort = RTCMDLSSORT_NONE; + break; + + case OPT_FORMAT: + if ( strcmp(ValueUnion.psz, "across") == 0 + || strcmp(ValueUnion.psz, "horizontal") == 0) + Opts.enmFormat = RTCMDLSFORMAT_COLS_HORIZONTAL; + else if (strcmp(ValueUnion.psz, "commas") == 0) + Opts.enmFormat = RTCMDLSFORMAT_COMMAS; + else if ( strcmp(ValueUnion.psz, "long") == 0 + || strcmp(ValueUnion.psz, "verbose") == 0) + Opts.enmFormat = RTCMDLSFORMAT_LONG; + else if (strcmp(ValueUnion.psz, "single-column") == 0) + Opts.enmFormat = RTCMDLSFORMAT_SINGLE; + else if (strcmp(ValueUnion.psz, "vertical") == 0) + Opts.enmFormat = RTCMDLSFORMAT_COLS_VERTICAL; + else if (strcmp(ValueUnion.psz, "machine-readable") == 0) + Opts.enmFormat = RTCMDLSFORMAT_MACHINE_READABLE; + else + { + Assert(!Opts.papCollections); + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown format: %s", ValueUnion.psz); + } + break; + + case OPT_FULL_TIME: + Opts.enmFormat = RTCMDLSFORMAT_LONG; + Opts.enmTimeStyle = RTCMDLSTIMESTYLE_FULL_ISO; + break; + + case 'g': + Opts.enmFormat = RTCMDLSFORMAT_LONG; + Opts.fShowOwner = false; + break; + + case OPT_GROUP_DIRECTORIES_FIRST: + Opts.fGroupDirectoriesFirst = true; + break; + + case 'G': + Opts.fShowGroup = false; + break; + + case 'h': + Opts.fHumanReadableSizes = true; + Opts.fSiUnits = false; + break; + + case OPT_SI: + Opts.fHumanReadableSizes = true; + Opts.fSiUnits = true; + break; + + case 'H': + Opts.fFollowSymlinkToAnyArgs = true; + Opts.fFollowSymlinkToDirArgs = true; + break; + + case OPT_DEREFERENCE_COMMAND_LINE_SYMLINK_TO_DIR: + Opts.fFollowSymlinkToAnyArgs = false; + Opts.fFollowSymlinkToDirArgs = true; + break; + + case 'i': + Opts.fShowINode = true; + break; + + case 'k': + Opts.cbBlock = _1K; + Opts.fHumanReadableSizes = false; + Opts.fSiUnits = false; + break; + + case 'l': + Opts.enmFormat = RTCMDLSFORMAT_LONG; + break; + + case 'L': + Opts.fFollowSymlinksInDirs = true; + Opts.fFollowSymlinkToAnyArgs = true; + Opts.fFollowSymlinkToDirArgs = true; + break; + + case 'm': + Opts.enmFormat = RTCMDLSFORMAT_COMMAS; + break; + + case OPT_MACHINE_READABLE: + Opts.enmFormat = RTCMDLSFORMAT_MACHINE_READABLE; + break; + + case 'n': + Opts.fNumericalIds = true; + break; + + case 'N': + Opts.fEscapeNonGraphicChars = false; + Opts.fEscapeControlChars = false; + Opts.fHideControlChars = false; + break; + + case 'o': + Opts.enmFormat = RTCMDLSFORMAT_LONG; + Opts.fShowGroup = false; + break; + + case 'q': + Opts.fHideControlChars = true; + break; + + case OPT_SHOW_CONTROL_CHARS: + Opts.fHideControlChars = true; + break; + + case 'r': + Opts.fReverseSort = true; + break; + + case 'R': + Opts.fRecursive = true; + break; + + case 's': + Opts.fShowAllocatedSize = true; + break; + + case 'S': + Opts.enmSort = RTCMDLSSORT_SIZE; + break; + + case OPT_SORT: + if (strcmp(ValueUnion.psz, "none") == 0) + Opts.enmSort = RTCMDLSSORT_NONE; + else if (strcmp(ValueUnion.psz, "extension") == 0) + Opts.enmSort = RTCMDLSSORT_EXTENSION; + else if (strcmp(ValueUnion.psz, "size") == 0) + Opts.enmSort = RTCMDLSSORT_SIZE; + else if (strcmp(ValueUnion.psz, "time") == 0) + Opts.enmSort = RTCMDLSSORT_TIME; + else if (strcmp(ValueUnion.psz, "version") == 0) + Opts.enmSort = RTCMDLSSORT_VERSION; + else + { + Assert(!Opts.papCollections); + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown sort by: %s", ValueUnion.psz); + } + break; + + case OPT_TIME: + if ( strcmp(ValueUnion.psz, "btime") == 0 + || strcmp(ValueUnion.psz, "birth") == 0) + Opts.enmTime = RTCMDLSTIME_BTIME; + else if ( strcmp(ValueUnion.psz, "ctime") == 0 + || strcmp(ValueUnion.psz, "status") == 0) + Opts.enmTime = RTCMDLSTIME_CTIME; + else if ( strcmp(ValueUnion.psz, "mtime") == 0 + || strcmp(ValueUnion.psz, "write") == 0 + || strcmp(ValueUnion.psz, "modify") == 0) + Opts.enmTime = RTCMDLSTIME_MTIME; + else if ( strcmp(ValueUnion.psz, "atime") == 0 + || strcmp(ValueUnion.psz, "access") == 0 + || strcmp(ValueUnion.psz, "use") == 0) + Opts.enmTime = RTCMDLSTIME_ATIME; + else + { + Assert(!Opts.papCollections); + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown time attribute: %s", ValueUnion.psz); + } + break; + + case OPT_TIME_STYLE: + if (strcmp(ValueUnion.psz, "full-iso") == 0) + Opts.enmTimeStyle = RTCMDLSTIMESTYLE_FULL_ISO; + else if (strcmp(ValueUnion.psz, "long-iso") == 0) + Opts.enmTimeStyle = RTCMDLSTIMESTYLE_LONG_ISO; + else if (strcmp(ValueUnion.psz, "iso") == 0) + Opts.enmTimeStyle = RTCMDLSTIMESTYLE_ISO; + else if (strcmp(ValueUnion.psz, "locale") == 0) + Opts.enmTimeStyle = RTCMDLSTIMESTYLE_LOCALE; + else if (*ValueUnion.psz == '+') + { + Opts.enmTimeStyle = RTCMDLSTIMESTYLE_CUSTOM; + Opts.pszTimeCustom = ValueUnion.psz; + } + else + { + Assert(!Opts.papCollections); + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown sort by: %s", ValueUnion.psz); + } + break; + + case 't': + Opts.enmSort = RTCMDLSSORT_TIME; + break; + + case 'T': + Opts.cchTab = ValueUnion.u8; + break; + + case 'u': + Opts.enmTime = RTCMDLSTIME_ATIME; + break; + + case 'U': + Opts.enmSort = RTCMDLSSORT_NONE; + break; + + case 'v': + Opts.enmSort = RTCMDLSSORT_VERSION; + break; + + case 'w': + Opts.cchWidth = ValueUnion.u32; + break; + + case 'x': + Opts.enmFormat = RTCMDLSFORMAT_COLS_HORIZONTAL; + break; + + case 'X': + Opts.enmSort = RTCMDLSSORT_EXTENSION; + break; + + case '1': + Opts.enmFormat = RTCMDLSFORMAT_SINGLE; + break; + + case '?': + RTPrintf("Usage: to be written\nOpts.on dump:\n"); + for (unsigned i = 0; i < RT_ELEMENTS(s_aOptions); i++) + RTPrintf(" -%c,%s\n", s_aOptions[i].iShort, s_aOptions[i].pszLong); + Assert(!Opts.papCollections); + return RTEXITCODE_SUCCESS; + + case 'V': + RTPrintf("%sr%d\n", RTBldCfgVersion(), RTBldCfgRevision()); + Assert(!Opts.papCollections); + return RTEXITCODE_SUCCESS; + + default: + Assert(!Opts.papCollections); + return RTGetOptPrintError(chOpt, &ValueUnion); + } + } +} + |