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