chmod.c 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903
  1. /**
  2. * Licensed to the Apache Software Foundation (ASF) under one or more
  3. * contributor license agreements. See the NOTICE file distributed with this
  4. * work for additional information regarding copyright ownership. The ASF
  5. * licenses this file to you under the Apache License, Version 2.0 (the
  6. * "License"); you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  13. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  14. * License for the specific language governing permissions and limitations under
  15. * the License.
  16. */
  17. #include "winutils.h"
  18. #include <errno.h>
  19. enum CHMOD_WHO
  20. {
  21. CHMOD_WHO_NONE = 0,
  22. CHMOD_WHO_OTHER = 07,
  23. CHMOD_WHO_GROUP = 070,
  24. CHMOD_WHO_USER = 0700,
  25. CHMOD_WHO_ALL = CHMOD_WHO_OTHER | CHMOD_WHO_GROUP | CHMOD_WHO_USER
  26. };
  27. enum CHMOD_OP
  28. {
  29. CHMOD_OP_INVALID,
  30. CHMOD_OP_PLUS,
  31. CHMOD_OP_MINUS,
  32. CHMOD_OP_EQUAL,
  33. };
  34. enum CHMOD_PERM
  35. {
  36. CHMOD_PERM_NA = 00,
  37. CHMOD_PERM_R = 01,
  38. CHMOD_PERM_W = 02,
  39. CHMOD_PERM_X = 04,
  40. CHMOD_PERM_LX = 010,
  41. };
  42. /*
  43. * We use the following struct to build a linked list of mode change actions.
  44. * The mode is described by the following grammar:
  45. * mode ::= clause [, clause ...]
  46. * clause ::= [who ...] [action ...]
  47. * action ::= op [perm ...] | op [ref]
  48. * who ::= a | u | g | o
  49. * op ::= + | - | =
  50. * perm ::= r | w | x | X
  51. * ref ::= u | g | o
  52. */
  53. typedef struct _MODE_CHANGE_ACTION
  54. {
  55. USHORT who;
  56. USHORT op;
  57. USHORT perm;
  58. USHORT ref;
  59. struct _MODE_CHANGE_ACTION *next_action;
  60. } MODE_CHANGE_ACTION, *PMODE_CHANGE_ACTION;
  61. const MODE_CHANGE_ACTION INIT_MODE_CHANGE_ACTION = {
  62. CHMOD_WHO_NONE, CHMOD_OP_INVALID, CHMOD_PERM_NA, CHMOD_WHO_NONE, NULL
  63. };
  64. static BOOL ParseOctalMode(LPCWSTR tsMask, INT *uMask);
  65. static BOOL ParseMode(LPCWSTR modeString, PMODE_CHANGE_ACTION *actions);
  66. static BOOL FreeActions(PMODE_CHANGE_ACTION actions);
  67. static BOOL ParseCommandLineArguments(
  68. __in int argc,
  69. __in_ecount(argc) wchar_t *argv[],
  70. __out BOOL *rec,
  71. __out_opt INT *mask,
  72. __out_opt PMODE_CHANGE_ACTION *actions,
  73. __out LPCWSTR *path);
  74. static BOOL ChangeFileModeByActions(__in LPCWSTR path,
  75. MODE_CHANGE_ACTION const *actions);
  76. static BOOL ChangeFileMode(__in LPCWSTR path, __in_opt INT mode,
  77. __in_opt MODE_CHANGE_ACTION const *actions);
  78. static BOOL ChangeFileModeRecursively(__in LPCWSTR path, __in_opt INT mode,
  79. __in_opt MODE_CHANGE_ACTION const *actions);
  80. //----------------------------------------------------------------------------
  81. // Function: Chmod
  82. //
  83. // Description:
  84. // The main method for chmod command
  85. //
  86. // Returns:
  87. // 0: on success
  88. //
  89. // Notes:
  90. //
  91. int Chmod(__in int argc, __in_ecount(argc) wchar_t *argv[])
  92. {
  93. LPWSTR pathName = NULL;
  94. LPWSTR longPathName = NULL;
  95. BOOL recursive = FALSE;
  96. PMODE_CHANGE_ACTION actions = NULL;
  97. INT unixAccessMask = 0;
  98. DWORD dwRtnCode = 0;
  99. int ret = EXIT_FAILURE;
  100. // Parsing chmod arguments
  101. //
  102. if (!ParseCommandLineArguments(argc, argv,
  103. &recursive, &unixAccessMask, &actions, &pathName))
  104. {
  105. fwprintf(stderr, L"Incorrect command line arguments.\n\n");
  106. ChmodUsage(argv[0]);
  107. return EXIT_FAILURE;
  108. }
  109. // Convert the path the the long path
  110. //
  111. dwRtnCode = ConvertToLongPath(pathName, &longPathName);
  112. if (dwRtnCode != ERROR_SUCCESS)
  113. {
  114. ReportErrorCode(L"ConvertToLongPath", dwRtnCode);
  115. goto ChmodEnd;
  116. }
  117. if (!recursive)
  118. {
  119. if (ChangeFileMode(longPathName, unixAccessMask, actions))
  120. {
  121. ret = EXIT_SUCCESS;
  122. }
  123. }
  124. else
  125. {
  126. if (ChangeFileModeRecursively(longPathName, unixAccessMask, actions))
  127. {
  128. ret = EXIT_SUCCESS;
  129. }
  130. }
  131. ChmodEnd:
  132. FreeActions(actions);
  133. LocalFree(longPathName);
  134. return ret;
  135. }
  136. //----------------------------------------------------------------------------
  137. // Function: ChangeFileMode
  138. //
  139. // Description:
  140. // Wrapper function for change file mode. Choose either change by action or by
  141. // access mask.
  142. //
  143. // Returns:
  144. // TRUE: on success
  145. // FALSE: otherwise
  146. //
  147. // Notes:
  148. //
  149. static BOOL ChangeFileMode(__in LPCWSTR path, __in_opt INT unixAccessMask,
  150. __in_opt MODE_CHANGE_ACTION const *actions)
  151. {
  152. if (actions != NULL)
  153. return ChangeFileModeByActions(path, actions);
  154. else
  155. {
  156. DWORD dwRtnCode = ChangeFileModeByMask(path, unixAccessMask);
  157. if (dwRtnCode != ERROR_SUCCESS)
  158. {
  159. ReportErrorCode(L"ChangeFileModeByMask", dwRtnCode);
  160. return FALSE;
  161. }
  162. return TRUE;
  163. }
  164. }
  165. //----------------------------------------------------------------------------
  166. // Function: ChangeFileModeRecursively
  167. //
  168. // Description:
  169. // Travel the directory recursively to change the permissions.
  170. //
  171. // Returns:
  172. // TRUE: on success
  173. // FALSE: otherwise
  174. //
  175. // Notes:
  176. // The recursion works in the following way:
  177. // - If the path is not a directory, change its mode and return.
  178. // Symbolic links and junction points are not considered as directories.
  179. // - Otherwise, call the method on all its children, then change its mode.
  180. //
  181. static BOOL ChangeFileModeRecursively(__in LPCWSTR path, __in_opt INT mode,
  182. __in_opt MODE_CHANGE_ACTION const *actions)
  183. {
  184. BOOL isDir = FALSE;
  185. BOOL isSymlink = FALSE;
  186. LPWSTR dir = NULL;
  187. size_t pathSize = 0;
  188. size_t dirSize = 0;
  189. HANDLE hFind = INVALID_HANDLE_VALUE;
  190. WIN32_FIND_DATA ffd;
  191. DWORD dwRtnCode = ERROR_SUCCESS;
  192. BOOL ret = FALSE;
  193. if ((dwRtnCode = DirectoryCheck(path, &isDir)) != ERROR_SUCCESS)
  194. {
  195. ReportErrorCode(L"IsDirectory", dwRtnCode);
  196. return FALSE;
  197. }
  198. if ((dwRtnCode = SymbolicLinkCheck(path, &isSymlink)) != ERROR_SUCCESS)
  199. {
  200. ReportErrorCode(L"IsSymbolicLink", dwRtnCode);
  201. return FALSE;
  202. }
  203. if (isSymlink || !isDir)
  204. {
  205. if (ChangeFileMode(path, mode, actions))
  206. return TRUE;
  207. else
  208. return FALSE;
  209. }
  210. if (FAILED(StringCchLengthW(path, STRSAFE_MAX_CCH - 3, &pathSize)))
  211. {
  212. return FALSE;
  213. }
  214. dirSize = pathSize + 3;
  215. dir = (LPWSTR)LocalAlloc(LPTR, dirSize * sizeof(WCHAR));
  216. if (dir == NULL)
  217. {
  218. ReportErrorCode(L"LocalAlloc", GetLastError());
  219. goto ChangeFileModeRecursivelyEnd;
  220. }
  221. if (FAILED(StringCchCopyW(dir, dirSize, path)) ||
  222. FAILED(StringCchCatW(dir, dirSize, L"\\*")))
  223. {
  224. goto ChangeFileModeRecursivelyEnd;
  225. }
  226. hFind = FindFirstFile(dir, &ffd);
  227. if (hFind == INVALID_HANDLE_VALUE)
  228. {
  229. ReportErrorCode(L"FindFirstFile", GetLastError());
  230. goto ChangeFileModeRecursivelyEnd;
  231. }
  232. do
  233. {
  234. LPWSTR filename = NULL;
  235. LPWSTR longFilename = NULL;
  236. size_t filenameSize = 0;
  237. if (wcscmp(ffd.cFileName, L".") == 0 ||
  238. wcscmp(ffd.cFileName, L"..") == 0)
  239. continue;
  240. filenameSize = pathSize + wcslen(ffd.cFileName) + 2;
  241. filename = (LPWSTR)LocalAlloc(LPTR, filenameSize * sizeof(WCHAR));
  242. if (filename == NULL)
  243. {
  244. ReportErrorCode(L"LocalAlloc", GetLastError());
  245. goto ChangeFileModeRecursivelyEnd;
  246. }
  247. if (FAILED(StringCchCopyW(filename, filenameSize, path)) ||
  248. FAILED(StringCchCatW(filename, filenameSize, L"\\")) ||
  249. FAILED(StringCchCatW(filename, filenameSize, ffd.cFileName)))
  250. {
  251. LocalFree(filename);
  252. goto ChangeFileModeRecursivelyEnd;
  253. }
  254. // The child fileanme is not prepended with long path prefix.
  255. // Convert the filename to long path format.
  256. //
  257. dwRtnCode = ConvertToLongPath(filename, &longFilename);
  258. LocalFree(filename);
  259. if (dwRtnCode != ERROR_SUCCESS)
  260. {
  261. ReportErrorCode(L"ConvertToLongPath", dwRtnCode);
  262. LocalFree(longFilename);
  263. goto ChangeFileModeRecursivelyEnd;
  264. }
  265. if(!ChangeFileModeRecursively(longFilename, mode, actions))
  266. {
  267. LocalFree(longFilename);
  268. goto ChangeFileModeRecursivelyEnd;
  269. }
  270. LocalFree(longFilename);
  271. } while (FindNextFileW(hFind, &ffd));
  272. if (!ChangeFileMode(path, mode, actions))
  273. {
  274. goto ChangeFileModeRecursivelyEnd;
  275. }
  276. ret = TRUE;
  277. ChangeFileModeRecursivelyEnd:
  278. LocalFree(dir);
  279. return ret;
  280. }
  281. //----------------------------------------------------------------------------
  282. // Function: ParseCommandLineArguments
  283. //
  284. // Description:
  285. // Parse command line arguments for chmod.
  286. //
  287. // Returns:
  288. // TRUE: on success
  289. // FALSE: otherwise
  290. //
  291. // Notes:
  292. // 1. Recursive is only set on directories
  293. // 2. 'actions' is NULL if the mode is octal
  294. //
  295. static BOOL ParseCommandLineArguments(
  296. __in int argc,
  297. __in_ecount(argc) wchar_t *argv[],
  298. __out BOOL *rec,
  299. __out_opt INT *mask,
  300. __out_opt PMODE_CHANGE_ACTION *actions,
  301. __out LPCWSTR *path)
  302. {
  303. LPCWSTR maskString;
  304. BY_HANDLE_FILE_INFORMATION fileInfo;
  305. DWORD dwRtnCode = ERROR_SUCCESS;
  306. assert(path != NULL);
  307. if (argc != 3 && argc != 4)
  308. return FALSE;
  309. *rec = FALSE;
  310. if (argc == 4)
  311. {
  312. maskString = argv[2];
  313. *path = argv[3];
  314. if (wcscmp(argv[1], L"-R") == 0)
  315. {
  316. // Check if the given path name is a file or directory
  317. // Only set recursive flag if the given path is a directory
  318. //
  319. dwRtnCode = GetFileInformationByName(*path, FALSE, &fileInfo);
  320. if (dwRtnCode != ERROR_SUCCESS)
  321. {
  322. ReportErrorCode(L"GetFileInformationByName", dwRtnCode);
  323. return FALSE;
  324. }
  325. if (IsDirFileInfo(&fileInfo))
  326. {
  327. *rec = TRUE;
  328. }
  329. }
  330. else
  331. return FALSE;
  332. }
  333. else
  334. {
  335. maskString = argv[1];
  336. *path = argv[2];
  337. }
  338. if (ParseOctalMode(maskString, mask))
  339. {
  340. return TRUE;
  341. }
  342. else if (ParseMode(maskString, actions))
  343. {
  344. return TRUE;
  345. }
  346. return FALSE;
  347. }
  348. //----------------------------------------------------------------------------
  349. // Function: FreeActions
  350. //
  351. // Description:
  352. // Free a linked list of mode change actions given the head node.
  353. //
  354. // Returns:
  355. // TRUE: on success
  356. // FALSE: otherwise
  357. //
  358. // Notes:
  359. // none
  360. //
  361. static BOOL FreeActions(PMODE_CHANGE_ACTION actions)
  362. {
  363. PMODE_CHANGE_ACTION curr = NULL;
  364. PMODE_CHANGE_ACTION next = NULL;
  365. // Nothing to free if NULL is passed in
  366. //
  367. if (actions == NULL)
  368. {
  369. return TRUE;
  370. }
  371. curr = actions;
  372. while (curr != NULL)
  373. {
  374. next = curr->next_action;
  375. LocalFree(curr);
  376. curr = next;
  377. }
  378. actions = NULL;
  379. return TRUE;
  380. }
  381. //----------------------------------------------------------------------------
  382. // Function: ComputeNewMode
  383. //
  384. // Description:
  385. // Compute a new mode based on the old mode and a mode change action.
  386. //
  387. // Returns:
  388. // The newly computed mode
  389. //
  390. // Notes:
  391. // Apply 'rwx' permission mask or reference permission mode according to the
  392. // '+', '-', or '=' operator.
  393. //
  394. static INT ComputeNewMode(__in INT oldMode,
  395. __in USHORT who, __in USHORT op,
  396. __in USHORT perm, __in USHORT ref)
  397. {
  398. static const INT readMask = 0444;
  399. static const INT writeMask = 0222;
  400. static const INT exeMask = 0111;
  401. INT mask = 0;
  402. INT mode = 0;
  403. // Operations are exclusive, and cannot be invalid
  404. //
  405. assert(op == CHMOD_OP_EQUAL || op == CHMOD_OP_PLUS || op == CHMOD_OP_MINUS);
  406. // Nothing needs to be changed if there is not permission or reference
  407. //
  408. if(perm == CHMOD_PERM_NA && ref == CHMOD_WHO_NONE)
  409. {
  410. return oldMode;
  411. }
  412. // We should have only permissions or a reference target, not both.
  413. //
  414. assert((perm != CHMOD_PERM_NA && ref == CHMOD_WHO_NONE) ||
  415. (perm == CHMOD_PERM_NA && ref != CHMOD_WHO_NONE));
  416. if (perm != CHMOD_PERM_NA)
  417. {
  418. if ((perm & CHMOD_PERM_R) == CHMOD_PERM_R)
  419. mask |= readMask;
  420. if ((perm & CHMOD_PERM_W) == CHMOD_PERM_W)
  421. mask |= writeMask;
  422. if ((perm & CHMOD_PERM_X) == CHMOD_PERM_X)
  423. mask |= exeMask;
  424. if (((perm & CHMOD_PERM_LX) == CHMOD_PERM_LX))
  425. {
  426. // It applies execute permissions to directories regardless of their
  427. // current permissions and applies execute permissions to a file which
  428. // already has at least 1 execute permission bit already set (either user,
  429. // group or other). It is only really useful when used with '+' and
  430. // usually in combination with the -R option for giving group or other
  431. // access to a big directory tree without setting execute permission on
  432. // normal files (such as text files), which would normally happen if you
  433. // just used "chmod -R a+rx .", whereas with 'X' you can do
  434. // "chmod -R a+rX ." instead (Source: Wikipedia)
  435. //
  436. if ((oldMode & UX_DIRECTORY) == UX_DIRECTORY || (oldMode & exeMask))
  437. mask |= exeMask;
  438. }
  439. }
  440. else if (ref != CHMOD_WHO_NONE)
  441. {
  442. mask |= oldMode & ref;
  443. switch(ref)
  444. {
  445. case CHMOD_WHO_GROUP:
  446. mask |= mask >> 3;
  447. mask |= mask << 3;
  448. break;
  449. case CHMOD_WHO_OTHER:
  450. mask |= mask << 3;
  451. mask |= mask << 6;
  452. break;
  453. case CHMOD_WHO_USER:
  454. mask |= mask >> 3;
  455. mask |= mask >> 6;
  456. break;
  457. default:
  458. // Reference modes can only be U/G/O and are exclusive
  459. assert(FALSE);
  460. }
  461. }
  462. mask &= who;
  463. if (op == CHMOD_OP_EQUAL)
  464. {
  465. mode = (oldMode & (~who)) | mask;
  466. }
  467. else if (op == CHMOD_OP_MINUS)
  468. {
  469. mode = oldMode & (~mask);
  470. }
  471. else if (op == CHMOD_OP_PLUS)
  472. {
  473. mode = oldMode | mask;
  474. }
  475. return mode;
  476. }
  477. //----------------------------------------------------------------------------
  478. // Function: ConvertActionsToMask
  479. //
  480. // Description:
  481. // Convert a linked list of mode change actions to the Unix permission mask
  482. // given the head node.
  483. //
  484. // Returns:
  485. // TRUE: on success
  486. // FALSE: otherwise
  487. //
  488. // Notes:
  489. // none
  490. //
  491. static BOOL ConvertActionsToMask(__in LPCWSTR path,
  492. __in MODE_CHANGE_ACTION const *actions, __out PINT puMask)
  493. {
  494. MODE_CHANGE_ACTION const *curr = NULL;
  495. BY_HANDLE_FILE_INFORMATION fileInformation;
  496. DWORD dwErrorCode = ERROR_SUCCESS;
  497. INT mode = 0;
  498. dwErrorCode = GetFileInformationByName(path, FALSE, &fileInformation);
  499. if (dwErrorCode != ERROR_SUCCESS)
  500. {
  501. ReportErrorCode(L"GetFileInformationByName", dwErrorCode);
  502. return FALSE;
  503. }
  504. if (IsDirFileInfo(&fileInformation))
  505. {
  506. mode |= UX_DIRECTORY;
  507. }
  508. dwErrorCode = FindFileOwnerAndPermission(path, NULL, NULL, &mode);
  509. if (dwErrorCode != ERROR_SUCCESS)
  510. {
  511. ReportErrorCode(L"FindFileOwnerAndPermission", dwErrorCode);
  512. return FALSE;
  513. }
  514. *puMask = mode;
  515. // Nothing to change if NULL is passed in
  516. //
  517. if (actions == NULL)
  518. {
  519. return TRUE;
  520. }
  521. for (curr = actions; curr != NULL; curr = curr->next_action)
  522. {
  523. mode = ComputeNewMode(mode, curr->who, curr->op, curr->perm, curr->ref);
  524. }
  525. *puMask = mode;
  526. return TRUE;
  527. }
  528. //----------------------------------------------------------------------------
  529. // Function: ChangeFileModeByActions
  530. //
  531. // Description:
  532. // Change a file mode through a list of actions.
  533. //
  534. // Returns:
  535. // TRUE: on success
  536. // FALSE: otherwise
  537. //
  538. // Notes:
  539. // none
  540. //
  541. static BOOL ChangeFileModeByActions(__in LPCWSTR path,
  542. MODE_CHANGE_ACTION const *actions)
  543. {
  544. INT mask = 0;
  545. if (ConvertActionsToMask(path, actions, &mask))
  546. {
  547. DWORD dwRtnCode = ChangeFileModeByMask(path, mask);
  548. if (dwRtnCode != ERROR_SUCCESS)
  549. {
  550. ReportErrorCode(L"ChangeFileModeByMask", dwRtnCode);
  551. return FALSE;
  552. }
  553. return TRUE;
  554. }
  555. else
  556. return FALSE;
  557. }
  558. //----------------------------------------------------------------------------
  559. // Function: ParseMode
  560. //
  561. // Description:
  562. // Convert a mode string into a linked list of actions
  563. //
  564. // Returns:
  565. // TRUE: on success
  566. // FALSE: otherwise
  567. //
  568. // Notes:
  569. // Take a state machine approach to parse the mode. Each mode change action
  570. // will be a node in the output linked list. The state machine has five state,
  571. // and each will only transit to the next; the end state can transit back to
  572. // the first state, and thus form a circle. In each state, if we see a
  573. // a character not belongs to the state, we will move to next state. WHO, PERM,
  574. // and REF states are optional; OP and END states are required; and errors
  575. // will only be reported at the latter two states.
  576. //
  577. static BOOL ParseMode(LPCWSTR modeString, PMODE_CHANGE_ACTION *pActions)
  578. {
  579. enum __PARSE_MODE_ACTION_STATE
  580. {
  581. PARSE_MODE_ACTION_WHO_STATE,
  582. PARSE_MODE_ACTION_OP_STATE,
  583. PARSE_MODE_ACTION_PERM_STATE,
  584. PARSE_MODE_ACTION_REF_STATE,
  585. PARSE_MODE_ACTION_END_STATE
  586. } state = PARSE_MODE_ACTION_WHO_STATE;
  587. MODE_CHANGE_ACTION action = INIT_MODE_CHANGE_ACTION;
  588. PMODE_CHANGE_ACTION actionsEnd = NULL;
  589. PMODE_CHANGE_ACTION actionsLast = NULL;
  590. USHORT lastWho;
  591. WCHAR c = 0;
  592. size_t len = 0;
  593. size_t i = 0;
  594. assert(modeString != NULL && pActions != NULL);
  595. if (FAILED(StringCchLengthW(modeString, STRSAFE_MAX_CCH, &len)))
  596. {
  597. return FALSE;
  598. }
  599. actionsEnd = *pActions;
  600. while(i <= len)
  601. {
  602. c = modeString[i];
  603. if (state == PARSE_MODE_ACTION_WHO_STATE)
  604. {
  605. switch (c)
  606. {
  607. case L'a':
  608. action.who |= CHMOD_WHO_ALL;
  609. i++;
  610. break;
  611. case L'u':
  612. action.who |= CHMOD_WHO_USER;
  613. i++;
  614. break;
  615. case L'g':
  616. action.who |= CHMOD_WHO_GROUP;
  617. i++;
  618. break;
  619. case L'o':
  620. action.who |= CHMOD_WHO_OTHER;
  621. i++;
  622. break;
  623. default:
  624. state = PARSE_MODE_ACTION_OP_STATE;
  625. } // WHO switch
  626. }
  627. else if (state == PARSE_MODE_ACTION_OP_STATE)
  628. {
  629. switch (c)
  630. {
  631. case L'+':
  632. action.op = CHMOD_OP_PLUS;
  633. break;
  634. case L'-':
  635. action.op = CHMOD_OP_MINUS;
  636. break;
  637. case L'=':
  638. action.op = CHMOD_OP_EQUAL;
  639. break;
  640. default:
  641. fwprintf(stderr, L"Invalid mode: '%s'\n", modeString);
  642. FreeActions(*pActions);
  643. return FALSE;
  644. } // OP switch
  645. i++;
  646. state = PARSE_MODE_ACTION_PERM_STATE;
  647. }
  648. else if (state == PARSE_MODE_ACTION_PERM_STATE)
  649. {
  650. switch (c)
  651. {
  652. case L'r':
  653. action.perm |= CHMOD_PERM_R;
  654. i++;
  655. break;
  656. case L'w':
  657. action.perm |= CHMOD_PERM_W;
  658. i++;
  659. break;
  660. case L'x':
  661. action.perm |= CHMOD_PERM_X;
  662. i++;
  663. break;
  664. case L'X':
  665. action.perm |= CHMOD_PERM_LX;
  666. i++;
  667. break;
  668. default:
  669. state = PARSE_MODE_ACTION_REF_STATE;
  670. } // PERM switch
  671. }
  672. else if (state == PARSE_MODE_ACTION_REF_STATE)
  673. {
  674. switch (c)
  675. {
  676. case L'u':
  677. action.ref = CHMOD_WHO_USER;
  678. i++;
  679. break;
  680. case L'g':
  681. action.ref = CHMOD_WHO_GROUP;
  682. i++;
  683. break;
  684. case L'o':
  685. action.ref = CHMOD_WHO_OTHER;
  686. i++;
  687. break;
  688. default:
  689. state = PARSE_MODE_ACTION_END_STATE;
  690. } // REF switch
  691. }
  692. else if (state == PARSE_MODE_ACTION_END_STATE)
  693. {
  694. switch (c)
  695. {
  696. case NULL:
  697. __fallthrough;
  698. case L',':
  699. i++;
  700. __fallthrough;
  701. case L'+':
  702. __fallthrough;
  703. case L'-':
  704. __fallthrough;
  705. case L'=':
  706. state = PARSE_MODE_ACTION_WHO_STATE;
  707. // Append the current action to the end of the linked list
  708. //
  709. assert(actionsEnd == NULL);
  710. // Allocate memory
  711. actionsEnd = (PMODE_CHANGE_ACTION) LocalAlloc(LPTR,
  712. sizeof(MODE_CHANGE_ACTION));
  713. if (actionsEnd == NULL)
  714. {
  715. ReportErrorCode(L"LocalAlloc", GetLastError());
  716. FreeActions(*pActions);
  717. return FALSE;
  718. }
  719. if (action.who == CHMOD_WHO_NONE) action.who = CHMOD_WHO_ALL;
  720. // Copy the action to the new node
  721. *actionsEnd = action;
  722. // Append to the last node in the linked list
  723. if (actionsLast != NULL) actionsLast->next_action = actionsEnd;
  724. // pActions should point to the head of the linked list
  725. if (*pActions == NULL) *pActions = actionsEnd;
  726. // Update the two pointers to point to the last node and the tail
  727. actionsLast = actionsEnd;
  728. actionsEnd = actionsLast->next_action;
  729. // Reset action
  730. //
  731. lastWho = action.who;
  732. action = INIT_MODE_CHANGE_ACTION;
  733. if (c != L',')
  734. {
  735. action.who = lastWho;
  736. }
  737. break;
  738. default:
  739. fwprintf(stderr, L"Invalid mode: '%s'\n", modeString);
  740. FreeActions(*pActions);
  741. return FALSE;
  742. } // END switch
  743. }
  744. } // while
  745. return TRUE;
  746. }
  747. //----------------------------------------------------------------------------
  748. // Function: ParseOctalMode
  749. //
  750. // Description:
  751. // Convert the 3 or 4 digits Unix mask string into the binary representation
  752. // of the Unix access mask, i.e. 9 bits each an indicator of the permission
  753. // of 'rwxrwxrwx', i.e. user's, group's, and owner's read, write, and
  754. // execute/search permissions.
  755. //
  756. // Returns:
  757. // TRUE: on success
  758. // FALSE: otherwise
  759. //
  760. // Notes:
  761. // none
  762. //
  763. static BOOL ParseOctalMode(LPCWSTR tsMask, INT *uMask)
  764. {
  765. size_t tsMaskLen = 0;
  766. DWORD i;
  767. LONG l;
  768. WCHAR *end;
  769. if (uMask == NULL)
  770. return FALSE;
  771. if (FAILED(StringCchLengthW(tsMask, STRSAFE_MAX_CCH, &tsMaskLen)))
  772. return FALSE;
  773. if (tsMaskLen == 0 || tsMaskLen > 4)
  774. {
  775. return FALSE;
  776. }
  777. for (i = 0; i < tsMaskLen; i++)
  778. {
  779. if (!(tsMask[tsMaskLen - i - 1] >= L'0' &&
  780. tsMask[tsMaskLen - i - 1] <= L'7'))
  781. return FALSE;
  782. }
  783. errno = 0;
  784. if (tsMaskLen == 4)
  785. // Windows does not have any equivalent of setuid/setgid and sticky bit.
  786. // So the first bit is omitted for the 4 digit octal mode case.
  787. //
  788. l = wcstol(tsMask + 1, &end, 8);
  789. else
  790. l = wcstol(tsMask, &end, 8);
  791. if (errno || l > 0x0777 || l < 0 || *end != 0)
  792. {
  793. return FALSE;
  794. }
  795. *uMask = (INT) l;
  796. return TRUE;
  797. }
  798. void ChmodUsage(LPCWSTR program)
  799. {
  800. fwprintf(stdout, L"\
  801. Usage: %s [OPTION] OCTAL-MODE [FILE]\n\
  802. or: %s [OPTION] MODE [FILE]\n\
  803. Change the mode of the FILE to MODE.\n\
  804. \n\
  805. -R: change files and directories recursively\n\
  806. \n\
  807. Each MODE is of the form '[ugoa]*([-+=]([rwxX]*|[ugo]))+'.\n",
  808. program, program);
  809. }