/usr.bin/rctl/rctl.c

https://bitbucket.org/freebsd/freebsd-head/ · C · 539 lines · 402 code · 91 blank · 46 comment · 155 complexity · f8f8aadca9433a7cb8e9230e0f2bd683 MD5 · raw file

  1. /*-
  2. * Copyright (c) 2010 The FreeBSD Foundation
  3. * All rights reserved.
  4. *
  5. * This software was developed by Edward Tomasz Napierala under sponsorship
  6. * from the FreeBSD Foundation.
  7. *
  8. * Redistribution and use in source and binary forms, with or without
  9. * modification, are permitted provided that the following conditions
  10. * are met:
  11. * 1. Redistributions of source code must retain the above copyright
  12. * notice, this list of conditions and the following disclaimer.
  13. * 2. Redistributions in binary form must reproduce the above copyright
  14. * notice, this list of conditions and the following disclaimer in the
  15. * documentation and/or other materials provided with the distribution.
  16. *
  17. * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
  18. * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  19. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  20. * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
  21. * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  22. * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  23. * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  24. * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  25. * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  26. * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  27. * SUCH DAMAGE.
  28. *
  29. * $FreeBSD$
  30. */
  31. #include <sys/cdefs.h>
  32. __FBSDID("$FreeBSD$");
  33. #include <sys/types.h>
  34. #include <sys/rctl.h>
  35. #include <assert.h>
  36. #include <ctype.h>
  37. #include <err.h>
  38. #include <errno.h>
  39. #include <getopt.h>
  40. #include <grp.h>
  41. #include <libutil.h>
  42. #include <pwd.h>
  43. #include <stdint.h>
  44. #include <stdio.h>
  45. #include <stdlib.h>
  46. #include <string.h>
  47. #define RCTL_DEFAULT_BUFSIZE 4096
  48. static id_t
  49. parse_user(const char *s)
  50. {
  51. id_t id;
  52. char *end;
  53. struct passwd *pwd;
  54. pwd = getpwnam(s);
  55. if (pwd != NULL)
  56. return (pwd->pw_uid);
  57. if (!isnumber(s[0]))
  58. errx(1, "uknown user '%s'", s);
  59. id = strtod(s, &end);
  60. if ((size_t)(end - s) != strlen(s))
  61. errx(1, "trailing characters after numerical id");
  62. return (id);
  63. }
  64. static id_t
  65. parse_group(const char *s)
  66. {
  67. id_t id;
  68. char *end;
  69. struct group *grp;
  70. grp = getgrnam(s);
  71. if (grp != NULL)
  72. return (grp->gr_gid);
  73. if (!isnumber(s[0]))
  74. errx(1, "uknown group '%s'", s);
  75. id = strtod(s, &end);
  76. if ((size_t)(end - s) != strlen(s))
  77. errx(1, "trailing characters after numerical id");
  78. return (id);
  79. }
  80. /*
  81. * This routine replaces user/group name with numeric id.
  82. */
  83. static char *
  84. resolve_ids(char *rule)
  85. {
  86. id_t id;
  87. const char *subject, *textid, *rest;
  88. char *resolved;
  89. subject = strsep(&rule, ":");
  90. textid = strsep(&rule, ":");
  91. if (textid == NULL)
  92. errx(1, "error in rule specification -- no subject");
  93. if (rule != NULL)
  94. rest = rule;
  95. else
  96. rest = "";
  97. if (strcasecmp(subject, "u") == 0)
  98. subject = "user";
  99. else if (strcasecmp(subject, "g") == 0)
  100. subject = "group";
  101. else if (strcasecmp(subject, "p") == 0)
  102. subject = "process";
  103. else if (strcasecmp(subject, "l") == 0 ||
  104. strcasecmp(subject, "c") == 0 ||
  105. strcasecmp(subject, "class") == 0)
  106. subject = "loginclass";
  107. else if (strcasecmp(subject, "j") == 0)
  108. subject = "jail";
  109. if (strcasecmp(subject, "user") == 0 && strlen(textid) > 0) {
  110. id = parse_user(textid);
  111. asprintf(&resolved, "%s:%d:%s", subject, (int)id, rest);
  112. } else if (strcasecmp(subject, "group") == 0 && strlen(textid) > 0) {
  113. id = parse_group(textid);
  114. asprintf(&resolved, "%s:%d:%s", subject, (int)id, rest);
  115. } else
  116. asprintf(&resolved, "%s:%s:%s", subject, textid, rest);
  117. if (resolved == NULL)
  118. err(1, "asprintf");
  119. return (resolved);
  120. }
  121. /*
  122. * This routine replaces "human-readable" number with its expanded form.
  123. */
  124. static char *
  125. expand_amount(char *rule)
  126. {
  127. uint64_t num;
  128. const char *subject, *subject_id, *resource, *action, *amount, *per;
  129. char *copy, *expanded;
  130. copy = strdup(rule);
  131. if (copy == NULL)
  132. err(1, "strdup");
  133. subject = strsep(&copy, ":");
  134. subject_id = strsep(&copy, ":");
  135. resource = strsep(&copy, ":");
  136. action = strsep(&copy, "=/");
  137. amount = strsep(&copy, "/");
  138. per = copy;
  139. if (amount == NULL || strlen(amount) == 0) {
  140. free(copy);
  141. return (rule);
  142. }
  143. assert(subject != NULL);
  144. assert(subject_id != NULL);
  145. assert(resource != NULL);
  146. assert(action != NULL);
  147. if (expand_number(amount, &num))
  148. err(1, "expand_number");
  149. if (per == NULL)
  150. asprintf(&expanded, "%s:%s:%s:%s=%ju", subject, subject_id,
  151. resource, action, (uintmax_t)num);
  152. else
  153. asprintf(&expanded, "%s:%s:%s:%s=%ju/%s", subject, subject_id,
  154. resource, action, (uintmax_t)num, per);
  155. if (expanded == NULL)
  156. err(1, "asprintf");
  157. return (expanded);
  158. }
  159. static char *
  160. humanize_ids(char *rule)
  161. {
  162. id_t id;
  163. struct passwd *pwd;
  164. struct group *grp;
  165. const char *subject, *textid, *rest;
  166. char *humanized;
  167. subject = strsep(&rule, ":");
  168. textid = strsep(&rule, ":");
  169. if (textid == NULL)
  170. errx(1, "rule passed from the kernel didn't contain subject");
  171. if (rule != NULL)
  172. rest = rule;
  173. else
  174. rest = "";
  175. /* Replace numerical user and group ids with names. */
  176. if (strcasecmp(subject, "user") == 0) {
  177. id = parse_user(textid);
  178. pwd = getpwuid(id);
  179. if (pwd != NULL)
  180. textid = pwd->pw_name;
  181. } else if (strcasecmp(subject, "group") == 0) {
  182. id = parse_group(textid);
  183. grp = getgrgid(id);
  184. if (grp != NULL)
  185. textid = grp->gr_name;
  186. }
  187. asprintf(&humanized, "%s:%s:%s", subject, textid, rest);
  188. if (humanized == NULL)
  189. err(1, "asprintf");
  190. return (humanized);
  191. }
  192. static int
  193. str2int64(const char *str, int64_t *value)
  194. {
  195. char *end;
  196. if (str == NULL)
  197. return (EINVAL);
  198. *value = strtoul(str, &end, 10);
  199. if ((size_t)(end - str) != strlen(str))
  200. return (EINVAL);
  201. return (0);
  202. }
  203. static char *
  204. humanize_amount(char *rule)
  205. {
  206. int64_t num;
  207. const char *subject, *subject_id, *resource, *action, *amount, *per;
  208. char *copy, *humanized, buf[6];
  209. copy = strdup(rule);
  210. if (copy == NULL)
  211. err(1, "strdup");
  212. subject = strsep(&copy, ":");
  213. subject_id = strsep(&copy, ":");
  214. resource = strsep(&copy, ":");
  215. action = strsep(&copy, "=/");
  216. amount = strsep(&copy, "/");
  217. per = copy;
  218. if (amount == NULL || strlen(amount) == 0 ||
  219. str2int64(amount, &num) != 0) {
  220. free(copy);
  221. return (rule);
  222. }
  223. assert(subject != NULL);
  224. assert(subject_id != NULL);
  225. assert(resource != NULL);
  226. assert(action != NULL);
  227. if (humanize_number(buf, sizeof(buf), num, "", HN_AUTOSCALE,
  228. HN_DECIMAL | HN_NOSPACE) == -1)
  229. err(1, "humanize_number");
  230. if (per == NULL)
  231. asprintf(&humanized, "%s:%s:%s:%s=%s", subject, subject_id,
  232. resource, action, buf);
  233. else
  234. asprintf(&humanized, "%s:%s:%s:%s=%s/%s", subject, subject_id,
  235. resource, action, buf, per);
  236. if (humanized == NULL)
  237. err(1, "asprintf");
  238. return (humanized);
  239. }
  240. /*
  241. * Print rules, one per line.
  242. */
  243. static void
  244. print_rules(char *rules, int hflag, int nflag)
  245. {
  246. char *rule;
  247. while ((rule = strsep(&rules, ",")) != NULL) {
  248. if (rule[0] == '\0')
  249. break; /* XXX */
  250. if (nflag == 0)
  251. rule = humanize_ids(rule);
  252. if (hflag)
  253. rule = humanize_amount(rule);
  254. printf("%s\n", rule);
  255. }
  256. }
  257. static void
  258. add_rule(char *rule)
  259. {
  260. int error;
  261. error = rctl_add_rule(rule, strlen(rule) + 1, NULL, 0);
  262. if (error != 0)
  263. err(1, "rctl_add_rule");
  264. free(rule);
  265. }
  266. static void
  267. show_limits(char *filter, int hflag, int nflag)
  268. {
  269. int error;
  270. char *outbuf = NULL;
  271. size_t outbuflen = RCTL_DEFAULT_BUFSIZE / 4;
  272. do {
  273. outbuflen *= 4;
  274. outbuf = realloc(outbuf, outbuflen);
  275. if (outbuf == NULL)
  276. err(1, "realloc");
  277. error = rctl_get_limits(filter, strlen(filter) + 1, outbuf,
  278. outbuflen);
  279. if (error && errno != ERANGE)
  280. err(1, "rctl_get_limits");
  281. } while (error && errno == ERANGE);
  282. print_rules(outbuf, hflag, nflag);
  283. free(filter);
  284. free(outbuf);
  285. }
  286. static void
  287. remove_rule(char *filter)
  288. {
  289. int error;
  290. error = rctl_remove_rule(filter, strlen(filter) + 1, NULL, 0);
  291. if (error != 0)
  292. err(1, "rctl_remove_rule");
  293. free(filter);
  294. }
  295. static char *
  296. humanize_usage_amount(char *usage)
  297. {
  298. int64_t num;
  299. const char *resource, *amount;
  300. char *copy, *humanized, buf[6];
  301. copy = strdup(usage);
  302. if (copy == NULL)
  303. err(1, "strdup");
  304. resource = strsep(&copy, "=");
  305. amount = copy;
  306. assert(resource != NULL);
  307. assert(amount != NULL);
  308. if (str2int64(amount, &num) != 0 ||
  309. humanize_number(buf, sizeof(buf), num, "", HN_AUTOSCALE,
  310. HN_DECIMAL | HN_NOSPACE) == -1) {
  311. free(copy);
  312. return (usage);
  313. }
  314. asprintf(&humanized, "%s=%s", resource, buf);
  315. if (humanized == NULL)
  316. err(1, "asprintf");
  317. return (humanized);
  318. }
  319. /*
  320. * Query the kernel about a resource usage and print it out.
  321. */
  322. static void
  323. show_usage(char *filter, int hflag)
  324. {
  325. int error;
  326. char *outbuf = NULL, *tmp;
  327. size_t outbuflen = RCTL_DEFAULT_BUFSIZE / 4;
  328. do {
  329. outbuflen *= 4;
  330. outbuf = realloc(outbuf, outbuflen);
  331. if (outbuf == NULL)
  332. err(1, "realloc");
  333. error = rctl_get_racct(filter, strlen(filter) + 1, outbuf,
  334. outbuflen);
  335. if (error && errno != ERANGE)
  336. err(1, "rctl_get_racct");
  337. } while (error && errno == ERANGE);
  338. while ((tmp = strsep(&outbuf, ",")) != NULL) {
  339. if (tmp[0] == '\0')
  340. break; /* XXX */
  341. if (hflag)
  342. tmp = humanize_usage_amount(tmp);
  343. printf("%s\n", tmp);
  344. }
  345. free(filter);
  346. free(outbuf);
  347. }
  348. /*
  349. * Query the kernel about resource limit rules and print them out.
  350. */
  351. static void
  352. show_rules(char *filter, int hflag, int nflag)
  353. {
  354. int error;
  355. char *outbuf = NULL;
  356. size_t filterlen, outbuflen = RCTL_DEFAULT_BUFSIZE / 4;
  357. if (filter != NULL)
  358. filterlen = strlen(filter) + 1;
  359. else
  360. filterlen = 0;
  361. do {
  362. outbuflen *= 4;
  363. outbuf = realloc(outbuf, outbuflen);
  364. if (outbuf == NULL)
  365. err(1, "realloc");
  366. error = rctl_get_rules(filter, filterlen, outbuf, outbuflen);
  367. if (error && errno != ERANGE)
  368. err(1, "rctl_get_rules");
  369. } while (error && errno == ERANGE);
  370. print_rules(outbuf, hflag, nflag);
  371. free(outbuf);
  372. }
  373. static void
  374. usage(void)
  375. {
  376. fprintf(stderr, "usage: rctl [ -h ] [-a rule | -l filter | -r filter "
  377. "| -u filter | filter]\n");
  378. exit(1);
  379. }
  380. int
  381. main(int argc __unused, char **argv __unused)
  382. {
  383. int ch, aflag = 0, hflag = 0, nflag = 0, lflag = 0, rflag = 0,
  384. uflag = 0;
  385. char *rule = NULL;
  386. while ((ch = getopt(argc, argv, "a:hl:nr:u:")) != -1) {
  387. switch (ch) {
  388. case 'a':
  389. aflag = 1;
  390. rule = strdup(optarg);
  391. break;
  392. case 'h':
  393. hflag = 1;
  394. break;
  395. case 'l':
  396. lflag = 1;
  397. rule = strdup(optarg);
  398. break;
  399. case 'n':
  400. nflag = 1;
  401. break;
  402. case 'r':
  403. rflag = 1;
  404. rule = strdup(optarg);
  405. break;
  406. case 'u':
  407. uflag = 1;
  408. rule = strdup(optarg);
  409. break;
  410. case '?':
  411. default:
  412. usage();
  413. }
  414. }
  415. argc -= optind;
  416. argv += optind;
  417. if (argc > 1)
  418. usage();
  419. if (rule == NULL) {
  420. if (argc == 1)
  421. rule = strdup(argv[0]);
  422. else
  423. rule = strdup("::");
  424. }
  425. if (aflag + lflag + rflag + uflag + argc > 1)
  426. errx(1, "only one flag or argument may be specified "
  427. "at the same time");
  428. rule = resolve_ids(rule);
  429. rule = expand_amount(rule);
  430. if (aflag) {
  431. add_rule(rule);
  432. return (0);
  433. }
  434. if (lflag) {
  435. show_limits(rule, hflag, nflag);
  436. return (0);
  437. }
  438. if (rflag) {
  439. remove_rule(rule);
  440. return (0);
  441. }
  442. if (uflag) {
  443. show_usage(rule, hflag);
  444. return (0);
  445. }
  446. show_rules(rule, hflag, nflag);
  447. return (0);
  448. }