logo

utils-std

Collection of commonly available Unix tools

mode.c (6443B)


  1. // Collection of Unix tools, comparable to coreutils
  2. // SPDX-FileCopyrightText: 2017 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
  3. // SPDX-License-Identifier: MPL-2.0
  4. #define _POSIX_C_SOURCE 200809L
  5. #define _XOPEN_SOURCE 700 // S_ISVTX
  6. #include "mode.h"
  7. #include "bitmasks.h"
  8. #include <assert.h>
  9. #include <errno.h>
  10. #include <stdbool.h>
  11. #include <stdint.h> // int8_t
  12. #include <stdio.h> // strtol
  13. #include <stdlib.h> // abort
  14. #include <string.h> // strlen
  15. #include <sys/stat.h> // umask
  16. enum perm_who_e
  17. {
  18. WHO_RST = 0, // 0b000
  19. WHO_USER = 1, // 0b001
  20. WHO_GROUP = 2, // 0b010
  21. WHO_OTHER = 4, // 0b100
  22. WHO_ALL = 7, // 0b111
  23. };
  24. enum operation_e
  25. {
  26. OP_RST,
  27. OP_ADD,
  28. OP_DEL,
  29. OP_SET,
  30. };
  31. struct modes_t
  32. {
  33. int8_t set;
  34. int8_t user;
  35. int8_t group;
  36. int8_t other;
  37. };
  38. struct meta_t
  39. {
  40. enum perm_who_e dest;
  41. enum operation_e op;
  42. struct modes_t new_modes;
  43. };
  44. static void
  45. apply(struct meta_t *meta, int8_t mode)
  46. {
  47. // get old, then reset to old. Yay to POSIX nonsense
  48. mode_t mask = umask(0);
  49. umask(mask);
  50. // negate to get a normal bitmask
  51. mask ^= 0777;
  52. switch(meta->op)
  53. {
  54. case OP_ADD:
  55. if(meta->dest == WHO_RST)
  56. {
  57. FIELD_SET(meta->new_modes.user, mode & ((mask >> 6) & 07));
  58. FIELD_SET(meta->new_modes.group, mode & ((mask >> 3) & 07));
  59. FIELD_SET(meta->new_modes.other, mode & ((mask >> 0) & 07));
  60. }
  61. if(FIELD_MATCH(meta->dest, WHO_USER)) FIELD_SET(meta->new_modes.user, mode);
  62. if(FIELD_MATCH(meta->dest, WHO_GROUP)) FIELD_SET(meta->new_modes.group, mode);
  63. if(FIELD_MATCH(meta->dest, WHO_OTHER)) FIELD_SET(meta->new_modes.other, mode);
  64. break;
  65. case OP_DEL:
  66. if(meta->dest == WHO_RST)
  67. {
  68. FIELD_CLR(meta->new_modes.user, mode & ((mask >> 6) & 07));
  69. FIELD_CLR(meta->new_modes.group, mode & ((mask >> 3) & 07));
  70. FIELD_CLR(meta->new_modes.other, mode & ((mask >> 0) & 07));
  71. }
  72. if(FIELD_MATCH(meta->dest, WHO_USER)) FIELD_CLR(meta->new_modes.user, mode);
  73. if(FIELD_MATCH(meta->dest, WHO_GROUP)) FIELD_CLR(meta->new_modes.group, mode);
  74. if(FIELD_MATCH(meta->dest, WHO_OTHER)) FIELD_CLR(meta->new_modes.other, mode);
  75. break;
  76. case OP_SET:
  77. if(meta->dest == WHO_RST)
  78. {
  79. meta->new_modes.user = mode & ((mask >> 6) & 07);
  80. meta->new_modes.group = mode & ((mask >> 3) & 07);
  81. meta->new_modes.other = mode & ((mask >> 0) & 07);
  82. }
  83. if(FIELD_MATCH(meta->dest, WHO_USER)) meta->new_modes.user = mode;
  84. if(FIELD_MATCH(meta->dest, WHO_GROUP)) meta->new_modes.group = mode;
  85. if(FIELD_MATCH(meta->dest, WHO_OTHER)) meta->new_modes.other = mode;
  86. // So a=rw is a=r+w rather than a=r,a=w
  87. meta->op = OP_ADD;
  88. break;
  89. default:
  90. abort();
  91. }
  92. }
  93. mode_t
  94. new_mode(const char *mode, mode_t old, const char **errstr)
  95. {
  96. // NULL? Then nothing to change :)
  97. if(mode == NULL) return old;
  98. size_t mode_len = strlen(mode);
  99. if(mode_len == 0) return old;
  100. bool symbolic = false;
  101. for(size_t i = 0; i < mode_len; i++)
  102. {
  103. if(mode[i] < '0' || mode[i] > '7')
  104. {
  105. if(mode[i] == '8' || mode[i] == '9')
  106. {
  107. *errstr = "contains digit outside of [0-7]";
  108. return old;
  109. }
  110. symbolic = true;
  111. break;
  112. }
  113. }
  114. if(!symbolic)
  115. {
  116. char *endptr = NULL;
  117. assert(errno == 0);
  118. long new = strtol(mode, &endptr, 8);
  119. if(errno != 0)
  120. {
  121. *errstr = strerror(errno);
  122. errno = 0;
  123. return old;
  124. }
  125. if(new < 0)
  126. {
  127. *errstr = "can't be negative";
  128. return old;
  129. }
  130. if(new > 07777)
  131. {
  132. *errstr = "can't be higher than 0o7777";
  133. return old;
  134. }
  135. return (old & 0770000) | new;
  136. }
  137. // ((^|,)[ugoa]*([+-=]|[ugo]|[rwxXst]+)+)+
  138. struct meta_t meta = {.dest = WHO_RST,
  139. .op = OP_RST,
  140. .new_modes = {
  141. .set = (old & 07000) >> 9,
  142. .user = (old & 00700) >> 6,
  143. .group = (old & 00070) >> 3,
  144. .other = (old & 00007) >> 0,
  145. }};
  146. for(size_t i = 0; i < mode_len; i++)
  147. {
  148. char m = mode[i];
  149. switch(m)
  150. {
  151. // separator
  152. case ',':
  153. meta.dest = WHO_RST;
  154. meta.op = OP_RST;
  155. break;
  156. // who
  157. case 'u':
  158. if(meta.op == OP_RST)
  159. FIELD_SET(meta.dest, WHO_USER);
  160. else
  161. apply(&meta, meta.new_modes.user);
  162. break;
  163. case 'g':
  164. if(meta.op == OP_RST)
  165. FIELD_SET(meta.dest, WHO_GROUP);
  166. else
  167. apply(&meta, meta.new_modes.group);
  168. break;
  169. case 'o':
  170. if(meta.op == OP_RST)
  171. FIELD_SET(meta.dest, WHO_OTHER);
  172. else
  173. apply(&meta, meta.new_modes.other);
  174. break;
  175. case 'a':
  176. if(meta.op == OP_RST) FIELD_SET(meta.dest, WHO_ALL);
  177. // ignore =a, POSIX doesn't allows "a" in permcopy (would be a noop anyway)
  178. break;
  179. // op
  180. // Implementation-note: When another operator was set, override it
  181. case '+':
  182. meta.op = OP_ADD;
  183. break;
  184. case '-':
  185. meta.op = OP_DEL;
  186. break;
  187. case '=':
  188. meta.op = OP_SET;
  189. apply(&meta, 00);
  190. break;
  191. // perm
  192. case 'r':
  193. if(meta.op == OP_RST)
  194. {
  195. *errstr = "perm 'r' given without operator";
  196. return 0;
  197. }
  198. apply(&meta, 04);
  199. break;
  200. case 'w':
  201. if(meta.op == OP_RST)
  202. {
  203. *errstr = "perm 'w' given without operator";
  204. return 0;
  205. }
  206. apply(&meta, 02);
  207. break;
  208. case 'x':
  209. if(meta.op == OP_RST)
  210. {
  211. *errstr = "perm 'x' given without operator";
  212. return 0;
  213. }
  214. apply(&meta, 01);
  215. break;
  216. case 'X':
  217. if(meta.op == OP_RST)
  218. {
  219. *errstr = "perm 'X' given without operator";
  220. return 0;
  221. }
  222. if(S_ISDIR(old)) apply(&meta, 01);
  223. break;
  224. case 's':
  225. if(meta.op == OP_RST)
  226. {
  227. *errstr = "perm 's' given without operator";
  228. return 0;
  229. }
  230. if(FIELD_MATCH(meta.dest, WHO_USER))
  231. meta.new_modes.set = S_ISUID >> 9;
  232. else if(FIELD_MATCH(meta.dest, WHO_GROUP))
  233. meta.new_modes.set = S_ISGID >> 9;
  234. else
  235. meta.new_modes.set = S_ISUID >> 9;
  236. break;
  237. case 't':
  238. // Implementation defined behavior outside of directories and !(WHO_ALL|WHO_RST)
  239. if(meta.op == OP_RST)
  240. {
  241. *errstr = "perm 't' given without operator";
  242. return 0;
  243. }
  244. meta.new_modes.set = S_ISVTX >> 9;
  245. break;
  246. default:
  247. // Would need to either be non-thread-safe with a shared read-write buffer
  248. // or only allocate in this situation, meaning a memory leak.
  249. *errstr = "syntax error, got invalid character";
  250. return 0;
  251. }
  252. }
  253. // clang-format off
  254. return (old & 0770000)
  255. | ((meta.new_modes.set & 07) << 9)
  256. | ((meta.new_modes.user & 07) << 6)
  257. | ((meta.new_modes.group & 07) << 3)
  258. | ((meta.new_modes.other & 07) << 0);
  259. // clang-format on
  260. }