2019-09-21 09:39:01 +00:00
|
|
|
/*
|
2021-12-29 03:01:40 +00:00
|
|
|
* PROJECT: ReactOS Attrib Command
|
|
|
|
* LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
|
|
|
|
* PURPOSE: Displays or changes file attributes recursively.
|
|
|
|
* COPYRIGHT: Copyright 1998-2019 Eric Kohl <eric.kohl@reactos.org>
|
|
|
|
* Copyright 2021 Doug Lyons <douglyons@douglyons.com>
|
|
|
|
* Copyright 2021-2023 Hermès Bélusca-Maïto <hermes.belusca-maito@reactos.org>
|
2019-09-21 09:39:01 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
|
|
|
#include <windef.h>
|
|
|
|
#include <winbase.h>
|
|
|
|
#include <wincon.h>
|
|
|
|
#include <winuser.h>
|
2021-12-29 03:01:40 +00:00
|
|
|
#include <strsafe.h>
|
2019-09-21 09:39:01 +00:00
|
|
|
|
|
|
|
#include <conutils.h>
|
|
|
|
|
|
|
|
#include "resource.h"
|
|
|
|
|
2021-12-29 03:01:40 +00:00
|
|
|
/* Enable to support extended attributes.
|
|
|
|
* See https://ss64.com/nt/attrib.html for an exhaustive list. */
|
|
|
|
// TODO: If you enable this, translations need to be updated as well!
|
|
|
|
//#define EXTENDED_ATTRIBUTES
|
|
|
|
|
|
|
|
#define ALL_FILES_PATTERN L"*.*" // It may also be possible to use L"*" (shorter)
|
|
|
|
|
2019-09-21 09:39:01 +00:00
|
|
|
CON_SCREEN StdOutScreen = INIT_CON_SCREEN(StdOut);
|
|
|
|
|
2021-12-29 03:01:40 +00:00
|
|
|
static VOID
|
2019-09-21 09:39:01 +00:00
|
|
|
ErrorMessage(
|
2023-05-18 10:12:39 +00:00
|
|
|
_In_ DWORD dwErrorCode,
|
|
|
|
_In_opt_ PCWSTR pszMsg,
|
2019-09-21 09:39:01 +00:00
|
|
|
...)
|
|
|
|
{
|
2023-05-18 10:12:39 +00:00
|
|
|
INT Len;
|
2019-09-21 09:39:01 +00:00
|
|
|
va_list arg_ptr;
|
|
|
|
|
|
|
|
if (dwErrorCode == ERROR_SUCCESS)
|
|
|
|
return;
|
|
|
|
|
2023-05-18 10:12:39 +00:00
|
|
|
va_start(arg_ptr, pszMsg);
|
|
|
|
Len = ConMsgPrintfV(StdErr,
|
|
|
|
FORMAT_MESSAGE_FROM_SYSTEM,
|
|
|
|
NULL,
|
|
|
|
dwErrorCode,
|
|
|
|
LANG_USER_DEFAULT,
|
|
|
|
&arg_ptr);
|
|
|
|
va_end(arg_ptr);
|
2019-09-21 09:39:01 +00:00
|
|
|
|
|
|
|
/* Fall back just in case the error is not defined */
|
2023-05-18 10:12:39 +00:00
|
|
|
if (Len <= 0)
|
|
|
|
ConResPrintf(StdErr, STRING_CONSOLE_ERROR, dwErrorCode);
|
|
|
|
|
|
|
|
/* Display the extra optional message if necessary */
|
|
|
|
if (pszMsg)
|
|
|
|
ConPrintf(StdErr, L" %s\n", pszMsg);
|
2019-09-21 09:39:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-12-29 03:01:40 +00:00
|
|
|
/**
|
|
|
|
* @brief Displays attributes for the given file.
|
|
|
|
* @return Always TRUE (success).
|
|
|
|
**/
|
|
|
|
static BOOL
|
|
|
|
PrintAttributes(
|
|
|
|
_In_ PWIN32_FIND_DATAW pFindData,
|
|
|
|
_In_ PCWSTR pszFullName,
|
|
|
|
_Inout_opt_ PVOID Context)
|
|
|
|
{
|
|
|
|
DWORD dwAttributes = pFindData->dwFileAttributes;
|
|
|
|
|
|
|
|
UNREFERENCED_PARAMETER(Context);
|
|
|
|
|
|
|
|
ConPrintf(StdOut,
|
|
|
|
#ifdef EXTENDED_ATTRIBUTES
|
|
|
|
L"%c %c%c%c %c %s\n",
|
|
|
|
#else
|
|
|
|
L"%c %c%c%c %s\n",
|
|
|
|
#endif
|
|
|
|
(dwAttributes & FILE_ATTRIBUTE_ARCHIVE) ? L'A' : L' ',
|
|
|
|
(dwAttributes & FILE_ATTRIBUTE_SYSTEM) ? L'S' : L' ',
|
|
|
|
(dwAttributes & FILE_ATTRIBUTE_HIDDEN) ? L'H' : L' ',
|
|
|
|
(dwAttributes & FILE_ATTRIBUTE_READONLY) ? L'R' : L' ',
|
|
|
|
#ifdef EXTENDED_ATTRIBUTES
|
|
|
|
(dwAttributes & FILE_ATTRIBUTE_NOT_CONTENT_INDEXED) ? L'I' : L' ',
|
|
|
|
#endif
|
|
|
|
pszFullName);
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
2019-09-21 09:39:01 +00:00
|
|
|
|
2021-12-29 03:01:40 +00:00
|
|
|
typedef struct _ATTRIBS_MASKS
|
|
|
|
{
|
|
|
|
DWORD dwMask;
|
|
|
|
DWORD dwAttrib;
|
|
|
|
} ATTRIBS_MASKS, *PATTRIBS_MASKS;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief Changes attributes for the given file.
|
|
|
|
* @return TRUE if anything changed, FALSE otherwise.
|
|
|
|
**/
|
|
|
|
static BOOL
|
|
|
|
ChangeAttributes(
|
|
|
|
_In_ PWIN32_FIND_DATAW pFindData,
|
|
|
|
_In_ PCWSTR pszFullName,
|
|
|
|
_Inout_opt_ PVOID Context)
|
|
|
|
{
|
|
|
|
PATTRIBS_MASKS AttribsMasks = (PATTRIBS_MASKS)Context;
|
|
|
|
DWORD dwAttributes;
|
2019-09-21 09:39:01 +00:00
|
|
|
|
2021-12-29 03:01:40 +00:00
|
|
|
dwAttributes = ((pFindData->dwFileAttributes & ~AttribsMasks->dwMask) | AttribsMasks->dwAttrib);
|
|
|
|
return SetFileAttributesW(pszFullName, dwAttributes);
|
|
|
|
}
|
2019-09-21 09:39:01 +00:00
|
|
|
|
|
|
|
|
2021-12-29 03:01:40 +00:00
|
|
|
#define ENUM_RECURSE 0x01
|
|
|
|
#define ENUM_DIRECTORIES 0x02
|
2019-12-08 15:24:28 +00:00
|
|
|
|
2021-12-29 03:01:40 +00:00
|
|
|
typedef BOOL
|
|
|
|
(*PENUMFILES_CALLBACK)(
|
|
|
|
_In_ PWIN32_FIND_DATAW pFindData,
|
|
|
|
_In_ PCWSTR pszFullName,
|
|
|
|
_Inout_opt_ PVOID Context);
|
2019-12-08 15:24:28 +00:00
|
|
|
|
2021-12-29 03:01:40 +00:00
|
|
|
typedef struct _ENUMFILES_CTX
|
|
|
|
{
|
|
|
|
/* Fixed data */
|
|
|
|
_In_ PCWSTR FileName;
|
|
|
|
_In_ DWORD Flags;
|
2019-12-08 15:24:28 +00:00
|
|
|
|
2021-12-29 03:01:40 +00:00
|
|
|
/* Callback invoked on each enumerated file/directory */
|
|
|
|
_In_ PENUMFILES_CALLBACK Callback;
|
|
|
|
_In_ PVOID Context;
|
2019-09-21 09:39:01 +00:00
|
|
|
|
2021-12-29 03:01:40 +00:00
|
|
|
/* Dynamic data */
|
|
|
|
WIN32_FIND_DATAW findData;
|
|
|
|
ULONG uReparseLevel;
|
2019-09-21 09:39:01 +00:00
|
|
|
|
2021-12-29 03:01:40 +00:00
|
|
|
/* The full path buffer the function will act recursively */
|
|
|
|
// PWSTR FullPath; // Use a relocated buffer once long paths become supported!
|
|
|
|
size_t cchBuffer; // Buffer size
|
|
|
|
WCHAR FullPathBuffer[MAX_PATH + _countof("\\" ALL_FILES_PATTERN)];
|
2019-09-21 09:39:01 +00:00
|
|
|
|
2021-12-29 03:01:40 +00:00
|
|
|
} ENUMFILES_CTX, *PENUMFILES_CTX;
|
2019-09-21 09:39:01 +00:00
|
|
|
|
2021-12-29 03:01:40 +00:00
|
|
|
/* Returns TRUE if anything is done, FALSE otherwise */
|
|
|
|
static BOOL
|
|
|
|
EnumFilesWorker(
|
|
|
|
_Inout_ PENUMFILES_CTX EnumCtx,
|
|
|
|
_Inout_ off_t offFilePart) // Offset to the file name inside FullPathBuffer
|
2019-09-21 09:39:01 +00:00
|
|
|
{
|
2021-12-29 03:01:40 +00:00
|
|
|
BOOL bFound = FALSE;
|
|
|
|
HRESULT hRes;
|
2019-09-21 09:39:01 +00:00
|
|
|
HANDLE hFind;
|
2021-12-29 03:01:40 +00:00
|
|
|
PWSTR findFileName = EnumCtx->findData.cFileName;
|
|
|
|
PWSTR pFilePart = EnumCtx->FullPathBuffer + offFilePart;
|
|
|
|
size_t cchRemaining = EnumCtx->cchBuffer - offFilePart;
|
|
|
|
|
|
|
|
/* Recurse over all subdirectories */
|
|
|
|
if (EnumCtx->Flags & ENUM_RECURSE)
|
2019-12-08 15:24:28 +00:00
|
|
|
{
|
2021-12-29 03:01:40 +00:00
|
|
|
/* Append '*.*' */
|
|
|
|
hRes = StringCchCopyW(pFilePart, cchRemaining, ALL_FILES_PATTERN);
|
|
|
|
if (hRes != S_OK)
|
|
|
|
{
|
|
|
|
if (hRes == STRSAFE_E_INSUFFICIENT_BUFFER)
|
|
|
|
{
|
|
|
|
// TODO: If this fails, try to reallocate EnumCtx->FullPathBuffer by
|
|
|
|
// increasing its size by _countof(EnumCtx->findData.cFileName) + 1
|
|
|
|
// to satisfy this copy, as well as the one made in the loop below.
|
|
|
|
}
|
|
|
|
// else
|
|
|
|
ConPrintf(StdErr, L"Directory level too deep: %s\n", EnumCtx->FullPathBuffer);
|
|
|
|
return FALSE;
|
|
|
|
}
|
2019-12-08 15:24:28 +00:00
|
|
|
|
2021-12-29 03:01:40 +00:00
|
|
|
hFind = FindFirstFileW(EnumCtx->FullPathBuffer, &EnumCtx->findData);
|
2019-12-08 15:24:28 +00:00
|
|
|
if (hFind == INVALID_HANDLE_VALUE)
|
|
|
|
{
|
2021-12-29 03:01:40 +00:00
|
|
|
DWORD Error = GetLastError();
|
|
|
|
if ((Error != ERROR_DIRECTORY) &&
|
|
|
|
(Error != ERROR_SHARING_VIOLATION) &&
|
|
|
|
(Error != ERROR_FILE_NOT_FOUND))
|
2021-12-29 02:13:55 +00:00
|
|
|
{
|
2021-12-29 03:01:40 +00:00
|
|
|
ErrorMessage(Error, EnumCtx->FullPathBuffer);
|
2021-12-29 02:13:55 +00:00
|
|
|
}
|
2019-12-08 15:24:28 +00:00
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
do
|
|
|
|
{
|
2021-12-29 03:01:40 +00:00
|
|
|
BOOL bIsReparse;
|
|
|
|
size_t offNewFilePart;
|
|
|
|
|
|
|
|
if (!(EnumCtx->findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
|
2019-12-08 15:24:28 +00:00
|
|
|
continue;
|
|
|
|
|
2021-12-29 03:01:40 +00:00
|
|
|
if (!wcscmp(findFileName, L".") || !wcscmp(findFileName, L".."))
|
2019-12-08 15:24:28 +00:00
|
|
|
continue;
|
2021-12-29 03:01:40 +00:00
|
|
|
|
|
|
|
/* Allow at most 2 levels of reparse points / symbolic links */
|
|
|
|
bIsReparse = !!(EnumCtx->findData.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT);
|
|
|
|
if (bIsReparse)
|
|
|
|
{
|
|
|
|
if (EnumCtx->uReparseLevel < 2)
|
|
|
|
EnumCtx->uReparseLevel++;
|
|
|
|
else
|
|
|
|
continue;
|
2021-12-29 02:13:55 +00:00
|
|
|
}
|
2019-12-08 15:24:28 +00:00
|
|
|
|
2021-12-29 03:01:40 +00:00
|
|
|
hRes = StringCchPrintfExW(pFilePart, cchRemaining,
|
|
|
|
NULL, &offNewFilePart, 0,
|
|
|
|
L"%s\\", findFileName);
|
|
|
|
/* Offset to the new file name part */
|
|
|
|
offNewFilePart = EnumCtx->cchBuffer - offNewFilePart;
|
|
|
|
|
|
|
|
bFound |= EnumFilesWorker(EnumCtx, offNewFilePart);
|
|
|
|
|
|
|
|
/* Recalculate the file part pointer and the number of characters
|
|
|
|
* remaining: the buffer may have been enlarged and relocated. */
|
|
|
|
pFilePart = EnumCtx->FullPathBuffer + offFilePart;
|
|
|
|
cchRemaining = EnumCtx->cchBuffer - offFilePart;
|
|
|
|
|
|
|
|
/* If we went through a reparse point / symbolic link, decrease level */
|
|
|
|
if (bIsReparse)
|
|
|
|
EnumCtx->uReparseLevel--;
|
2019-12-08 15:24:28 +00:00
|
|
|
}
|
2021-12-29 03:01:40 +00:00
|
|
|
while (FindNextFileW(hFind, &EnumCtx->findData));
|
2019-12-08 15:24:28 +00:00
|
|
|
FindClose(hFind);
|
|
|
|
}
|
|
|
|
|
2021-12-29 03:01:40 +00:00
|
|
|
/* Append the file name pattern to search for */
|
|
|
|
hRes = StringCchCopyW(pFilePart, cchRemaining, EnumCtx->FileName);
|
2019-09-21 09:39:01 +00:00
|
|
|
|
2021-12-29 03:01:40 +00:00
|
|
|
/* Search in the current directory */
|
|
|
|
hFind = FindFirstFileW(EnumCtx->FullPathBuffer, &EnumCtx->findData);
|
2019-11-29 11:03:19 +00:00
|
|
|
if (hFind == INVALID_HANDLE_VALUE)
|
2019-12-08 15:24:28 +00:00
|
|
|
return bFound;
|
|
|
|
|
|
|
|
do
|
2019-11-29 11:03:19 +00:00
|
|
|
{
|
2021-12-29 03:01:40 +00:00
|
|
|
BOOL bIsDir = !!(EnumCtx->findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
|
|
|
|
BOOL bExactMatch = (wcsicmp(findFileName, EnumCtx->FileName) == 0);
|
2019-09-21 09:39:01 +00:00
|
|
|
|
2021-12-29 03:01:40 +00:00
|
|
|
if (bIsDir && !(EnumCtx->Flags & ENUM_DIRECTORIES) && !bExactMatch)
|
2019-12-08 15:24:28 +00:00
|
|
|
continue;
|
2019-09-21 09:39:01 +00:00
|
|
|
|
2021-12-29 03:01:40 +00:00
|
|
|
if (!wcscmp(findFileName, L".") || !wcscmp(findFileName, L".."))
|
2019-12-08 15:24:28 +00:00
|
|
|
continue;
|
|
|
|
|
2021-12-29 03:01:40 +00:00
|
|
|
/* If we recursively enumerate files excluding directories,
|
|
|
|
* exclude any directory from the enumeration. */
|
|
|
|
if (bIsDir && !(EnumCtx->Flags & ENUM_DIRECTORIES) && (EnumCtx->Flags & ENUM_RECURSE))
|
2019-12-08 15:24:28 +00:00
|
|
|
continue;
|
|
|
|
|
2021-12-29 03:01:40 +00:00
|
|
|
StringCchCopyW(pFilePart, cchRemaining, findFileName);
|
|
|
|
/* bFound = */ EnumCtx->Callback(&EnumCtx->findData, EnumCtx->FullPathBuffer, EnumCtx->Context);
|
2019-12-08 15:24:28 +00:00
|
|
|
bFound = TRUE;
|
2019-09-21 09:39:01 +00:00
|
|
|
}
|
2021-12-29 03:01:40 +00:00
|
|
|
while (FindNextFileW(hFind, &EnumCtx->findData));
|
2019-12-08 15:24:28 +00:00
|
|
|
FindClose(hFind);
|
2019-09-21 09:39:01 +00:00
|
|
|
|
2019-12-08 15:24:28 +00:00
|
|
|
return bFound;
|
2019-09-21 09:39:01 +00:00
|
|
|
}
|
|
|
|
|
2021-12-29 03:01:40 +00:00
|
|
|
static BOOL
|
|
|
|
AttribEnumFiles(
|
|
|
|
_In_ PCWSTR pszPath,
|
|
|
|
_In_ PCWSTR pszFile,
|
|
|
|
_In_ DWORD fFlags,
|
|
|
|
_In_ PATTRIBS_MASKS AttribsMasks)
|
|
|
|
{
|
|
|
|
ENUMFILES_CTX EnumContext = {0};
|
|
|
|
size_t offFilePart;
|
|
|
|
HRESULT hRes;
|
|
|
|
|
|
|
|
EnumContext.FileName = pszFile;
|
|
|
|
EnumContext.Flags = fFlags;
|
|
|
|
EnumContext.Callback = (AttribsMasks->dwMask == 0 ? PrintAttributes : ChangeAttributes);
|
|
|
|
EnumContext.Context = (AttribsMasks->dwMask == 0 ? NULL : AttribsMasks);
|
|
|
|
|
|
|
|
/* Prepare the full file path buffer */
|
|
|
|
EnumContext.cchBuffer = _countof(EnumContext.FullPathBuffer);
|
|
|
|
hRes = StringCchCopyExW(EnumContext.FullPathBuffer,
|
|
|
|
EnumContext.cchBuffer,
|
|
|
|
pszPath,
|
|
|
|
NULL,
|
|
|
|
&offFilePart,
|
|
|
|
0);
|
|
|
|
if (hRes != S_OK)
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
/* Offset to the file name part */
|
|
|
|
offFilePart = EnumContext.cchBuffer - offFilePart;
|
|
|
|
if (EnumContext.FullPathBuffer[offFilePart - 1] != L'\\')
|
|
|
|
{
|
|
|
|
EnumContext.FullPathBuffer[offFilePart] = L'\\';
|
|
|
|
EnumContext.FullPathBuffer[offFilePart + 1] = UNICODE_NULL;
|
|
|
|
offFilePart++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return EnumFilesWorker(&EnumContext, offFilePart);
|
|
|
|
}
|
|
|
|
|
2019-09-21 09:39:01 +00:00
|
|
|
int wmain(int argc, WCHAR *argv[])
|
|
|
|
{
|
2023-05-18 10:43:23 +00:00
|
|
|
INT i;
|
2021-12-29 03:01:40 +00:00
|
|
|
DWORD dwEnumFlags = 0;
|
|
|
|
ATTRIBS_MASKS AttribsMasks = {0};
|
2023-05-18 10:43:23 +00:00
|
|
|
BOOL bFound = FALSE;
|
|
|
|
PWSTR pszFileName;
|
|
|
|
WCHAR szFilePath[MAX_PATH + 2] = L""; // + 2 to reserve an extra path separator and a NULL-terminator.
|
2019-09-21 09:39:01 +00:00
|
|
|
|
|
|
|
/* Initialize the Console Standard Streams */
|
|
|
|
ConInitStdStreams();
|
|
|
|
|
2023-05-18 10:43:23 +00:00
|
|
|
/* Check for options and file specifications */
|
2019-09-21 09:39:01 +00:00
|
|
|
for (i = 1; i < argc; i++)
|
|
|
|
{
|
2023-05-18 10:43:23 +00:00
|
|
|
if (*argv[i] == L'/')
|
2019-09-21 09:39:01 +00:00
|
|
|
{
|
2023-05-18 10:43:23 +00:00
|
|
|
/* Print help and bail out if needed */
|
|
|
|
if (wcscmp(argv[i], L"/?") == 0)
|
2019-09-21 09:39:01 +00:00
|
|
|
{
|
2023-05-18 10:43:23 +00:00
|
|
|
ConResPuts(StdOut, STRING_ATTRIB_HELP);
|
|
|
|
return 0;
|
2019-09-21 09:39:01 +00:00
|
|
|
}
|
2023-05-18 10:43:23 +00:00
|
|
|
else
|
|
|
|
/* Retrieve the enumeration modes */
|
|
|
|
if (wcsicmp(argv[i], L"/s") == 0)
|
2021-12-29 03:01:40 +00:00
|
|
|
dwEnumFlags |= ENUM_RECURSE;
|
2023-05-18 10:43:23 +00:00
|
|
|
else if (wcsicmp(argv[i], L"/d") == 0)
|
2021-12-29 03:01:40 +00:00
|
|
|
dwEnumFlags |= ENUM_DIRECTORIES;
|
2023-05-18 10:43:23 +00:00
|
|
|
else
|
2019-09-21 09:39:01 +00:00
|
|
|
{
|
2023-05-18 10:43:23 +00:00
|
|
|
/* Unknown option */
|
|
|
|
ConResPrintf(StdErr, STRING_ERROR_INVALID_PARAM_FORMAT, argv[i]);
|
|
|
|
return -1;
|
2019-09-21 09:39:01 +00:00
|
|
|
}
|
|
|
|
}
|
2023-05-18 10:43:23 +00:00
|
|
|
else
|
|
|
|
/* Build attributes and mask */
|
|
|
|
if ((*argv[i] == L'+') || (*argv[i] == L'-'))
|
2019-09-21 09:39:01 +00:00
|
|
|
{
|
2023-05-18 10:43:23 +00:00
|
|
|
BOOL bAdd = (*argv[i] == L'+');
|
|
|
|
|
2019-09-21 09:39:01 +00:00
|
|
|
if (wcslen(argv[i]) != 2)
|
|
|
|
{
|
2023-05-18 10:43:23 +00:00
|
|
|
ConResPrintf(StdErr, STRING_ERROR_INVALID_PARAM_FORMAT, argv[i]);
|
2019-09-21 09:39:01 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (towupper(argv[i][1]))
|
|
|
|
{
|
|
|
|
case L'A':
|
2021-12-29 03:01:40 +00:00
|
|
|
AttribsMasks.dwMask |= FILE_ATTRIBUTE_ARCHIVE;
|
2023-05-18 10:43:23 +00:00
|
|
|
if (bAdd)
|
2021-12-29 03:01:40 +00:00
|
|
|
AttribsMasks.dwAttrib |= FILE_ATTRIBUTE_ARCHIVE;
|
2023-05-18 10:43:23 +00:00
|
|
|
else
|
2021-12-29 03:01:40 +00:00
|
|
|
AttribsMasks.dwAttrib &= ~FILE_ATTRIBUTE_ARCHIVE;
|
2019-09-21 09:39:01 +00:00
|
|
|
break;
|
|
|
|
|
2023-05-18 10:43:23 +00:00
|
|
|
case L'S':
|
2021-12-29 03:01:40 +00:00
|
|
|
AttribsMasks.dwMask |= FILE_ATTRIBUTE_SYSTEM;
|
2023-05-18 10:43:23 +00:00
|
|
|
if (bAdd)
|
2021-12-29 03:01:40 +00:00
|
|
|
AttribsMasks.dwAttrib |= FILE_ATTRIBUTE_SYSTEM;
|
2023-05-18 10:43:23 +00:00
|
|
|
else
|
2021-12-29 03:01:40 +00:00
|
|
|
AttribsMasks.dwAttrib &= ~FILE_ATTRIBUTE_SYSTEM;
|
2019-09-21 09:39:01 +00:00
|
|
|
break;
|
|
|
|
|
2023-05-18 10:43:23 +00:00
|
|
|
case L'H':
|
2021-12-29 03:01:40 +00:00
|
|
|
AttribsMasks.dwMask |= FILE_ATTRIBUTE_HIDDEN;
|
2023-05-18 10:43:23 +00:00
|
|
|
if (bAdd)
|
2021-12-29 03:01:40 +00:00
|
|
|
AttribsMasks.dwAttrib |= FILE_ATTRIBUTE_HIDDEN;
|
2023-05-18 10:43:23 +00:00
|
|
|
else
|
2021-12-29 03:01:40 +00:00
|
|
|
AttribsMasks.dwAttrib &= ~FILE_ATTRIBUTE_HIDDEN;
|
2019-09-21 09:39:01 +00:00
|
|
|
break;
|
|
|
|
|
2023-05-18 10:43:23 +00:00
|
|
|
case L'R':
|
2021-12-29 03:01:40 +00:00
|
|
|
AttribsMasks.dwMask |= FILE_ATTRIBUTE_READONLY;
|
2023-05-18 10:43:23 +00:00
|
|
|
if (bAdd)
|
2021-12-29 03:01:40 +00:00
|
|
|
AttribsMasks.dwAttrib |= FILE_ATTRIBUTE_READONLY;
|
2023-05-18 10:43:23 +00:00
|
|
|
else
|
2021-12-29 03:01:40 +00:00
|
|
|
AttribsMasks.dwAttrib &= ~FILE_ATTRIBUTE_READONLY;
|
2019-09-21 09:39:01 +00:00
|
|
|
break;
|
|
|
|
|
2021-12-29 03:01:40 +00:00
|
|
|
#ifdef EXTENDED_ATTRIBUTES
|
|
|
|
case L'I':
|
|
|
|
AttribsMasks.dwMask |= FILE_ATTRIBUTE_NOT_CONTENT_INDEXED;
|
|
|
|
if (bAdd)
|
|
|
|
AttribsMasks.dwAttrib |= FILE_ATTRIBUTE_NOT_CONTENT_INDEXED;
|
|
|
|
else
|
|
|
|
AttribsMasks.dwAttrib &= ~FILE_ATTRIBUTE_NOT_CONTENT_INDEXED;
|
|
|
|
break;
|
|
|
|
#endif
|
|
|
|
|
2019-09-21 09:39:01 +00:00
|
|
|
default:
|
2023-05-18 10:43:23 +00:00
|
|
|
ConResPrintf(StdErr, STRING_ERROR_INVALID_PARAM_FORMAT, argv[i]);
|
2019-09-21 09:39:01 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
2023-05-18 10:43:23 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
/* At least one file specification found */
|
|
|
|
bFound = TRUE;
|
|
|
|
}
|
2019-09-21 09:39:01 +00:00
|
|
|
}
|
|
|
|
|
2023-05-18 10:43:23 +00:00
|
|
|
/* If no file specification was found, operate on all files of the current directory */
|
|
|
|
if (!bFound)
|
2019-09-21 09:39:01 +00:00
|
|
|
{
|
2021-12-29 03:01:40 +00:00
|
|
|
GetCurrentDirectoryW(_countof(szFilePath) - 2, szFilePath);
|
|
|
|
pszFileName = ALL_FILES_PATTERN;
|
2023-05-18 10:43:23 +00:00
|
|
|
|
2021-12-29 03:01:40 +00:00
|
|
|
bFound = AttribEnumFiles(szFilePath, pszFileName, dwEnumFlags, &AttribsMasks);
|
2023-05-18 10:43:23 +00:00
|
|
|
if (!bFound)
|
|
|
|
ConResPrintf(StdOut, STRING_FILE_NOT_FOUND, pszFileName);
|
|
|
|
|
2019-09-21 09:39:01 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2023-05-18 10:43:23 +00:00
|
|
|
/* Operate on each file specification */
|
2019-09-21 09:39:01 +00:00
|
|
|
for (i = 1; i < argc; i++)
|
|
|
|
{
|
2023-05-18 10:43:23 +00:00
|
|
|
/* Skip options */
|
|
|
|
if (*argv[i] == L'/' || *argv[i] == L'+' || *argv[i] == L'-')
|
2019-11-29 11:03:19 +00:00
|
|
|
continue;
|
2019-09-21 09:39:01 +00:00
|
|
|
|
2023-05-18 10:43:23 +00:00
|
|
|
GetFullPathNameW(argv[i], _countof(szFilePath) - 2, szFilePath, &pszFileName);
|
|
|
|
if (pszFileName)
|
2019-11-29 11:03:19 +00:00
|
|
|
{
|
2023-05-18 10:43:23 +00:00
|
|
|
/* Move the file part so as to separate and NULL-terminate the directory */
|
|
|
|
MoveMemory(pszFileName + 1, pszFileName,
|
|
|
|
sizeof(szFilePath) - (pszFileName -szFilePath + 1) * sizeof(*szFilePath));
|
|
|
|
*pszFileName++ = UNICODE_NULL;
|
2019-11-29 11:03:19 +00:00
|
|
|
}
|
2023-05-18 10:43:23 +00:00
|
|
|
else
|
2019-11-29 11:03:19 +00:00
|
|
|
{
|
2023-05-18 10:43:23 +00:00
|
|
|
pszFileName = L"";
|
2019-09-21 09:39:01 +00:00
|
|
|
}
|
|
|
|
|
2021-12-29 03:01:40 +00:00
|
|
|
bFound = AttribEnumFiles(szFilePath, pszFileName, dwEnumFlags, &AttribsMasks);
|
2023-05-18 10:43:23 +00:00
|
|
|
if (!bFound)
|
|
|
|
ConResPrintf(StdOut, STRING_FILE_NOT_FOUND, argv[i]);
|
2019-12-11 02:15:54 +00:00
|
|
|
}
|
2019-12-08 15:24:28 +00:00
|
|
|
|
2019-09-21 09:39:01 +00:00
|
|
|
return 0;
|
|
|
|
}
|