Created
June 3, 2012 20:33
-
-
Save fuzzy/2864929 to your computer and use it in GitHub Desktop.
Python rctl binding for FreeBSD 9.X+
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*- | |
* Copyright (c) 2010 The FreeBSD Foundation | |
* All rights reserved. | |
* | |
* This software was developed by Edward Tomasz Napierala under sponsorship | |
* from the FreeBSD Foundation. | |
* | |
* Redistribution and use in source and binary forms, with or without | |
* modification, are permitted provided that the following conditions | |
* are met: | |
* 1. Redistributions of source code must retain the above copyright | |
* notice, this list of conditions and the following disclaimer. | |
* 2. Redistributions in binary form must reproduce the above copyright | |
* notice, this list of conditions and the following disclaimer in the | |
* documentation and/or other materials provided with the distribution. | |
* | |
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND | |
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE | |
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | |
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |
* SUCH DAMAGE. | |
* | |
* $FreeBSD: src/usr.bin/rctl/rctl.c,v 1.1.2.1 2011/09/23 00:51:37 kensmith Exp $ | |
*/ | |
#include <sys/cdefs.h> | |
__FBSDID("$FreeBSD: src/usr.bin/rctl/rctl.c,v 1.1.2.1 2011/09/23 00:51:37 kensmith Exp $"); | |
#include <sys/types.h> | |
#include <sys/rctl.h> | |
#include <assert.h> | |
#include <ctype.h> | |
#include <err.h> | |
#include <errno.h> | |
#include <getopt.h> | |
#include <grp.h> | |
#include <libutil.h> | |
#include <pwd.h> | |
#include <stdint.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
/* | |
* Lets get Python in here | |
*/ | |
#include <Python.h> | |
/* | |
* The default is fine for FreeBSD and it's fine for me. | |
*/ | |
#define RCTL_DEFAULT_BUFSIZE 4096 | |
static id_t | |
parse_user(const char *s) | |
{ | |
id_t id; | |
char *end; | |
struct passwd *pwd; | |
pwd = getpwnam(s); | |
if (pwd != NULL) | |
return (pwd->pw_uid); | |
if (!isnumber(s[0])) | |
errx(1, "uknown user '%s'", s); | |
id = strtod(s, &end); | |
if ((size_t)(end - s) != strlen(s)) | |
errx(1, "trailing characters after numerical id"); | |
return (id); | |
} | |
static id_t | |
parse_group(const char *s) | |
{ | |
id_t id; | |
char *end; | |
struct group *grp; | |
grp = getgrnam(s); | |
if (grp != NULL) | |
return (grp->gr_gid); | |
if (!isnumber(s[0])) | |
errx(1, "uknown group '%s'", s); | |
id = strtod(s, &end); | |
if ((size_t)(end - s) != strlen(s)) | |
errx(1, "trailing characters after numerical id"); | |
return (id); | |
} | |
/* | |
* This routine replaces user/group name with numeric id. | |
*/ | |
static char * | |
resolve_ids(char *rule) | |
{ | |
id_t id; | |
const char *subject, *textid, *rest; | |
char *resolved; | |
subject = strsep(&rule, ":"); | |
textid = strsep(&rule, ":"); | |
if (textid == NULL) | |
errx(1, "error in rule specification -- no subject"); | |
if (rule != NULL) | |
rest = rule; | |
else | |
rest = ""; | |
if (strcasecmp(subject, "u") == 0) | |
subject = "user"; | |
else if (strcasecmp(subject, "g") == 0) | |
subject = "group"; | |
else if (strcasecmp(subject, "p") == 0) | |
subject = "process"; | |
else if (strcasecmp(subject, "l") == 0 || | |
strcasecmp(subject, "c") == 0 || | |
strcasecmp(subject, "class") == 0) | |
subject = "loginclass"; | |
else if (strcasecmp(subject, "j") == 0) | |
subject = "jail"; | |
if (strcasecmp(subject, "user") == 0 && strlen(textid) > 0) { | |
id = parse_user(textid); | |
asprintf(&resolved, "%s:%d:%s", subject, (int)id, rest); | |
} else if (strcasecmp(subject, "group") == 0 && strlen(textid) > 0) { | |
id = parse_group(textid); | |
asprintf(&resolved, "%s:%d:%s", subject, (int)id, rest); | |
} else | |
asprintf(&resolved, "%s:%s:%s", subject, textid, rest); | |
if (resolved == NULL) | |
err(1, "asprintf"); | |
return (resolved); | |
} | |
/* | |
* This routine replaces "human-readable" number with its expanded form. | |
*/ | |
static char * | |
expand_amount(char *rule) | |
{ | |
uint64_t num; | |
const char *subject, *subject_id, *resource, *action, *amount, *per; | |
char *copy, *expanded; | |
copy = strdup(rule); | |
if (copy == NULL) | |
err(1, "strdup"); | |
subject = strsep(©, ":"); | |
subject_id = strsep(©, ":"); | |
resource = strsep(©, ":"); | |
action = strsep(©, "=/"); | |
amount = strsep(©, "/"); | |
per = copy; | |
if (amount == NULL || strlen(amount) == 0) { | |
free(copy); | |
return (rule); | |
} | |
assert(subject != NULL); | |
assert(subject_id != NULL); | |
assert(resource != NULL); | |
assert(action != NULL); | |
if (expand_number(amount, &num)) | |
err(1, "expand_number"); | |
if (per == NULL) | |
asprintf(&expanded, "%s:%s:%s:%s=%ju", subject, subject_id, | |
resource, action, (uintmax_t)num); | |
else | |
asprintf(&expanded, "%s:%s:%s:%s=%ju/%s", subject, subject_id, | |
resource, action, (uintmax_t)num, per); | |
if (expanded == NULL) | |
err(1, "asprintf"); | |
return (expanded); | |
} | |
static char * | |
humanize_ids(char *rule) | |
{ | |
id_t id; | |
struct passwd *pwd; | |
struct group *grp; | |
const char *subject, *textid, *rest; | |
char *humanized; | |
subject = strsep(&rule, ":"); | |
textid = strsep(&rule, ":"); | |
if (textid == NULL) | |
errx(1, "rule passed from the kernel didn't contain subject"); | |
if (rule != NULL) | |
rest = rule; | |
else | |
rest = ""; | |
/* Replace numerical user and group ids with names. */ | |
if (strcasecmp(subject, "user") == 0) { | |
id = parse_user(textid); | |
pwd = getpwuid(id); | |
if (pwd != NULL) | |
textid = pwd->pw_name; | |
} else if (strcasecmp(subject, "group") == 0) { | |
id = parse_group(textid); | |
grp = getgrgid(id); | |
if (grp != NULL) | |
textid = grp->gr_name; | |
} | |
asprintf(&humanized, "%s:%s:%s", subject, textid, rest); | |
if (humanized == NULL) | |
err(1, "asprintf"); | |
return (humanized); | |
} | |
static int | |
str2int64(const char *str, int64_t *value) | |
{ | |
char *end; | |
if (str == NULL) | |
return (EINVAL); | |
*value = strtoul(str, &end, 10); | |
if ((size_t)(end - str) != strlen(str)) | |
return (EINVAL); | |
return (0); | |
} | |
static char * | |
humanize_amount(char *rule) | |
{ | |
int64_t num; | |
const char *subject, *subject_id, *resource, *action, *amount, *per; | |
char *copy, *humanized, buf[6]; | |
copy = strdup(rule); | |
if (copy == NULL) | |
err(1, "strdup"); | |
subject = strsep(©, ":"); | |
subject_id = strsep(©, ":"); | |
resource = strsep(©, ":"); | |
action = strsep(©, "=/"); | |
amount = strsep(©, "/"); | |
per = copy; | |
if (amount == NULL || strlen(amount) == 0 || | |
str2int64(amount, &num) != 0) { | |
free(copy); | |
return (rule); | |
} | |
assert(subject != NULL); | |
assert(subject_id != NULL); | |
assert(resource != NULL); | |
assert(action != NULL); | |
if (humanize_number(buf, sizeof(buf), num, "", HN_AUTOSCALE, | |
HN_DECIMAL | HN_NOSPACE) == -1) | |
err(1, "humanize_number"); | |
if (per == NULL) | |
asprintf(&humanized, "%s:%s:%s:%s=%s", subject, subject_id, | |
resource, action, buf); | |
else | |
asprintf(&humanized, "%s:%s:%s:%s=%s/%s", subject, subject_id, | |
resource, action, buf, per); | |
if (humanized == NULL) | |
err(1, "asprintf"); | |
return (humanized); | |
} | |
static char * | |
humanize_usage_amount(char *usage) | |
{ | |
int64_t num; | |
const char *resource, *amount; | |
char *copy, *humanized, buf[6]; | |
copy = strdup(usage); | |
if (copy == NULL) | |
err(1, "strdup"); | |
resource = strsep(©, "="); | |
amount = copy; | |
assert(resource != NULL); | |
assert(amount != NULL); | |
if (str2int64(amount, &num) != 0 || | |
humanize_number(buf, sizeof(buf), num, "", HN_AUTOSCALE, | |
HN_DECIMAL | HN_NOSPACE) == -1) { | |
free(copy); | |
return (usage); | |
} | |
asprintf(&humanized, "%s=%s", resource, buf); | |
if (humanized == NULL) | |
err(1, "asprintf"); | |
return (humanized); | |
} | |
static PyObject * | |
py_add_rule(PyObject *dummy, PyObject *args) | |
{ | |
int error; | |
char *rule; | |
PyArg_ParseTuple(args, "s", &rule); | |
rule = resolve_ids(rule); | |
rule = expand_amount(rule); | |
error = rctl_add_rule(rule, strlen(rule) + 1, NULL, 0); | |
if (error != 0) | |
err(1, "rctl_add_rule"); | |
return (Py_BuildValue("i", error)); | |
} | |
static PyObject * | |
py_remove_rule(PyObject *dummy, PyObject *args) | |
{ | |
int error; | |
char *filter; | |
PyArg_ParseTuple(args, "s", &filter); | |
error = rctl_remove_rule(filter, strlen(filter) + 1, NULL, 0); | |
if (error != 0) | |
err(1, "rctl_remove_rule"); | |
return (Py_BuildValue("i", error)); | |
} | |
/* | |
* Print rules, one per line. | |
*/ | |
static PyObject * | |
py_print_rules(char *rules, int hflag, int nflag) | |
{ | |
char *rule; | |
PyObject *newlist = PyList_New(0); | |
while ((rule = strsep(&rules, ",")) != NULL) { | |
if (rule[0] == '\0') | |
break; /* XXX */ | |
if (nflag == 0) | |
rule = humanize_ids(rule); | |
if (hflag) | |
rule = humanize_amount(rule); | |
PyList_Append(newlist, PyString_FromString((const char*)rule)); | |
// printf("%s\n", rule); // This doesn't work from the module | |
} | |
return (newlist); | |
} | |
// char *filter, int hflag, int nflag | |
static PyObject * | |
py_show_rules(PyObject *dummy, PyObject *args) | |
{ | |
char *filter = strdup("::"); | |
int error; | |
char *outbuf = NULL; | |
size_t filterlen, outbuflen = RCTL_DEFAULT_BUFSIZE / 4; | |
PyArg_ParseTuple(args, "s", &filter); | |
if (filter != NULL) { | |
filterlen = strlen(filter) + 1; | |
} else { | |
filterlen = 0; | |
} | |
do { | |
outbuflen *= 4; | |
outbuf = malloc(outbuflen); | |
if (outbuf == NULL) | |
err(1, "malloc"); | |
filter = resolve_ids(filter); | |
filter = expand_amount(filter); | |
error = rctl_get_rules(filter, filterlen, outbuf, outbuflen); | |
if (error && errno != ERANGE) | |
err(1, filter); | |
} while (error && errno == ERANGE); | |
return (py_print_rules(outbuf, 0, 0)); | |
} | |
static PyObject * | |
py_show_limits(PyObject *dummy, PyObject *args) | |
{ | |
char *filter = strdup("::"); | |
int error; | |
char *outbuf = NULL; | |
size_t filterlen, outbuflen = RCTL_DEFAULT_BUFSIZE / 4; | |
PyArg_ParseTuple(args, "s", &filter); | |
if (filter != NULL) { | |
filterlen = strlen(filter) + 1; | |
} else { | |
filterlen = 0; | |
} | |
do { | |
outbuflen *= 4; | |
outbuf = malloc(outbuflen); | |
if (outbuf == NULL) | |
err(1, "malloc"); | |
filter = resolve_ids(filter); | |
filter = expand_amount(filter); | |
error = rctl_get_limits(filter, filterlen, outbuf, outbuflen); | |
if (error && errno != ERANGE) | |
err(1, filter); | |
} while (error && errno == ERANGE); | |
return (py_print_rules(outbuf, 0, 0)); | |
} | |
/* | |
* Query the kernel about a resource usage and print it out. | |
*/ | |
static PyObject * | |
py_show_usage(PyObject *dummy, PyObject *args) | |
{ | |
int hflag = 0; | |
int error; | |
char *tmp; | |
char *outbuf = NULL, *filter = NULL; | |
PyObject *newlist = PyList_New(0); | |
size_t filterlen, outbuflen = RCTL_DEFAULT_BUFSIZE / 4; | |
errno = 0; | |
PyArg_ParseTuple(args, "s", &filter); | |
if (filter != NULL) { | |
filterlen = strlen(filter) + 1; | |
} else { | |
filterlen = 0; | |
} | |
warn("470: %d %s", (int)filterlen, filter); | |
//filter = resolve_ids(filter); | |
errno = 0; | |
do { | |
outbuflen *= 4; | |
outbuf = realloc(outbuf, outbuflen); | |
if (outbuf == NULL) | |
err(1, "realloc"); | |
error = rctl_get_racct(filter, filterlen, outbuf, outbuflen); | |
if (error && errno != ERANGE) | |
err(1, "rctl_get_racct"); | |
} while (error && errno == ERANGE); | |
while ((tmp = strsep(&outbuf, ",")) != NULL) { | |
if (tmp[0] == '\0') | |
break; /* XXX */ | |
if (hflag == 1) | |
tmp = humanize_usage_amount(tmp); | |
PyList_Append(newlist, PyString_FromString((const char*)tmp)); | |
} | |
free(filter); | |
free(outbuf); | |
return (newlist); | |
} | |
/* | |
* Lets get all our methods listed and python symbols defined. | |
*/ | |
static PyMethodDef module_methods[] = { | |
{ "add_rule", (PyCFunction)py_add_rule, METH_VARARGS, NULL }, | |
{ "del_rule", (PyCFunction)py_remove_rule, METH_VARARGS, NULL }, | |
{ "show_rules", (PyCFunction)py_show_rules, METH_VARARGS, NULL }, | |
{ "show_limits", (PyCFunction)py_show_limits, METH_VARARGS, NULL }, | |
{ "show_usage", (PyCFunction)py_show_usage, METH_VARARGS, NULL }, | |
{ NULL, NULL, 0, NULL } | |
}; | |
/* | |
* Let's get python initialized | |
*/ | |
PyMODINIT_FUNC initrctl(void) { | |
Py_InitModule3("rctl", module_methods, "docstring..."); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment