Skip to content
Snippets Groups Projects
sysctl.c 61.6 KiB
Newer Older
  • Learn to ignore specific revisions
  • Linus Torvalds's avatar
    Linus Torvalds committed
    		if (len > *lenp)
    			len = *lenp;
    		if (len)
    
    			if(copy_to_user(buffer, data, len))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    				return -EFAULT;
    		if (len < *lenp) {
    			if(put_user('\n', ((char __user *) buffer) + len))
    				return -EFAULT;
    			len++;
    		}
    		*lenp = len;
    		*ppos += len;
    	}
    	return 0;
    }
    
    
    /**
     * proc_dostring - read a string sysctl
     * @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 a string from/to the user buffer. If the kernel
     * buffer provided is not large enough to hold the string, the
     * string is truncated. The copied string is %NULL-terminated.
     * If the string is being read by the user process, it is copied
     * and a newline '\n' is added. It is truncated if the buffer is
     * not large enough.
     *
     * Returns 0 on success.
     */
    
    int proc_dostring(struct ctl_table *table, int write,
    
    		  void __user *buffer, size_t *lenp, loff_t *ppos)
    {
    
    	return _proc_do_string(table->data, table->maxlen, write,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    static int do_proc_dointvec_conv(int *negp, unsigned long *lvalp,
    				 int *valp,
    				 int write, void *data)
    {
    	if (write) {
    		*valp = *negp ? -*lvalp : *lvalp;
    	} else {
    		int val = *valp;
    		if (val < 0) {
    			*negp = -1;
    			*lvalp = (unsigned long)-val;
    		} else {
    			*negp = 0;
    			*lvalp = (unsigned long)val;
    		}
    	}
    	return 0;
    }
    
    
    static int __do_proc_dointvec(void *tbl_data, struct ctl_table *table,
    
    		  int write, void __user *buffer,
    
    		  size_t *lenp, loff_t *ppos,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		  int (*conv)(int *negp, unsigned long *lvalp, int *valp,
    			      int write, void *data),
    		  void *data)
    {
    #define TMPBUFLEN 21
    
    	int *i, vleft, first = 1, neg;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	unsigned long lval;
    	size_t left, len;
    	
    	char buf[TMPBUFLEN], *p;
    	char __user *s = buffer;
    	
    
    	if (!tbl_data || !table->maxlen || !*lenp ||
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	    (*ppos && !write)) {
    		*lenp = 0;
    		return 0;
    	}
    	
    
    	i = (int *) tbl_data;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	vleft = table->maxlen / sizeof(*i);
    	left = *lenp;
    
    	if (!conv)
    		conv = do_proc_dointvec_conv;
    
    	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;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			}
    			if (*p < '0' || *p > '9')
    				break;
    
    			lval = simple_strtoul(p, &p, 0);
    
    			len = p-buf;
    			if ((len < left) && *p && !isspace(*p))
    				break;
    			s += len;
    			left -= len;
    
    			if (conv(&neg, &lval, i, 1, data))
    				break;
    		} else {
    			p = buf;
    			if (!first)
    				*p++ = '\t';
    	
    			if (conv(&neg, &lval, i, 0, data))
    				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) {
    		while (left) {
    			char c;
    			if (get_user(c, s++))
    				return -EFAULT;
    			if (!isspace(c))
    				break;
    			left--;
    		}
    	}
    	if (write && first)
    		return -EINVAL;
    	*lenp -= left;
    	*ppos += *lenp;
    	return 0;
    #undef TMPBUFLEN
    }
    
    
    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 write, void *data),
    		  void *data)
    {
    
    	return __do_proc_dointvec(table->data, table, write,
    
    			buffer, lenp, ppos, conv, data);
    }
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    /**
     * proc_dointvec - read a vector of integers
     * @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 int) integer
     * values from/to the user buffer, treated as an ASCII string. 
     *
     * Returns 0 on success.
     */
    
    int proc_dointvec(struct ctl_table *table, int write,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		     void __user *buffer, size_t *lenp, loff_t *ppos)
    {
    
        return do_proc_dointvec(table,write,buffer,lenp,ppos,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		    	    NULL,NULL);
    }
    
    
     * Taint values can only be increased
     * This means we can safely use a temporary.
    
    static int proc_taint(struct ctl_table *table, int write,
    
    			       void __user *buffer, size_t *lenp, loff_t *ppos)
    {
    
    	struct ctl_table t;
    	unsigned long tmptaint = get_taint();
    	int err;
    
    	if (write && !capable(CAP_SYS_ADMIN))
    
    	t = *table;
    	t.data = &tmptaint;
    
    	err = proc_doulongvec_minmax(&t, write, buffer, lenp, ppos);
    
    	if (err < 0)
    		return err;
    
    	if (write) {
    		/*
    		 * Poor man's atomic or. Not worth adding a primitive
    		 * to everyone's atomic.h for this
    		 */
    		int i;
    		for (i = 0; i < BITS_PER_LONG && tmptaint >> i; i++) {
    			if ((tmptaint >> i) & 1)
    				add_taint(i);
    		}
    	}
    
    	return err;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    struct do_proc_dointvec_minmax_conv_param {
    	int *min;
    	int *max;
    };
    
    static int do_proc_dointvec_minmax_conv(int *negp, unsigned long *lvalp, 
    					int *valp, 
    					int write, void *data)
    {
    	struct do_proc_dointvec_minmax_conv_param *param = data;
    	if (write) {
    		int val = *negp ? -*lvalp : *lvalp;
    		if ((param->min && *param->min > val) ||
    		    (param->max && *param->max < val))
    			return -EINVAL;
    		*valp = val;
    	} else {
    		int val = *valp;
    		if (val < 0) {
    			*negp = -1;
    			*lvalp = (unsigned long)-val;
    		} else {
    			*negp = 0;
    			*lvalp = (unsigned long)val;
    		}
    	}
    	return 0;
    }
    
    /**
     * proc_dointvec_minmax - read a vector of 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 int) integer
     * 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_dointvec_minmax(struct ctl_table *table, int write,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		  void __user *buffer, size_t *lenp, loff_t *ppos)
    {
    	struct do_proc_dointvec_minmax_conv_param param = {
    		.min = (int *) table->extra1,
    		.max = (int *) table->extra2,
    	};
    
    	return do_proc_dointvec(table, write, buffer, lenp, ppos,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    				do_proc_dointvec_minmax_conv, &param);
    }
    
    
    static int __do_proc_doulongvec_minmax(void *data, struct ctl_table *table, int write,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    				     void __user *buffer,
    				     size_t *lenp, loff_t *ppos,
    				     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 ||
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	    (*ppos && !write)) {
    		*lenp = 0;
    		return 0;
    	}
    	
    
    	i = (unsigned long *) data;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	min = (unsigned long *) table->extra1;
    	max = (unsigned long *) table->extra2;
    	vleft = table->maxlen / sizeof(unsigned long);
    	left = *lenp;
    	
    	for (; left && vleft--; i++, min++, max++, 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 > 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;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			}
    			if (*p < '0' || *p > '9')
    				break;
    			val = simple_strtoul(p, &p, 0) * convmul / convdiv ;
    			len = p-buf;
    			if ((len < left) && *p && !isspace(*p))
    				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;
    			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;
    		}
    	}
    
    	if (!write && !first && left) {
    		if(put_user('\n', s))
    			return -EFAULT;
    		left--, s++;
    	}
    	if (write) {
    		while (left) {
    			char c;
    			if (get_user(c, s++))
    				return -EFAULT;
    			if (!isspace(c))
    				break;
    			left--;
    		}
    	}
    	if (write && first)
    		return -EINVAL;
    	*lenp -= left;
    	*ppos += *lenp;
    	return 0;
    #undef TMPBUFLEN
    }
    
    
    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);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    /**
     * 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,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			   void __user *buffer, size_t *lenp, loff_t *ppos)
    {
    
        return do_proc_doulongvec_minmax(table, write, buffer, lenp, ppos, 1l, 1l);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    }
    
    /**
     * proc_doulongvec_ms_jiffies_minmax - read a vector of millisecond values 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. The values
     * are treated as milliseconds, and converted to jiffies when they are stored.
     *
     * 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_ms_jiffies_minmax(struct ctl_table *table, int write,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    				      void __user *buffer,
    				      size_t *lenp, loff_t *ppos)
    {
    
        return do_proc_doulongvec_minmax(table, write, buffer,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    				     lenp, ppos, HZ, 1000l);
    }
    
    
    static int do_proc_dointvec_jiffies_conv(int *negp, unsigned long *lvalp,
    					 int *valp,
    					 int write, void *data)
    {
    	if (write) {
    
    		if (*lvalp > LONG_MAX / HZ)
    			return 1;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		*valp = *negp ? -(*lvalp*HZ) : (*lvalp*HZ);
    	} else {
    		int val = *valp;
    		unsigned long lval;
    		if (val < 0) {
    			*negp = -1;
    			lval = (unsigned long)-val;
    		} else {
    			*negp = 0;
    			lval = (unsigned long)val;
    		}
    		*lvalp = lval / HZ;
    	}
    	return 0;
    }
    
    static int do_proc_dointvec_userhz_jiffies_conv(int *negp, unsigned long *lvalp,
    						int *valp,
    						int write, void *data)
    {
    	if (write) {
    
    		if (USER_HZ < HZ && *lvalp > (LONG_MAX / HZ) * USER_HZ)
    			return 1;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		*valp = clock_t_to_jiffies(*negp ? -*lvalp : *lvalp);
    	} else {
    		int val = *valp;
    		unsigned long lval;
    		if (val < 0) {
    			*negp = -1;
    			lval = (unsigned long)-val;
    		} else {
    			*negp = 0;
    			lval = (unsigned long)val;
    		}
    		*lvalp = jiffies_to_clock_t(lval);
    	}
    	return 0;
    }
    
    static int do_proc_dointvec_ms_jiffies_conv(int *negp, unsigned long *lvalp,
    					    int *valp,
    					    int write, void *data)
    {
    	if (write) {
    		*valp = msecs_to_jiffies(*negp ? -*lvalp : *lvalp);
    	} else {
    		int val = *valp;
    		unsigned long lval;
    		if (val < 0) {
    			*negp = -1;
    			lval = (unsigned long)-val;
    		} else {
    			*negp = 0;
    			lval = (unsigned long)val;
    		}
    		*lvalp = jiffies_to_msecs(lval);
    	}
    	return 0;
    }
    
    /**
     * proc_dointvec_jiffies - read a vector of integers as seconds
     * @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 int) integer
     * 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.
     *
     * Returns 0 on success.
     */
    
    int proc_dointvec_jiffies(struct ctl_table *table, int write,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			  void __user *buffer, size_t *lenp, loff_t *ppos)
    {
    
        return do_proc_dointvec(table,write,buffer,lenp,ppos,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		    	    do_proc_dointvec_jiffies_conv,NULL);
    }
    
    /**
     * proc_dointvec_userhz_jiffies - read a vector of integers as 1/USER_HZ seconds
     * @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: pointer to the file position
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     *
     * 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 
     * are converted into jiffies.
     *
     * Returns 0 on success.
     */
    
    int proc_dointvec_userhz_jiffies(struct ctl_table *table, int write,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    				 void __user *buffer, size_t *lenp, loff_t *ppos)
    {
    
        return do_proc_dointvec(table,write,buffer,lenp,ppos,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		    	    do_proc_dointvec_userhz_jiffies_conv,NULL);
    }
    
    /**
     * proc_dointvec_ms_jiffies - read a vector of integers as 1 milliseconds
     * @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
     * @ppos: the current position in the file
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     *
     * 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 
     * are converted into jiffies.
     *
     * Returns 0 on success.
     */
    
    int proc_dointvec_ms_jiffies(struct ctl_table *table, int write,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			     void __user *buffer, size_t *lenp, loff_t *ppos)
    {
    
    	return do_proc_dointvec(table, write, buffer, lenp, ppos,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    				do_proc_dointvec_ms_jiffies_conv, NULL);
    }
    
    
    static int proc_do_cad_pid(struct ctl_table *table, int write,
    
    			   void __user *buffer, size_t *lenp, loff_t *ppos)
    {
    	struct pid *new_pid;
    	pid_t tmp;
    	int r;
    
    
    	tmp = pid_vnr(cad_pid);
    
    	r = __do_proc_dointvec(&tmp, table, write, buffer,
    
    			       lenp, ppos, NULL, NULL);
    	if (r || !write)
    		return r;
    
    	new_pid = find_get_pid(tmp);
    	if (!new_pid)
    		return -ESRCH;
    
    	put_pid(xchg(&cad_pid, new_pid));
    	return 0;
    }
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #else /* CONFIG_PROC_FS */
    
    
    int proc_dostring(struct ctl_table *table, int write,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		  void __user *buffer, size_t *lenp, loff_t *ppos)
    {
    	return -ENOSYS;
    }
    
    
    int proc_dointvec(struct ctl_table *table, int write,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		  void __user *buffer, size_t *lenp, loff_t *ppos)
    {
    	return -ENOSYS;
    }
    
    
    int proc_dointvec_minmax(struct ctl_table *table, int write,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		    void __user *buffer, size_t *lenp, loff_t *ppos)
    {
    	return -ENOSYS;
    }
    
    
    int proc_dointvec_jiffies(struct ctl_table *table, int write,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		    void __user *buffer, size_t *lenp, loff_t *ppos)
    {
    	return -ENOSYS;
    }
    
    
    int proc_dointvec_userhz_jiffies(struct ctl_table *table, int write,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		    void __user *buffer, size_t *lenp, loff_t *ppos)
    {
    	return -ENOSYS;
    }
    
    
    int proc_dointvec_ms_jiffies(struct ctl_table *table, int write,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			     void __user *buffer, size_t *lenp, loff_t *ppos)
    {
    	return -ENOSYS;
    }
    
    
    int proc_doulongvec_minmax(struct ctl_table *table, int write,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		    void __user *buffer, size_t *lenp, loff_t *ppos)
    {
    	return -ENOSYS;
    }
    
    
    int proc_doulongvec_ms_jiffies_minmax(struct ctl_table *table, int write,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    				      void __user *buffer,
    				      size_t *lenp, loff_t *ppos)
    {
        return -ENOSYS;
    }
    
    
    #endif /* CONFIG_PROC_FS */
    
    /*
     * No sense putting this after each symbol definition, twice,
     * exception granted :-)
     */
    EXPORT_SYMBOL(proc_dointvec);
    EXPORT_SYMBOL(proc_dointvec_jiffies);
    EXPORT_SYMBOL(proc_dointvec_minmax);
    EXPORT_SYMBOL(proc_dointvec_userhz_jiffies);
    EXPORT_SYMBOL(proc_dointvec_ms_jiffies);
    EXPORT_SYMBOL(proc_dostring);
    EXPORT_SYMBOL(proc_doulongvec_minmax);
    EXPORT_SYMBOL(proc_doulongvec_ms_jiffies_minmax);
    EXPORT_SYMBOL(register_sysctl_table);
    
    EXPORT_SYMBOL(register_sysctl_paths);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    EXPORT_SYMBOL(unregister_sysctl_table);