logo

utils-std

Collection of commonly available Unix tools git clone https://anongit.hacktivis.me/git/utils-std.git/

mode.c (6437B)


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