| /** |
| * 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); |
| } |