893 lines
21 KiB
C
893 lines
21 KiB
C
/**
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed with this
|
|
* work for additional information regarding copyright ownership. The ASF
|
|
* licenses this file to you under the Apache License, Version 2.0 (the
|
|
* "License"); you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
* License for the specific language governing permissions and limitations under
|
|
* the License.
|
|
*/
|
|
|
|
#include "winutils.h"
|
|
#include <errno.h>
|
|
|
|
enum CHMOD_WHO
|
|
{
|
|
CHMOD_WHO_NONE = 0,
|
|
CHMOD_WHO_OTHER = 07,
|
|
CHMOD_WHO_GROUP = 070,
|
|
CHMOD_WHO_USER = 0700,
|
|
CHMOD_WHO_ALL = CHMOD_WHO_OTHER | CHMOD_WHO_GROUP | CHMOD_WHO_USER
|
|
};
|
|
|
|
enum CHMOD_OP
|
|
{
|
|
CHMOD_OP_INVALID,
|
|
CHMOD_OP_PLUS,
|
|
CHMOD_OP_MINUS,
|
|
CHMOD_OP_EQUAL,
|
|
};
|
|
|
|
enum CHMOD_PERM
|
|
{
|
|
CHMOD_PERM_NA = 00,
|
|
CHMOD_PERM_R = 01,
|
|
CHMOD_PERM_W = 02,
|
|
CHMOD_PERM_X = 04,
|
|
CHMOD_PERM_LX = 010,
|
|
};
|
|
|
|
/*
|
|
* We use the following struct to build a linked list of mode change actions.
|
|
* The mode is described by the following grammar:
|
|
* mode ::= clause [, clause ...]
|
|
* clause ::= [who ...] [action ...]
|
|
* action ::= op [perm ...] | op [ref]
|
|
* who ::= a | u | g | o
|
|
* op ::= + | - | =
|
|
* perm ::= r | w | x | X
|
|
* ref ::= u | g | o
|
|
*/
|
|
typedef struct _MODE_CHANGE_ACTION
|
|
{
|
|
USHORT who;
|
|
USHORT op;
|
|
USHORT perm;
|
|
USHORT ref;
|
|
struct _MODE_CHANGE_ACTION *next_action;
|
|
} MODE_CHANGE_ACTION, *PMODE_CHANGE_ACTION;
|
|
|
|
const MODE_CHANGE_ACTION INIT_MODE_CHANGE_ACTION = {
|
|
CHMOD_WHO_NONE, CHMOD_OP_INVALID, CHMOD_PERM_NA, CHMOD_WHO_NONE, NULL
|
|
};
|
|
|
|
static BOOL ParseOctalMode(LPCWSTR tsMask, INT *uMask);
|
|
|
|
static BOOL ParseMode(LPCWSTR modeString, PMODE_CHANGE_ACTION *actions);
|
|
|
|
static BOOL FreeActions(PMODE_CHANGE_ACTION actions);
|
|
|
|
static BOOL ParseCommandLineArguments(
|
|
__in int argc,
|
|
__in_ecount(argc) wchar_t *argv[],
|
|
__out BOOL *rec,
|
|
__out_opt INT *mask,
|
|
__out_opt PMODE_CHANGE_ACTION *actions,
|
|
__out LPCWSTR *path);
|
|
|
|
static BOOL ChangeFileModeByActions(__in LPCWSTR path,
|
|
MODE_CHANGE_ACTION const *actions);
|
|
|
|
static BOOL ChangeFileMode(__in LPCWSTR path, __in_opt INT mode,
|
|
__in_opt MODE_CHANGE_ACTION const *actions);
|
|
|
|
static BOOL ChangeFileModeRecursively(__in LPCWSTR path, __in_opt INT mode,
|
|
__in_opt MODE_CHANGE_ACTION const *actions);
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
// Function: Chmod
|
|
//
|
|
// Description:
|
|
// The main method for chmod command
|
|
//
|
|
// Returns:
|
|
// 0: on success
|
|
//
|
|
// Notes:
|
|
//
|
|
int Chmod(__in int argc, __in_ecount(argc) wchar_t *argv[])
|
|
{
|
|
LPCWSTR pathName = NULL;
|
|
LPWSTR longPathName = NULL;
|
|
|
|
BOOL recursive = FALSE;
|
|
|
|
PMODE_CHANGE_ACTION actions = NULL;
|
|
|
|
INT unixAccessMask = 0;
|
|
|
|
DWORD dwRtnCode = 0;
|
|
|
|
int ret = EXIT_FAILURE;
|
|
|
|
// Parsing chmod arguments
|
|
//
|
|
if (!ParseCommandLineArguments(argc, argv,
|
|
&recursive, &unixAccessMask, &actions, &pathName))
|
|
{
|
|
fwprintf(stderr, L"Incorrect command line arguments.\n\n");
|
|
ChmodUsage(argv[0]);
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
// Convert the path to the long path
|
|
//
|
|
dwRtnCode = ConvertToLongPath(pathName, &longPathName);
|
|
if (dwRtnCode != ERROR_SUCCESS)
|
|
{
|
|
ReportErrorCode(L"ConvertToLongPath", dwRtnCode);
|
|
goto ChmodEnd;
|
|
}
|
|
|
|
if (!recursive)
|
|
{
|
|
if (ChangeFileMode(longPathName, unixAccessMask, actions))
|
|
{
|
|
ret = EXIT_SUCCESS;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (ChangeFileModeRecursively(longPathName, unixAccessMask, actions))
|
|
{
|
|
ret = EXIT_SUCCESS;
|
|
}
|
|
}
|
|
|
|
ChmodEnd:
|
|
FreeActions(actions);
|
|
LocalFree(longPathName);
|
|
|
|
return ret;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
// Function: ChangeFileMode
|
|
//
|
|
// Description:
|
|
// Wrapper function for change file mode. Choose either change by action or by
|
|
// access mask.
|
|
//
|
|
// Returns:
|
|
// TRUE: on success
|
|
// FALSE: otherwise
|
|
//
|
|
// Notes:
|
|
//
|
|
static BOOL ChangeFileMode(__in LPCWSTR path, __in_opt INT unixAccessMask,
|
|
__in_opt MODE_CHANGE_ACTION const *actions)
|
|
{
|
|
if (actions != NULL)
|
|
return ChangeFileModeByActions(path, actions);
|
|
else
|
|
{
|
|
DWORD dwRtnCode = ChangeFileModeByMask(path, unixAccessMask);
|
|
if (dwRtnCode != ERROR_SUCCESS)
|
|
{
|
|
ReportErrorCode(L"ChangeFileModeByMask", dwRtnCode);
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
// Function: ChangeFileModeRecursively
|
|
//
|
|
// Description:
|
|
// Travel the directory recursively to change the permissions.
|
|
//
|
|
// Returns:
|
|
// TRUE: on success
|
|
// FALSE: otherwise
|
|
//
|
|
// Notes:
|
|
// The recursion works in the following way:
|
|
// - If the path is not a directory, change its mode and return.
|
|
// Symbolic links and junction points are not considered as directories.
|
|
// - Otherwise, call the method on all its children, then change its mode.
|
|
//
|
|
static BOOL ChangeFileModeRecursively(__in LPCWSTR path, __in_opt INT mode,
|
|
__in_opt MODE_CHANGE_ACTION const *actions)
|
|
{
|
|
BOOL isDir = FALSE;
|
|
BOOL isSymlink = FALSE;
|
|
LPWSTR dir = NULL;
|
|
|
|
size_t pathSize = 0;
|
|
size_t dirSize = 0;
|
|
|
|
HANDLE hFind = INVALID_HANDLE_VALUE;
|
|
WIN32_FIND_DATA ffd;
|
|
DWORD dwRtnCode = ERROR_SUCCESS;
|
|
BOOL ret = FALSE;
|
|
|
|
if ((dwRtnCode = DirectoryCheck(path, &isDir)) != ERROR_SUCCESS)
|
|
{
|
|
ReportErrorCode(L"IsDirectory", dwRtnCode);
|
|
return FALSE;
|
|
}
|
|
if ((dwRtnCode = SymbolicLinkCheck(path, &isSymlink)) != ERROR_SUCCESS)
|
|
{
|
|
ReportErrorCode(L"IsSymbolicLink", dwRtnCode);
|
|
return FALSE;
|
|
}
|
|
|
|
if (isSymlink || !isDir)
|
|
{
|
|
if (ChangeFileMode(path, mode, actions))
|
|
return TRUE;
|
|
else
|
|
return FALSE;
|
|
}
|
|
|
|
if (FAILED(StringCchLengthW(path, STRSAFE_MAX_CCH - 3, &pathSize)))
|
|
{
|
|
return FALSE;
|
|
}
|
|
dirSize = pathSize + 3;
|
|
dir = (LPWSTR)LocalAlloc(LPTR, dirSize * sizeof(WCHAR));
|
|
if (dir == NULL)
|
|
{
|
|
ReportErrorCode(L"LocalAlloc", GetLastError());
|
|
goto ChangeFileModeRecursivelyEnd;
|
|
}
|
|
|
|
if (FAILED(StringCchCopyW(dir, dirSize, path)) ||
|
|
FAILED(StringCchCatW(dir, dirSize, L"\\*")))
|
|
{
|
|
goto ChangeFileModeRecursivelyEnd;
|
|
}
|
|
|
|
hFind = FindFirstFile(dir, &ffd);
|
|
if (hFind == INVALID_HANDLE_VALUE)
|
|
{
|
|
ReportErrorCode(L"FindFirstFile", GetLastError());
|
|
goto ChangeFileModeRecursivelyEnd;
|
|
}
|
|
|
|
do
|
|
{
|
|
LPWSTR filename = NULL;
|
|
LPWSTR longFilename = NULL;
|
|
size_t filenameSize = 0;
|
|
|
|
if (wcscmp(ffd.cFileName, L".") == 0 ||
|
|
wcscmp(ffd.cFileName, L"..") == 0)
|
|
continue;
|
|
|
|
filenameSize = pathSize + wcslen(ffd.cFileName) + 2;
|
|
filename = (LPWSTR)LocalAlloc(LPTR, filenameSize * sizeof(WCHAR));
|
|
if (filename == NULL)
|
|
{
|
|
ReportErrorCode(L"LocalAlloc", GetLastError());
|
|
goto ChangeFileModeRecursivelyEnd;
|
|
}
|
|
|
|
if (FAILED(StringCchCopyW(filename, filenameSize, path)) ||
|
|
FAILED(StringCchCatW(filename, filenameSize, L"\\")) ||
|
|
FAILED(StringCchCatW(filename, filenameSize, ffd.cFileName)))
|
|
{
|
|
LocalFree(filename);
|
|
goto ChangeFileModeRecursivelyEnd;
|
|
}
|
|
|
|
// The child fileanme is not prepended with long path prefix.
|
|
// Convert the filename to long path format.
|
|
//
|
|
dwRtnCode = ConvertToLongPath(filename, &longFilename);
|
|
LocalFree(filename);
|
|
if (dwRtnCode != ERROR_SUCCESS)
|
|
{
|
|
ReportErrorCode(L"ConvertToLongPath", dwRtnCode);
|
|
LocalFree(longFilename);
|
|
goto ChangeFileModeRecursivelyEnd;
|
|
}
|
|
|
|
if(!ChangeFileModeRecursively(longFilename, mode, actions))
|
|
{
|
|
LocalFree(longFilename);
|
|
goto ChangeFileModeRecursivelyEnd;
|
|
}
|
|
|
|
LocalFree(longFilename);
|
|
|
|
} while (FindNextFileW(hFind, &ffd));
|
|
|
|
if (!ChangeFileMode(path, mode, actions))
|
|
{
|
|
goto ChangeFileModeRecursivelyEnd;
|
|
}
|
|
|
|
ret = TRUE;
|
|
|
|
ChangeFileModeRecursivelyEnd:
|
|
LocalFree(dir);
|
|
|
|
return ret;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
// Function: ParseCommandLineArguments
|
|
//
|
|
// Description:
|
|
// Parse command line arguments for chmod.
|
|
//
|
|
// Returns:
|
|
// TRUE: on success
|
|
// FALSE: otherwise
|
|
//
|
|
// Notes:
|
|
// 1. Recursive is only set on directories
|
|
// 2. 'actions' is NULL if the mode is octal
|
|
//
|
|
static BOOL ParseCommandLineArguments(
|
|
__in int argc,
|
|
__in_ecount(argc) wchar_t *argv[],
|
|
__out BOOL *rec,
|
|
__out_opt INT *mask,
|
|
__out_opt PMODE_CHANGE_ACTION *actions,
|
|
__out LPCWSTR *path)
|
|
{
|
|
LPCWSTR maskString;
|
|
BY_HANDLE_FILE_INFORMATION fileInfo;
|
|
DWORD dwRtnCode = ERROR_SUCCESS;
|
|
|
|
assert(path != NULL);
|
|
|
|
if (argc != 3 && argc != 4)
|
|
return FALSE;
|
|
|
|
*rec = FALSE;
|
|
if (argc == 4)
|
|
{
|
|
maskString = argv[2];
|
|
*path = argv[3];
|
|
|
|
if (wcscmp(argv[1], L"-R") == 0)
|
|
{
|
|
// Check if the given path name is a file or directory
|
|
// Only set recursive flag if the given path is a directory
|
|
//
|
|
dwRtnCode = GetFileInformationByName(*path, FALSE, &fileInfo);
|
|
if (dwRtnCode != ERROR_SUCCESS)
|
|
{
|
|
ReportErrorCode(L"GetFileInformationByName", dwRtnCode);
|
|
return FALSE;
|
|
}
|
|
|
|
if (IsDirFileInfo(&fileInfo))
|
|
{
|
|
*rec = TRUE;
|
|
}
|
|
}
|
|
else
|
|
return FALSE;
|
|
}
|
|
else
|
|
{
|
|
maskString = argv[1];
|
|
*path = argv[2];
|
|
}
|
|
|
|
if (ParseOctalMode(maskString, mask))
|
|
{
|
|
return TRUE;
|
|
}
|
|
else if (ParseMode(maskString, actions))
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
// Function: FreeActions
|
|
//
|
|
// Description:
|
|
// Free a linked list of mode change actions given the head node.
|
|
//
|
|
// Returns:
|
|
// TRUE: on success
|
|
// FALSE: otherwise
|
|
//
|
|
// Notes:
|
|
// none
|
|
//
|
|
static BOOL FreeActions(PMODE_CHANGE_ACTION actions)
|
|
{
|
|
PMODE_CHANGE_ACTION curr = NULL;
|
|
PMODE_CHANGE_ACTION next = NULL;
|
|
|
|
// Nothing to free if NULL is passed in
|
|
//
|
|
if (actions == NULL)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
curr = actions;
|
|
while (curr != NULL)
|
|
{
|
|
next = curr->next_action;
|
|
LocalFree(curr);
|
|
curr = next;
|
|
}
|
|
actions = NULL;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
// Function: ComputeNewMode
|
|
//
|
|
// Description:
|
|
// Compute a new mode based on the old mode and a mode change action.
|
|
//
|
|
// Returns:
|
|
// The newly computed mode
|
|
//
|
|
// Notes:
|
|
// Apply 'rwx' permission mask or reference permission mode according to the
|
|
// '+', '-', or '=' operator.
|
|
//
|
|
static INT ComputeNewMode(__in INT oldMode,
|
|
__in USHORT who, __in USHORT op,
|
|
__in USHORT perm, __in USHORT ref)
|
|
{
|
|
static const INT readMask = 0444;
|
|
static const INT writeMask = 0222;
|
|
static const INT exeMask = 0111;
|
|
|
|
INT mask = 0;
|
|
INT mode = 0;
|
|
|
|
// Operations are exclusive, and cannot be invalid
|
|
//
|
|
assert(op == CHMOD_OP_EQUAL || op == CHMOD_OP_PLUS || op == CHMOD_OP_MINUS);
|
|
|
|
// Nothing needs to be changed if there is not permission or reference
|
|
//
|
|
if(perm == CHMOD_PERM_NA && ref == CHMOD_WHO_NONE)
|
|
{
|
|
return oldMode;
|
|
}
|
|
|
|
// We should have only permissions or a reference target, not both.
|
|
//
|
|
assert((perm != CHMOD_PERM_NA && ref == CHMOD_WHO_NONE) ||
|
|
(perm == CHMOD_PERM_NA && ref != CHMOD_WHO_NONE));
|
|
|
|
if (perm != CHMOD_PERM_NA)
|
|
{
|
|
if ((perm & CHMOD_PERM_R) == CHMOD_PERM_R)
|
|
mask |= readMask;
|
|
if ((perm & CHMOD_PERM_W) == CHMOD_PERM_W)
|
|
mask |= writeMask;
|
|
if ((perm & CHMOD_PERM_X) == CHMOD_PERM_X)
|
|
mask |= exeMask;
|
|
if (((perm & CHMOD_PERM_LX) == CHMOD_PERM_LX))
|
|
{
|
|
// It applies execute permissions to directories regardless of their
|
|
// current permissions and applies execute permissions to a file which
|
|
// already has at least 1 execute permission bit already set (either user,
|
|
// group or other). It is only really useful when used with '+' and
|
|
// usually in combination with the -R option for giving group or other
|
|
// access to a big directory tree without setting execute permission on
|
|
// normal files (such as text files), which would normally happen if you
|
|
// just used "chmod -R a+rx .", whereas with 'X' you can do
|
|
// "chmod -R a+rX ." instead (Source: Wikipedia)
|
|
//
|
|
if ((oldMode & UX_DIRECTORY) == UX_DIRECTORY || (oldMode & exeMask))
|
|
mask |= exeMask;
|
|
}
|
|
}
|
|
else if (ref != CHMOD_WHO_NONE)
|
|
{
|
|
mask |= oldMode & ref;
|
|
switch(ref)
|
|
{
|
|
case CHMOD_WHO_GROUP:
|
|
mask |= mask >> 3;
|
|
mask |= mask << 3;
|
|
break;
|
|
case CHMOD_WHO_OTHER:
|
|
mask |= mask << 3;
|
|
mask |= mask << 6;
|
|
break;
|
|
case CHMOD_WHO_USER:
|
|
mask |= mask >> 3;
|
|
mask |= mask >> 6;
|
|
break;
|
|
default:
|
|
// Reference modes can only be U/G/O and are exclusive
|
|
assert(FALSE);
|
|
}
|
|
}
|
|
|
|
mask &= who;
|
|
|
|
if (op == CHMOD_OP_EQUAL)
|
|
{
|
|
mode = (oldMode & (~who)) | mask;
|
|
}
|
|
else if (op == CHMOD_OP_MINUS)
|
|
{
|
|
mode = oldMode & (~mask);
|
|
}
|
|
else if (op == CHMOD_OP_PLUS)
|
|
{
|
|
mode = oldMode | mask;
|
|
}
|
|
|
|
return mode;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
// Function: ConvertActionsToMask
|
|
//
|
|
// Description:
|
|
// Convert a linked list of mode change actions to the Unix permission mask
|
|
// given the head node.
|
|
//
|
|
// Returns:
|
|
// TRUE: on success
|
|
// FALSE: otherwise
|
|
//
|
|
// Notes:
|
|
// none
|
|
//
|
|
static BOOL ConvertActionsToMask(__in LPCWSTR path,
|
|
__in MODE_CHANGE_ACTION const *actions, __out PINT puMask)
|
|
{
|
|
MODE_CHANGE_ACTION const *curr = NULL;
|
|
|
|
DWORD dwErrorCode = ERROR_SUCCESS;
|
|
|
|
INT mode = 0;
|
|
|
|
dwErrorCode = FindFileOwnerAndPermission(path, FALSE, NULL, NULL, &mode);
|
|
if (dwErrorCode != ERROR_SUCCESS)
|
|
{
|
|
ReportErrorCode(L"FindFileOwnerAndPermission", dwErrorCode);
|
|
return FALSE;
|
|
}
|
|
*puMask = mode;
|
|
|
|
// Nothing to change if NULL is passed in
|
|
//
|
|
if (actions == NULL)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
for (curr = actions; curr != NULL; curr = curr->next_action)
|
|
{
|
|
mode = ComputeNewMode(mode, curr->who, curr->op, curr->perm, curr->ref);
|
|
}
|
|
|
|
*puMask = mode;
|
|
return TRUE;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
// Function: ChangeFileModeByActions
|
|
//
|
|
// Description:
|
|
// Change a file mode through a list of actions.
|
|
//
|
|
// Returns:
|
|
// TRUE: on success
|
|
// FALSE: otherwise
|
|
//
|
|
// Notes:
|
|
// none
|
|
//
|
|
static BOOL ChangeFileModeByActions(__in LPCWSTR path,
|
|
MODE_CHANGE_ACTION const *actions)
|
|
{
|
|
INT mask = 0;
|
|
|
|
if (ConvertActionsToMask(path, actions, &mask))
|
|
{
|
|
DWORD dwRtnCode = ChangeFileModeByMask(path, mask);
|
|
if (dwRtnCode != ERROR_SUCCESS)
|
|
{
|
|
ReportErrorCode(L"ChangeFileModeByMask", dwRtnCode);
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
else
|
|
return FALSE;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
// Function: ParseMode
|
|
//
|
|
// Description:
|
|
// Convert a mode string into a linked list of actions
|
|
//
|
|
// Returns:
|
|
// TRUE: on success
|
|
// FALSE: otherwise
|
|
//
|
|
// Notes:
|
|
// Take a state machine approach to parse the mode. Each mode change action
|
|
// will be a node in the output linked list. The state machine has five state,
|
|
// and each will only transit to the next; the end state can transit back to
|
|
// the first state, and thus form a circle. In each state, if we see a
|
|
// a character not belongs to the state, we will move to next state. WHO, PERM,
|
|
// and REF states are optional; OP and END states are required; and errors
|
|
// will only be reported at the latter two states.
|
|
//
|
|
static BOOL ParseMode(LPCWSTR modeString, PMODE_CHANGE_ACTION *pActions)
|
|
{
|
|
enum __PARSE_MODE_ACTION_STATE
|
|
{
|
|
PARSE_MODE_ACTION_WHO_STATE,
|
|
PARSE_MODE_ACTION_OP_STATE,
|
|
PARSE_MODE_ACTION_PERM_STATE,
|
|
PARSE_MODE_ACTION_REF_STATE,
|
|
PARSE_MODE_ACTION_END_STATE
|
|
} state = PARSE_MODE_ACTION_WHO_STATE;
|
|
|
|
MODE_CHANGE_ACTION action = INIT_MODE_CHANGE_ACTION;
|
|
PMODE_CHANGE_ACTION actionsEnd = NULL;
|
|
PMODE_CHANGE_ACTION actionsLast = NULL;
|
|
USHORT lastWho;
|
|
WCHAR c = 0;
|
|
size_t len = 0;
|
|
size_t i = 0;
|
|
|
|
assert(modeString != NULL && pActions != NULL);
|
|
|
|
if (FAILED(StringCchLengthW(modeString, STRSAFE_MAX_CCH, &len)))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
actionsEnd = *pActions;
|
|
while(i <= len)
|
|
{
|
|
c = modeString[i];
|
|
if (state == PARSE_MODE_ACTION_WHO_STATE)
|
|
{
|
|
switch (c)
|
|
{
|
|
case L'a':
|
|
action.who |= CHMOD_WHO_ALL;
|
|
i++;
|
|
break;
|
|
case L'u':
|
|
action.who |= CHMOD_WHO_USER;
|
|
i++;
|
|
break;
|
|
case L'g':
|
|
action.who |= CHMOD_WHO_GROUP;
|
|
i++;
|
|
break;
|
|
case L'o':
|
|
action.who |= CHMOD_WHO_OTHER;
|
|
i++;
|
|
break;
|
|
default:
|
|
state = PARSE_MODE_ACTION_OP_STATE;
|
|
} // WHO switch
|
|
}
|
|
else if (state == PARSE_MODE_ACTION_OP_STATE)
|
|
{
|
|
switch (c)
|
|
{
|
|
case L'+':
|
|
action.op = CHMOD_OP_PLUS;
|
|
break;
|
|
case L'-':
|
|
action.op = CHMOD_OP_MINUS;
|
|
break;
|
|
case L'=':
|
|
action.op = CHMOD_OP_EQUAL;
|
|
break;
|
|
default:
|
|
fwprintf(stderr, L"Invalid mode: '%s'\n", modeString);
|
|
FreeActions(*pActions);
|
|
return FALSE;
|
|
} // OP switch
|
|
i++;
|
|
state = PARSE_MODE_ACTION_PERM_STATE;
|
|
}
|
|
else if (state == PARSE_MODE_ACTION_PERM_STATE)
|
|
{
|
|
switch (c)
|
|
{
|
|
case L'r':
|
|
action.perm |= CHMOD_PERM_R;
|
|
i++;
|
|
break;
|
|
case L'w':
|
|
action.perm |= CHMOD_PERM_W;
|
|
i++;
|
|
break;
|
|
case L'x':
|
|
action.perm |= CHMOD_PERM_X;
|
|
i++;
|
|
break;
|
|
case L'X':
|
|
action.perm |= CHMOD_PERM_LX;
|
|
i++;
|
|
break;
|
|
default:
|
|
state = PARSE_MODE_ACTION_REF_STATE;
|
|
} // PERM switch
|
|
}
|
|
else if (state == PARSE_MODE_ACTION_REF_STATE)
|
|
{
|
|
switch (c)
|
|
{
|
|
case L'u':
|
|
action.ref = CHMOD_WHO_USER;
|
|
i++;
|
|
break;
|
|
case L'g':
|
|
action.ref = CHMOD_WHO_GROUP;
|
|
i++;
|
|
break;
|
|
case L'o':
|
|
action.ref = CHMOD_WHO_OTHER;
|
|
i++;
|
|
break;
|
|
default:
|
|
state = PARSE_MODE_ACTION_END_STATE;
|
|
} // REF switch
|
|
}
|
|
else if (state == PARSE_MODE_ACTION_END_STATE)
|
|
{
|
|
switch (c)
|
|
{
|
|
case L'\0':
|
|
__fallthrough;
|
|
case L',':
|
|
i++;
|
|
__fallthrough;
|
|
case L'+':
|
|
__fallthrough;
|
|
case L'-':
|
|
__fallthrough;
|
|
case L'=':
|
|
state = PARSE_MODE_ACTION_WHO_STATE;
|
|
|
|
// Append the current action to the end of the linked list
|
|
//
|
|
assert(actionsEnd == NULL);
|
|
// Allocate memory
|
|
actionsEnd = (PMODE_CHANGE_ACTION) LocalAlloc(LPTR,
|
|
sizeof(MODE_CHANGE_ACTION));
|
|
if (actionsEnd == NULL)
|
|
{
|
|
ReportErrorCode(L"LocalAlloc", GetLastError());
|
|
FreeActions(*pActions);
|
|
return FALSE;
|
|
}
|
|
if (action.who == CHMOD_WHO_NONE) action.who = CHMOD_WHO_ALL;
|
|
// Copy the action to the new node
|
|
*actionsEnd = action;
|
|
// Append to the last node in the linked list
|
|
if (actionsLast != NULL) actionsLast->next_action = actionsEnd;
|
|
// pActions should point to the head of the linked list
|
|
if (*pActions == NULL) *pActions = actionsEnd;
|
|
// Update the two pointers to point to the last node and the tail
|
|
actionsLast = actionsEnd;
|
|
actionsEnd = actionsLast->next_action;
|
|
|
|
// Reset action
|
|
//
|
|
lastWho = action.who;
|
|
action = INIT_MODE_CHANGE_ACTION;
|
|
if (c != L',')
|
|
{
|
|
action.who = lastWho;
|
|
}
|
|
|
|
break;
|
|
default:
|
|
fwprintf(stderr, L"Invalid mode: '%s'\n", modeString);
|
|
FreeActions(*pActions);
|
|
return FALSE;
|
|
} // END switch
|
|
}
|
|
} // while
|
|
return TRUE;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
// Function: ParseOctalMode
|
|
//
|
|
// Description:
|
|
// Convert the 3 or 4 digits Unix mask string into the binary representation
|
|
// of the Unix access mask, i.e. 9 bits each an indicator of the permission
|
|
// of 'rwxrwxrwx', i.e. user's, group's, and owner's read, write, and
|
|
// execute/search permissions.
|
|
//
|
|
// Returns:
|
|
// TRUE: on success
|
|
// FALSE: otherwise
|
|
//
|
|
// Notes:
|
|
// none
|
|
//
|
|
static BOOL ParseOctalMode(LPCWSTR tsMask, INT *uMask)
|
|
{
|
|
size_t tsMaskLen = 0;
|
|
DWORD i;
|
|
LONG l;
|
|
WCHAR *end;
|
|
|
|
if (uMask == NULL)
|
|
return FALSE;
|
|
|
|
if (FAILED(StringCchLengthW(tsMask, STRSAFE_MAX_CCH, &tsMaskLen)))
|
|
return FALSE;
|
|
|
|
if (tsMaskLen == 0 || tsMaskLen > 4)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
for (i = 0; i < tsMaskLen; i++)
|
|
{
|
|
if (!(tsMask[tsMaskLen - i - 1] >= L'0' &&
|
|
tsMask[tsMaskLen - i - 1] <= L'7'))
|
|
return FALSE;
|
|
}
|
|
|
|
errno = 0;
|
|
if (tsMaskLen == 4)
|
|
// Windows does not have any equivalent of setuid/setgid and sticky bit.
|
|
// So the first bit is omitted for the 4 digit octal mode case.
|
|
//
|
|
l = wcstol(tsMask + 1, &end, 8);
|
|
else
|
|
l = wcstol(tsMask, &end, 8);
|
|
|
|
if (errno || l > 0x0777 || l < 0 || *end != 0)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
*uMask = (INT) l;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void ChmodUsage(LPCWSTR program)
|
|
{
|
|
fwprintf(stdout, L"\
|
|
Usage: %s [OPTION] OCTAL-MODE [FILE]\n\
|
|
or: %s [OPTION] MODE [FILE]\n\
|
|
Change the mode of the FILE to MODE.\n\
|
|
\n\
|
|
-R: change files and directories recursively\n\
|
|
\n\
|
|
Each MODE is of the form '[ugoa]*([-+=]([rwxX]*|[ugo]))+'.\n",
|
|
program, program);
|
|
}
|