chmod.c 21 KB

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