From 7abc58c688b2dfee14cb7d43ab2d457299e35e47 Mon Sep 17 00:00:00 2001 From: John Denker Date: Tue, 8 Oct 2013 11:48:50 -0700 Subject: upgrade sysctl /proc/ interface to handle ulonglong --- include/linux/sysctl.h | 2 + kernel/do_proc_dotype.h | 213 +++++++++++++++++++++++++++++++++++++++ kernel/sysctl.c | 259 ++++++++---------------------------------------- 3 files changed, 258 insertions(+), 216 deletions(-) create mode 100644 kernel/do_proc_dotype.h diff --git a/include/linux/sysctl.h b/include/linux/sysctl.h index 14a8ff2..a4a8098 100644 --- a/include/linux/sysctl.h +++ b/include/linux/sysctl.h @@ -53,6 +53,8 @@ extern int proc_dointvec_ms_jiffies(struct ctl_table *, int, void __user *, size_t *, loff_t *); extern int proc_doulongvec_minmax(struct ctl_table *, int, void __user *, size_t *, loff_t *); +extern int proc_doulonglongvec_minmax(struct ctl_table *, int, + void __user *, size_t *, loff_t *); extern int proc_doulongvec_ms_jiffies_minmax(struct ctl_table *table, int, void __user *, size_t *, loff_t *); extern int proc_do_large_bitmap(struct ctl_table *, int, diff --git a/kernel/do_proc_dotype.h b/kernel/do_proc_dotype.h new file mode 100644 index 0000000..7d30ffe --- /dev/null +++ b/kernel/do_proc_dotype.h @@ -0,0 +1,213 @@ +#define __do_NAME(x) CAT3(__do_proc_do, x, vec_minmax) +#define do_NAME(x) CAT3(do_proc_do, x, vec_minmax) +#define NAME(x) CAT3(proc_do, x, vec_minmax) +#define get_NAME(x) CAT3(proc_get_, x,) +#define put_NAME(x) CAT3(proc_put_, x,) + +#define TMPBUFLEN 22 +/** + * proc_get_long - reads an ASCII formatted integer from a user buffer + * + * @buf: a kernel buffer + * @size: size of the kernel buffer + * @val: this is where the number will be stored + * @neg: set to %TRUE if number is negative + * @perm_tr: a vector which contains the allowed trailers + * @perm_tr_len: size of the perm_tr vector + * @tr: pointer to store the trailer character + * + * In case of success %0 is returned and @buf and @size are updated with + * the amount of bytes read. If @tr is non-NULL and a trailing + * character exists (size is non-zero after returning from this + * function), @tr is updated with the trailing character. + */ +static int get_NAME(sTYPE)(char **buf, size_t *size, + uTYPE *val, bool *neg, + const char *perm_tr, unsigned perm_tr_len, char *tr) +{ + int len; + char *p, tmp[TMPBUFLEN]; + + if (!*size) + return -EINVAL; + + len = *size; + if (len > TMPBUFLEN - 1) + len = TMPBUFLEN - 1; + + memcpy(tmp, *buf, len); + + tmp[len] = 0; + p = tmp; + if (*p == '-' && *size > 1) { + *neg = true; + p++; + } else + *neg = false; + if (!isdigit(*p)) + return -EINVAL; + + *val = STR2TYPE (p, &p, 0); + + len = p - tmp; + + /* We don't know if the next char is whitespace thus we may accept + * invalid integers (e.g. 1234...a) or two integers instead of one + * (e.g. 123...1). So lets not allow such large numbers. */ + if (len == TMPBUFLEN - 1) + return -EINVAL; + + if (len < *size && perm_tr_len && !memchr(perm_tr, *p, perm_tr_len)) + return -EINVAL; + + if (tr && (len < *size)) + *tr = *p; + + *buf += len; + *size -= len; + + return 0; +} + +/** + * proc_put_long - converts an integer to a decimal ASCII formatted string + * + * @buf: the user buffer + * @size: the size of the user buffer + * @val: the integer to be converted + * @neg: sign of the number, %TRUE for negative + * + * In case of success %0 is returned and @buf and @size are updated with + * the amount of bytes written. + */ +static int put_NAME(sTYPE)(void __user **buf, size_t *size, + uTYPE val, bool neg) +{ + int len; + char tmp[TMPBUFLEN], *p = tmp; + + sprintf(p, OFMT, neg ? "-" : "", val); + len = strlen(tmp); + if (len > *size) + len = *size; + if (copy_to_user(*buf, tmp, len)) + return -EFAULT; + *size -= len; + *buf += len; + return 0; +} +#undef TMPBUFLEN + +static int __do_NAME(TYPE)(void *data, struct ctl_table *table, int write, + void __user *buffer, + size_t *lenp, loff_t *ppos, + TYPE convmul, + TYPE convdiv) +{ + TYPE *i, *min, *max; + int vleft, first = 1, err = 0; + unsigned long page = 0; + size_t left; + char *kbuf; + + if (!data || !table->maxlen || !*lenp || (*ppos && !write)) { + *lenp = 0; + return 0; + } + + i = (TYPE *) data; + min = (TYPE *) table->extra1; + max = (TYPE *) table->extra2; + vleft = table->maxlen / sizeof(TYPE); + left = *lenp; + + if (write) { + if (left > PAGE_SIZE - 1) + left = PAGE_SIZE - 1; + page = __get_free_page(GFP_TEMPORARY); + kbuf = (char *) page; + if (!kbuf) + return -ENOMEM; + if (copy_from_user(kbuf, buffer, left)) { + err = -EFAULT; + goto free; + } + kbuf[left] = 0; + } + + for (; left && vleft--; i++, first = 0) { + TYPE val; + + if (write) { + bool neg; + + left -= proc_skip_spaces(&kbuf); + + err = get_NAME(sTYPE)(&kbuf, &left, &val, &neg, + proc_wspace_sep, + sizeof(proc_wspace_sep), NULL); + if (err) + break; + if (neg) + continue; + if ((min && val < *min) || (max && val > *max)) + continue; + *i = val; + } else { + val = convdiv * (*i) / convmul; + if (!first) + err = proc_put_char(&buffer, &left, '\t'); + err = put_NAME(sTYPE)(&buffer, &left, val, false); + if (err) + break; + } + } + + if (!write && !first && left && !err) + err = proc_put_char(&buffer, &left, '\n'); +// FIXME: Why is the following line different from the +// corresponding line in __do_proc_dointvec??? + if (write && !err) + left -= proc_skip_spaces(&kbuf); +free: + if (write) { + free_page(page); + if (first) + return err ? : -EINVAL; + } + *lenp -= left; + *ppos += *lenp; + return err; +} + +static int do_NAME(TYPE)(struct ctl_table *table, int write, + void __user *buffer, + size_t *lenp, loff_t *ppos, + TYPE convmul, + TYPE convdiv) +{ + return __do_NAME(TYPE)(table->data, table, write, + buffer, lenp, ppos, convmul, convdiv); +} + +/** + * proc_doulongvec_minmax - read a vector of long integers with min/max values + * @table: the sysctl table + * @write: %TRUE if this is a write to the sysctl file + * @buffer: the user buffer + * @lenp: the size of the user buffer + * @ppos: file position + * + * Reads/writes up to table->maxlen/sizeof(TYPE) variables of type TYPE + * values from/to the user buffer, treated as an ASCII string. + * + * This routine will ensure the values are within the range specified by + * table->extra1 (min) and table->extra2 (max). + * + * Returns 0 on success. + */ +int NAME(TYPE)(struct ctl_table *table, int write, + void __user *buffer, size_t *lenp, loff_t *ppos) +{ + return do_NAME(TYPE)(table, write, buffer, lenp, ppos, 1l, 1l); +} diff --git a/kernel/sysctl.c b/kernel/sysctl.c index 07f6fc4..a97f159 100644 --- a/kernel/sysctl.c +++ b/kernel/sysctl.c @@ -1122,7 +1122,7 @@ static struct ctl_table vm_table[] = { .proc_handler = proc_dointvec, }, { - .procname = "page-cluster", + .procname = "page-cluster", .data = &page_cluster, .maxlen = sizeof(int), .mode = 0644, @@ -1580,7 +1580,7 @@ static struct ctl_table fs_table[] = { .mode = 0555, .child = inotify_table, }, -#endif +#endif #ifdef CONFIG_EPOLL { .procname = "epoll", @@ -1780,100 +1780,6 @@ static void proc_skip_char(char **buf, size_t *size, const char v) } } -#define TMPBUFLEN 22 -/** - * proc_get_long - reads an ASCII formatted integer from a user buffer - * - * @buf: a kernel buffer - * @size: size of the kernel buffer - * @val: this is where the number will be stored - * @neg: set to %TRUE if number is negative - * @perm_tr: a vector which contains the allowed trailers - * @perm_tr_len: size of the perm_tr vector - * @tr: pointer to store the trailer character - * - * In case of success %0 is returned and @buf and @size are updated with - * the amount of bytes read. If @tr is non-NULL and a trailing - * character exists (size is non-zero after returning from this - * function), @tr is updated with the trailing character. - */ -static int proc_get_long(char **buf, size_t *size, - unsigned long *val, bool *neg, - const char *perm_tr, unsigned perm_tr_len, char *tr) -{ - int len; - char *p, tmp[TMPBUFLEN]; - - if (!*size) - return -EINVAL; - - len = *size; - if (len > TMPBUFLEN - 1) - len = TMPBUFLEN - 1; - - memcpy(tmp, *buf, len); - - tmp[len] = 0; - p = tmp; - if (*p == '-' && *size > 1) { - *neg = true; - p++; - } else - *neg = false; - if (!isdigit(*p)) - return -EINVAL; - - *val = simple_strtoul(p, &p, 0); - - len = p - tmp; - - /* We don't know if the next char is whitespace thus we may accept - * invalid integers (e.g. 1234...a) or two integers instead of one - * (e.g. 123...1). So lets not allow such large numbers. */ - if (len == TMPBUFLEN - 1) - return -EINVAL; - - if (len < *size && perm_tr_len && !memchr(perm_tr, *p, perm_tr_len)) - return -EINVAL; - - if (tr && (len < *size)) - *tr = *p; - - *buf += len; - *size -= len; - - return 0; -} - -/** - * proc_put_long - converts an integer to a decimal ASCII formatted string - * - * @buf: the user buffer - * @size: the size of the user buffer - * @val: the integer to be converted - * @neg: sign of the number, %TRUE for negative - * - * In case of success %0 is returned and @buf and @size are updated with - * the amount of bytes written. - */ -static int proc_put_long(void __user **buf, size_t *size, unsigned long val, - bool neg) -{ - int len; - char tmp[TMPBUFLEN], *p = tmp; - - sprintf(p, "%s%lu", neg ? "-" : "", val); - len = strlen(tmp); - if (len > *size) - len = *size; - if (copy_to_user(*buf, tmp, len)) - return -EFAULT; - *size -= len; - *buf += len; - return 0; -} -#undef TMPBUFLEN - static int proc_put_char(void __user **buf, size_t *size, char c) { if (*size) { @@ -1907,6 +1813,37 @@ static int do_proc_dointvec_conv(bool *negp, unsigned long *lvalp, static const char proc_wspace_sep[] = { ' ', '\t', '\n' }; +#define CAT3x(a,b,c) a ## b ## c +#define CAT3(a,b,c) CAT3x(a,b,c) + +/* per-TYPE stuff: */ +#define TYPE ulong +#define sTYPE long /* signed version of TYPE */ +#define uTYPE ulong /* unsgined version of TYPE */ +#define STR2TYPE simple_strtoul +#define OFMT "%s%lu" +#include "do_proc_dotype.h" +#undef OFMT +#undef STR2TYPE +#undef sTYPE +#undef uTYPE +#undef TYPE + +typedef long long int longlong; +typedef unsigned long long int ulonglong; + +#define TYPE ulonglong +#define sTYPE longlong +#define uTYPE ulonglong +#define STR2TYPE simple_strtoull +#define OFMT "%s%llu" +#include "do_proc_dotype.h" +#undef OFMT +#undef STR2TYPE +#undef sTYPE +#undef uTYPE +#undef TYPE + static int __do_proc_dointvec(void *tbl_data, struct ctl_table *table, int write, void __user *buffer, size_t *lenp, loff_t *ppos, @@ -1918,12 +1855,12 @@ static int __do_proc_dointvec(void *tbl_data, struct ctl_table *table, unsigned long page = 0; size_t left; char *kbuf; - + if (!tbl_data || !table->maxlen || !*lenp || (*ppos && !write)) { *lenp = 0; return 0; } - + i = (int *) tbl_data; vleft = table->maxlen / sizeof(*i); left = *lenp; @@ -1980,6 +1917,8 @@ static int __do_proc_dointvec(void *tbl_data, struct ctl_table *table, if (!write && !first && left && !err) err = proc_put_char(&buffer, &left, '\n'); +// FIXME: Why is the following line different from the +// corresponding line in __do_proc_doulongvec_minmax??? if (write && !err && left) left -= proc_skip_spaces(&kbuf); free: @@ -2012,7 +1951,7 @@ static int do_proc_dointvec(struct ctl_table *table, int write, * @ppos: file position * * Reads/writes up to table->maxlen/sizeof(unsigned int) integer - * values from/to the user buffer, treated as an ASCII string. + * values from/to the user buffer, treated as an ASCII string. * * Returns 0 on success. */ @@ -2157,118 +2096,6 @@ static int proc_dostring_coredump(struct ctl_table *table, int write, } #endif -static int __do_proc_doulongvec_minmax(void *data, struct ctl_table *table, int write, - void __user *buffer, - size_t *lenp, loff_t *ppos, - unsigned long convmul, - unsigned long convdiv) -{ - unsigned long *i, *min, *max; - int vleft, first = 1, err = 0; - unsigned long page = 0; - size_t left; - char *kbuf; - - if (!data || !table->maxlen || !*lenp || (*ppos && !write)) { - *lenp = 0; - return 0; - } - - i = (unsigned long *) data; - min = (unsigned long *) table->extra1; - max = (unsigned long *) table->extra2; - vleft = table->maxlen / sizeof(unsigned long); - left = *lenp; - - if (write) { - if (left > PAGE_SIZE - 1) - left = PAGE_SIZE - 1; - page = __get_free_page(GFP_TEMPORARY); - kbuf = (char *) page; - if (!kbuf) - return -ENOMEM; - if (copy_from_user(kbuf, buffer, left)) { - err = -EFAULT; - goto free; - } - kbuf[left] = 0; - } - - for (; left && vleft--; i++, first = 0) { - unsigned long val; - - if (write) { - bool neg; - - left -= proc_skip_spaces(&kbuf); - - err = proc_get_long(&kbuf, &left, &val, &neg, - proc_wspace_sep, - sizeof(proc_wspace_sep), NULL); - if (err) - break; - if (neg) - continue; - if ((min && val < *min) || (max && val > *max)) - continue; - *i = val; - } else { - val = convdiv * (*i) / convmul; - if (!first) - err = proc_put_char(&buffer, &left, '\t'); - err = proc_put_long(&buffer, &left, val, false); - if (err) - break; - } - } - - if (!write && !first && left && !err) - err = proc_put_char(&buffer, &left, '\n'); - if (write && !err) - left -= proc_skip_spaces(&kbuf); -free: - if (write) { - free_page(page); - if (first) - return err ? : -EINVAL; - } - *lenp -= left; - *ppos += *lenp; - return err; -} - -static int do_proc_doulongvec_minmax(struct ctl_table *table, int write, - void __user *buffer, - size_t *lenp, loff_t *ppos, - unsigned long convmul, - unsigned long convdiv) -{ - return __do_proc_doulongvec_minmax(table->data, table, write, - buffer, lenp, ppos, convmul, convdiv); -} - -/** - * proc_doulongvec_minmax - read a vector of long integers with min/max values - * @table: the sysctl table - * @write: %TRUE if this is a write to the sysctl file - * @buffer: the user buffer - * @lenp: the size of the user buffer - * @ppos: file position - * - * Reads/writes up to table->maxlen/sizeof(unsigned long) unsigned long - * values from/to the user buffer, treated as an ASCII string. - * - * This routine will ensure the values are within the range specified by - * table->extra1 (min) and table->extra2 (max). - * - * Returns 0 on success. - */ -int proc_doulongvec_minmax(struct ctl_table *table, int write, - void __user *buffer, size_t *lenp, loff_t *ppos) -{ - return do_proc_doulongvec_minmax(table, write, buffer, lenp, ppos, 1l, 1l); -} - /** * proc_doulongvec_ms_jiffies_minmax - read a vector of millisecond values with min/max values * @table: the sysctl table @@ -2375,7 +2202,7 @@ static int do_proc_dointvec_ms_jiffies_conv(bool *negp, unsigned long *lvalp, * @ppos: file position * * Reads/writes up to table->maxlen/sizeof(unsigned int) integer - * values from/to the user buffer, treated as an ASCII string. + * values from/to the user buffer, treated as an ASCII string. * The values read are assumed to be in seconds, and are converted into * jiffies. * @@ -2397,8 +2224,8 @@ int proc_dointvec_jiffies(struct ctl_table *table, int write, * @ppos: pointer to the file position * * Reads/writes up to table->maxlen/sizeof(unsigned int) integer - * values from/to the user buffer, treated as an ASCII string. - * The values read are assumed to be in 1/USER_HZ seconds, and + * values from/to the user buffer, treated as an ASCII string. + * The values read are assumed to be in 1/USER_HZ seconds, and * are converted into jiffies. * * Returns 0 on success. @@ -2420,8 +2247,8 @@ int proc_dointvec_userhz_jiffies(struct ctl_table *table, int write, * @ppos: the current position in the file * * Reads/writes up to table->maxlen/sizeof(unsigned int) integer - * values from/to the user buffer, treated as an ASCII string. - * The values read are assumed to be in 1/1000 seconds, and + * values from/to the user buffer, treated as an ASCII string. + * The values read are assumed to be in 1/1000 seconds, and * are converted into jiffies. * * Returns 0 on success. -- cgit v1.2.3