diff options
Diffstat (limited to 'kernel/sysctl.c')
| -rw-r--r-- | kernel/sysctl.c | 588 | 
1 files changed, 424 insertions, 164 deletions
| diff --git a/kernel/sysctl.c b/kernel/sysctl.c index 8686b0f5fc12..4c93486b45d1 100644 --- a/kernel/sysctl.c +++ b/kernel/sysctl.c @@ -52,6 +52,7 @@  #include <linux/slow-work.h>  #include <linux/perf_event.h>  #include <linux/kprobes.h> +#include <linux/pipe_fs_i.h>  #include <asm/uaccess.h>  #include <asm/processor.h> @@ -163,6 +164,27 @@ static int proc_taint(struct ctl_table *table, int write,  			       void __user *buffer, size_t *lenp, loff_t *ppos);  #endif +#ifdef CONFIG_MAGIC_SYSRQ +static int __sysrq_enabled; /* Note: sysrq code ises it's own private copy */ + +static int sysrq_sysctl_handler(ctl_table *table, int write, +				void __user *buffer, size_t *lenp, +				loff_t *ppos) +{ +	int error; + +	error = proc_dointvec(table, write, buffer, lenp, ppos); +	if (error) +		return error; + +	if (write) +		sysrq_toggle_support(__sysrq_enabled); + +	return 0; +} + +#endif +  static struct ctl_table root_table[];  static struct ctl_table_root sysctl_table_root;  static struct ctl_table_header root_table_header = { @@ -567,7 +589,7 @@ static struct ctl_table kern_table[] = {  		.data		= &__sysrq_enabled,  		.maxlen		= sizeof (int),  		.mode		= 0644, -		.proc_handler	= proc_dointvec, +		.proc_handler	= sysrq_sysctl_handler,  	},  #endif  #ifdef CONFIG_PROC_SYSCTL @@ -621,7 +643,7 @@ static struct ctl_table kern_table[] = {  #endif  	{  		.procname	= "userprocess_debug", -		.data		= &sysctl_userprocess_debug, +		.data		= &show_unhandled_signals,  		.maxlen		= sizeof(int),  		.mode		= 0644,  		.proc_handler	= proc_dointvec, @@ -1423,6 +1445,14 @@ static struct ctl_table fs_table[] = {  		.child		= binfmt_misc_table,  	},  #endif +	{ +		.procname	= "pipe-max-pages", +		.data		= &pipe_max_pages, +		.maxlen		= sizeof(int), +		.mode		= 0644, +		.proc_handler	= &proc_dointvec_minmax, +		.extra1		= &two, +	},  /*   * NOTE: do not add new entries to this table unless you have read   * Documentation/sysctl/ctl_unnumbered.txt @@ -1431,7 +1461,8 @@ static struct ctl_table fs_table[] = {  };  static struct ctl_table debug_table[] = { -#if defined(CONFIG_X86) || defined(CONFIG_PPC) || defined(CONFIG_SPARC) +#if defined(CONFIG_X86) || defined(CONFIG_PPC) || defined(CONFIG_SPARC) || \ +    defined(CONFIG_S390)  	{  		.procname	= "exception-trace",  		.data		= &show_unhandled_signals, @@ -2040,8 +2071,132 @@ int proc_dostring(struct ctl_table *table, int write,  			       buffer, lenp, ppos);  } +static size_t proc_skip_spaces(char **buf) +{ +	size_t ret; +	char *tmp = skip_spaces(*buf); +	ret = tmp - *buf; +	*buf = tmp; +	return ret; +} + +static void proc_skip_char(char **buf, size_t *size, const char v) +{ +	while (*size) { +		if (**buf != v) +			break; +		(*size)--; +		(*buf)++; +	} +} + +#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) { +		char __user **buffer = (char __user **)buf; +		if (put_user(c, *buffer)) +			return -EFAULT; +		(*size)--, (*buffer)++; +		*buf = *buffer; +	} +	return 0; +} -static int do_proc_dointvec_conv(int *negp, unsigned long *lvalp, +static int do_proc_dointvec_conv(bool *negp, unsigned long *lvalp,  				 int *valp,  				 int write, void *data)  { @@ -2050,33 +2205,31 @@ static int do_proc_dointvec_conv(int *negp, unsigned long *lvalp,  	} else {  		int val = *valp;  		if (val < 0) { -			*negp = -1; +			*negp = true;  			*lvalp = (unsigned long)-val;  		} else { -			*negp = 0; +			*negp = false;  			*lvalp = (unsigned long)val;  		}  	}  	return 0;  } +static const char proc_wspace_sep[] = { ' ', '\t', '\n' }; +  static int __do_proc_dointvec(void *tbl_data, struct ctl_table *table,  		  int write, void __user *buffer,  		  size_t *lenp, loff_t *ppos, -		  int (*conv)(int *negp, unsigned long *lvalp, int *valp, +		  int (*conv)(bool *negp, unsigned long *lvalp, int *valp,  			      int write, void *data),  		  void *data)  { -#define TMPBUFLEN 21 -	int *i, vleft, first = 1, neg; -	unsigned long lval; -	size_t left, len; +	int *i, vleft, first = 1, err = 0; +	unsigned long page = 0; +	size_t left; +	char *kbuf; -	char buf[TMPBUFLEN], *p; -	char __user *s = buffer; -	 -	if (!tbl_data || !table->maxlen || !*lenp || -	    (*ppos && !write)) { +	if (!tbl_data || !table->maxlen || !*lenp || (*ppos && !write)) {  		*lenp = 0;  		return 0;  	} @@ -2088,89 +2241,69 @@ static int __do_proc_dointvec(void *tbl_data, struct ctl_table *table,  	if (!conv)  		conv = do_proc_dointvec_conv; +	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) { -		if (write) { -			while (left) { -				char c; -				if (get_user(c, s)) -					return -EFAULT; -				if (!isspace(c)) -					break; -				left--; -				s++; -			} -			if (!left) -				break; -			neg = 0; -			len = left; -			if (len > sizeof(buf) - 1) -				len = sizeof(buf) - 1; -			if (copy_from_user(buf, s, len)) -				return -EFAULT; -			buf[len] = 0; -			p = buf; -			if (*p == '-' && left > 1) { -				neg = 1; -				p++; -			} -			if (*p < '0' || *p > '9') -				break; +		unsigned long lval; +		bool neg; -			lval = simple_strtoul(p, &p, 0); +		if (write) { +			left -= proc_skip_spaces(&kbuf); -			len = p-buf; -			if ((len < left) && *p && !isspace(*p)) +			err = proc_get_long(&kbuf, &left, &lval, &neg, +					     proc_wspace_sep, +					     sizeof(proc_wspace_sep), NULL); +			if (err)  				break; -			s += len; -			left -= len; - -			if (conv(&neg, &lval, i, 1, data)) +			if (conv(&neg, &lval, i, 1, data)) { +				err = -EINVAL;  				break; +			}  		} else { -			p = buf; +			if (conv(&neg, &lval, i, 0, data)) { +				err = -EINVAL; +				break; +			}  			if (!first) -				*p++ = '\t'; -	 -			if (conv(&neg, &lval, i, 0, data)) +				err = proc_put_char(&buffer, &left, '\t'); +			if (err) +				break; +			err = proc_put_long(&buffer, &left, lval, neg); +			if (err)  				break; - -			sprintf(p, "%s%lu", neg ? "-" : "", lval); -			len = strlen(buf); -			if (len > left) -				len = left; -			if(copy_to_user(s, buf, len)) -				return -EFAULT; -			left -= len; -			s += len;  		}  	} -	if (!write && !first && left) { -		if(put_user('\n', s)) -			return -EFAULT; -		left--, s++; -	} +	if (!write && !first && left && !err) +		err = proc_put_char(&buffer, &left, '\n'); +	if (write && !err) +		left -= proc_skip_spaces(&kbuf); +free:  	if (write) { -		while (left) { -			char c; -			if (get_user(c, s++)) -				return -EFAULT; -			if (!isspace(c)) -				break; -			left--; -		} +		free_page(page); +		if (first) +			return err ? : -EINVAL;  	} -	if (write && first) -		return -EINVAL;  	*lenp -= left;  	*ppos += *lenp; -	return 0; -#undef TMPBUFLEN +	return err;  }  static int do_proc_dointvec(struct ctl_table *table, int write,  		  void __user *buffer, size_t *lenp, loff_t *ppos, -		  int (*conv)(int *negp, unsigned long *lvalp, int *valp, +		  int (*conv)(bool *negp, unsigned long *lvalp, int *valp,  			      int write, void *data),  		  void *data)  { @@ -2238,8 +2371,8 @@ struct do_proc_dointvec_minmax_conv_param {  	int *max;  }; -static int do_proc_dointvec_minmax_conv(int *negp, unsigned long *lvalp,  -					int *valp,  +static int do_proc_dointvec_minmax_conv(bool *negp, unsigned long *lvalp, +					int *valp,  					int write, void *data)  {  	struct do_proc_dointvec_minmax_conv_param *param = data; @@ -2252,10 +2385,10 @@ static int do_proc_dointvec_minmax_conv(int *negp, unsigned long *lvalp,  	} else {  		int val = *valp;  		if (val < 0) { -			*negp = -1; +			*negp = true;  			*lvalp = (unsigned long)-val;  		} else { -			*negp = 0; +			*negp = false;  			*lvalp = (unsigned long)val;  		}  	} @@ -2295,102 +2428,78 @@ static int __do_proc_doulongvec_minmax(void *data, struct ctl_table *table, int  				     unsigned long convmul,  				     unsigned long convdiv)  { -#define TMPBUFLEN 21 -	unsigned long *i, *min, *max, val; -	int vleft, first=1, neg; -	size_t len, left; -	char buf[TMPBUFLEN], *p; -	char __user *s = buffer; -	 -	if (!data || !table->maxlen || !*lenp || -	    (*ppos && !write)) { +	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++, min++, max++, first=0) { +		unsigned long val; +  		if (write) { -			while (left) { -				char c; -				if (get_user(c, s)) -					return -EFAULT; -				if (!isspace(c)) -					break; -				left--; -				s++; -			} -			if (!left) -				break; -			neg = 0; -			len = left; -			if (len > TMPBUFLEN-1) -				len = TMPBUFLEN-1; -			if (copy_from_user(buf, s, len)) -				return -EFAULT; -			buf[len] = 0; -			p = buf; -			if (*p == '-' && left > 1) { -				neg = 1; -				p++; -			} -			if (*p < '0' || *p > '9') -				break; -			val = simple_strtoul(p, &p, 0) * convmul / convdiv ; -			len = p-buf; -			if ((len < left) && *p && !isspace(*p)) +			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) -				val = -val; -			s += len; -			left -= len; - -			if(neg)  				continue;  			if ((min && val < *min) || (max && val > *max))  				continue;  			*i = val;  		} else { -			p = buf; +			val = convdiv * (*i) / convmul;  			if (!first) -				*p++ = '\t'; -			sprintf(p, "%lu", convdiv * (*i) / convmul); -			len = strlen(buf); -			if (len > left) -				len = left; -			if(copy_to_user(s, buf, len)) -				return -EFAULT; -			left -= len; -			s += len; +				err = proc_put_char(&buffer, &left, '\t'); +			err = proc_put_long(&buffer, &left, val, false); +			if (err) +				break;  		}  	} -	if (!write && !first && left) { -		if(put_user('\n', s)) -			return -EFAULT; -		left--, s++; -	} +	if (!write && !first && left && !err) +		err = proc_put_char(&buffer, &left, '\n'); +	if (write && !err) +		left -= proc_skip_spaces(&kbuf); +free:  	if (write) { -		while (left) { -			char c; -			if (get_user(c, s++)) -				return -EFAULT; -			if (!isspace(c)) -				break; -			left--; -		} +		free_page(page); +		if (first) +			return err ? : -EINVAL;  	} -	if (write && first) -		return -EINVAL;  	*lenp -= left;  	*ppos += *lenp; -	return 0; -#undef TMPBUFLEN +	return err;  }  static int do_proc_doulongvec_minmax(struct ctl_table *table, int write, @@ -2451,7 +2560,7 @@ int proc_doulongvec_ms_jiffies_minmax(struct ctl_table *table, int write,  } -static int do_proc_dointvec_jiffies_conv(int *negp, unsigned long *lvalp, +static int do_proc_dointvec_jiffies_conv(bool *negp, unsigned long *lvalp,  					 int *valp,  					 int write, void *data)  { @@ -2463,10 +2572,10 @@ static int do_proc_dointvec_jiffies_conv(int *negp, unsigned long *lvalp,  		int val = *valp;  		unsigned long lval;  		if (val < 0) { -			*negp = -1; +			*negp = true;  			lval = (unsigned long)-val;  		} else { -			*negp = 0; +			*negp = false;  			lval = (unsigned long)val;  		}  		*lvalp = lval / HZ; @@ -2474,7 +2583,7 @@ static int do_proc_dointvec_jiffies_conv(int *negp, unsigned long *lvalp,  	return 0;  } -static int do_proc_dointvec_userhz_jiffies_conv(int *negp, unsigned long *lvalp, +static int do_proc_dointvec_userhz_jiffies_conv(bool *negp, unsigned long *lvalp,  						int *valp,  						int write, void *data)  { @@ -2486,10 +2595,10 @@ static int do_proc_dointvec_userhz_jiffies_conv(int *negp, unsigned long *lvalp,  		int val = *valp;  		unsigned long lval;  		if (val < 0) { -			*negp = -1; +			*negp = true;  			lval = (unsigned long)-val;  		} else { -			*negp = 0; +			*negp = false;  			lval = (unsigned long)val;  		}  		*lvalp = jiffies_to_clock_t(lval); @@ -2497,7 +2606,7 @@ static int do_proc_dointvec_userhz_jiffies_conv(int *negp, unsigned long *lvalp,  	return 0;  } -static int do_proc_dointvec_ms_jiffies_conv(int *negp, unsigned long *lvalp, +static int do_proc_dointvec_ms_jiffies_conv(bool *negp, unsigned long *lvalp,  					    int *valp,  					    int write, void *data)  { @@ -2507,10 +2616,10 @@ static int do_proc_dointvec_ms_jiffies_conv(int *negp, unsigned long *lvalp,  		int val = *valp;  		unsigned long lval;  		if (val < 0) { -			*negp = -1; +			*negp = true;  			lval = (unsigned long)-val;  		} else { -			*negp = 0; +			*negp = false;  			lval = (unsigned long)val;  		}  		*lvalp = jiffies_to_msecs(lval); @@ -2607,6 +2716,157 @@ static int proc_do_cad_pid(struct ctl_table *table, int write,  	return 0;  } +/** + * proc_do_large_bitmap - read/write from/to a large bitmap + * @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 + * + * The bitmap is stored at table->data and the bitmap length (in bits) + * in table->maxlen. + * + * We use a range comma separated format (e.g. 1,3-4,10-10) so that + * large bitmaps may be represented in a compact manner. Writing into + * the file will clear the bitmap then update it with the given input. + * + * Returns 0 on success. + */ +int proc_do_large_bitmap(struct ctl_table *table, int write, +			 void __user *buffer, size_t *lenp, loff_t *ppos) +{ +	int err = 0; +	bool first = 1; +	size_t left = *lenp; +	unsigned long bitmap_len = table->maxlen; +	unsigned long *bitmap = (unsigned long *) table->data; +	unsigned long *tmp_bitmap = NULL; +	char tr_a[] = { '-', ',', '\n' }, tr_b[] = { ',', '\n', 0 }, c; + +	if (!bitmap_len || !left || (*ppos && !write)) { +		*lenp = 0; +		return 0; +	} + +	if (write) { +		unsigned long page = 0; +		char *kbuf; + +		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)) { +			free_page(page); +			return -EFAULT; +                } +		kbuf[left] = 0; + +		tmp_bitmap = kzalloc(BITS_TO_LONGS(bitmap_len) * sizeof(unsigned long), +				     GFP_KERNEL); +		if (!tmp_bitmap) { +			free_page(page); +			return -ENOMEM; +		} +		proc_skip_char(&kbuf, &left, '\n'); +		while (!err && left) { +			unsigned long val_a, val_b; +			bool neg; + +			err = proc_get_long(&kbuf, &left, &val_a, &neg, tr_a, +					     sizeof(tr_a), &c); +			if (err) +				break; +			if (val_a >= bitmap_len || neg) { +				err = -EINVAL; +				break; +			} + +			val_b = val_a; +			if (left) { +				kbuf++; +				left--; +			} + +			if (c == '-') { +				err = proc_get_long(&kbuf, &left, &val_b, +						     &neg, tr_b, sizeof(tr_b), +						     &c); +				if (err) +					break; +				if (val_b >= bitmap_len || neg || +				    val_a > val_b) { +					err = -EINVAL; +					break; +				} +				if (left) { +					kbuf++; +					left--; +				} +			} + +			while (val_a <= val_b) +				set_bit(val_a++, tmp_bitmap); + +			first = 0; +			proc_skip_char(&kbuf, &left, '\n'); +		} +		free_page(page); +	} else { +		unsigned long bit_a, bit_b = 0; + +		while (left) { +			bit_a = find_next_bit(bitmap, bitmap_len, bit_b); +			if (bit_a >= bitmap_len) +				break; +			bit_b = find_next_zero_bit(bitmap, bitmap_len, +						   bit_a + 1) - 1; + +			if (!first) { +				err = proc_put_char(&buffer, &left, ','); +				if (err) +					break; +			} +			err = proc_put_long(&buffer, &left, bit_a, false); +			if (err) +				break; +			if (bit_a != bit_b) { +				err = proc_put_char(&buffer, &left, '-'); +				if (err) +					break; +				err = proc_put_long(&buffer, &left, bit_b, false); +				if (err) +					break; +			} + +			first = 0; bit_b++; +		} +		if (!err) +			err = proc_put_char(&buffer, &left, '\n'); +	} + +	if (!err) { +		if (write) { +			if (*ppos) +				bitmap_or(bitmap, bitmap, tmp_bitmap, bitmap_len); +			else +				memcpy(bitmap, tmp_bitmap, +					BITS_TO_LONGS(bitmap_len) * sizeof(unsigned long)); +		} +		kfree(tmp_bitmap); +		*lenp -= left; +		*ppos += *lenp; +		return 0; +	} else { +		kfree(tmp_bitmap); +		return err; +	} +} +  #else /* CONFIG_PROC_FS */  int proc_dostring(struct ctl_table *table, int write, | 
