]> git.kernelconcepts.de Git - karo-tx-linux.git/blobdiff - kernel/sysctl.c
mei: exclude device from suspend direct complete optimization
[karo-tx-linux.git] / kernel / sysctl.c
index 4dfba1a76cc360f649cbe6f35b57cf9413f2f5e1..6648fbbb8157fc12703d02fa0fdc9ff85c527ac4 100644 (file)
@@ -174,11 +174,32 @@ extern int no_unaligned_warning;
 
 #ifdef CONFIG_PROC_SYSCTL
 
-#define SYSCTL_WRITES_LEGACY   -1
-#define SYSCTL_WRITES_WARN      0
-#define SYSCTL_WRITES_STRICT    1
+/**
+ * enum sysctl_writes_mode - supported sysctl write modes
+ *
+ * @SYSCTL_WRITES_LEGACY: each write syscall must fully contain the sysctl value
+ *     to be written, and multiple writes on the same sysctl file descriptor
+ *     will rewrite the sysctl value, regardless of file position. No warning
+ *     is issued when the initial position is not 0.
+ * @SYSCTL_WRITES_WARN: same as above but warn when the initial file position is
+ *     not 0.
+ * @SYSCTL_WRITES_STRICT: writes to numeric sysctl entries must always be at
+ *     file position 0 and the value must be fully contained in the buffer
+ *     sent to the write syscall. If dealing with strings respect the file
+ *     position, but restrict this to the max length of the buffer, anything
+ *     passed the max lenght will be ignored. Multiple writes will append
+ *     to the buffer.
+ *
+ * These write modes control how current file position affects the behavior of
+ * updating sysctl values through the proc interface on each write.
+ */
+enum sysctl_writes_mode {
+       SYSCTL_WRITES_LEGACY            = -1,
+       SYSCTL_WRITES_WARN              = 0,
+       SYSCTL_WRITES_STRICT            = 1,
+};
 
-static int sysctl_writes_strict = SYSCTL_WRITES_STRICT;
+static enum sysctl_writes_mode sysctl_writes_strict = SYSCTL_WRITES_STRICT;
 
 static int proc_do_cad_pid(struct ctl_table *table, int write,
                  void __user *buffer, size_t *lenp, loff_t *ppos);
@@ -879,6 +900,14 @@ static struct ctl_table kern_table[] = {
                .extra2         = &zero,
 #endif
        },
+       {
+               .procname       = "watchdog_cpumask",
+               .data           = &watchdog_cpumask_bits,
+               .maxlen         = NR_CPUS,
+               .mode           = 0644,
+               .proc_handler   = proc_watchdog_cpumask,
+       },
+#ifdef CONFIG_SOFTLOCKUP_DETECTOR
        {
                .procname       = "soft_watchdog",
                .data           = &soft_watchdog_enabled,
@@ -888,13 +917,6 @@ static struct ctl_table kern_table[] = {
                .extra1         = &zero,
                .extra2         = &one,
        },
-       {
-               .procname       = "watchdog_cpumask",
-               .data           = &watchdog_cpumask_bits,
-               .maxlen         = NR_CPUS,
-               .mode           = 0644,
-               .proc_handler   = proc_watchdog_cpumask,
-       },
        {
                .procname       = "softlockup_panic",
                .data           = &softlockup_panic,
@@ -904,27 +926,29 @@ static struct ctl_table kern_table[] = {
                .extra1         = &zero,
                .extra2         = &one,
        },
-#ifdef CONFIG_HARDLOCKUP_DETECTOR
+#ifdef CONFIG_SMP
        {
-               .procname       = "hardlockup_panic",
-               .data           = &hardlockup_panic,
+               .procname       = "softlockup_all_cpu_backtrace",
+               .data           = &sysctl_softlockup_all_cpu_backtrace,
                .maxlen         = sizeof(int),
                .mode           = 0644,
                .proc_handler   = proc_dointvec_minmax,
                .extra1         = &zero,
                .extra2         = &one,
        },
+#endif /* CONFIG_SMP */
 #endif
-#ifdef CONFIG_SMP
+#ifdef CONFIG_HARDLOCKUP_DETECTOR
        {
-               .procname       = "softlockup_all_cpu_backtrace",
-               .data           = &sysctl_softlockup_all_cpu_backtrace,
+               .procname       = "hardlockup_panic",
+               .data           = &hardlockup_panic,
                .maxlen         = sizeof(int),
                .mode           = 0644,
                .proc_handler   = proc_dointvec_minmax,
                .extra1         = &zero,
                .extra2         = &one,
        },
+#ifdef CONFIG_SMP
        {
                .procname       = "hardlockup_all_cpu_backtrace",
                .data           = &sysctl_hardlockup_all_cpu_backtrace,
@@ -936,6 +960,8 @@ static struct ctl_table kern_table[] = {
        },
 #endif /* CONFIG_SMP */
 #endif
+#endif
+
 #if defined(CONFIG_X86_LOCAL_APIC) && defined(CONFIG_X86)
        {
                .procname       = "unknown_nmi_panic",
@@ -1949,6 +1975,32 @@ static void warn_sysctl_write(struct ctl_table *table)
                current->comm, table->procname);
 }
 
+/**
+ * proc_first_pos_non_zero_ignore - check if firs position is allowed
+ * @ppos: file position
+ * @table: the sysctl table
+ *
+ * Returns true if the first position is non-zero and the sysctl_writes_strict
+ * mode indicates this is not allowed for numeric input types. String proc
+ * hadlers can ignore the return value.
+ */
+static bool proc_first_pos_non_zero_ignore(loff_t *ppos,
+                                          struct ctl_table *table)
+{
+       if (!*ppos)
+               return false;
+
+       switch (sysctl_writes_strict) {
+       case SYSCTL_WRITES_STRICT:
+               return true;
+       case SYSCTL_WRITES_WARN:
+               warn_sysctl_write(table);
+               return false;
+       default:
+               return false;
+       }
+}
+
 /**
  * proc_dostring - read a string sysctl
  * @table: the sysctl table
@@ -1969,8 +2021,8 @@ static void warn_sysctl_write(struct ctl_table *table)
 int proc_dostring(struct ctl_table *table, int write,
                  void __user *buffer, size_t *lenp, loff_t *ppos)
 {
-       if (write && *ppos && sysctl_writes_strict == SYSCTL_WRITES_WARN)
-               warn_sysctl_write(table);
+       if (write)
+               proc_first_pos_non_zero_ignore(ppos, table);
 
        return _proc_do_string((char *)(table->data), table->maxlen, write,
                               (char __user *)buffer, lenp, ppos);
@@ -2128,19 +2180,18 @@ static int do_proc_dointvec_conv(bool *negp, unsigned long *lvalp,
        return 0;
 }
 
-static int do_proc_douintvec_conv(bool *negp, unsigned long *lvalp,
-                                int *valp,
-                                int write, void *data)
+static int do_proc_douintvec_conv(unsigned long *lvalp,
+                                 unsigned int *valp,
+                                 int write, void *data)
 {
        if (write) {
-               if (*negp)
+               if (*lvalp > UINT_MAX)
                        return -EINVAL;
                if (*lvalp > UINT_MAX)
                        return -EINVAL;
                *valp = *lvalp;
        } else {
                unsigned int val = *valp;
-               *negp = false;
                *lvalp = (unsigned long)val;
        }
        return 0;
@@ -2172,17 +2223,8 @@ static int __do_proc_dointvec(void *tbl_data, struct ctl_table *table,
                conv = do_proc_dointvec_conv;
 
        if (write) {
-               if (*ppos) {
-                       switch (sysctl_writes_strict) {
-                       case SYSCTL_WRITES_STRICT:
-                               goto out;
-                       case SYSCTL_WRITES_WARN:
-                               warn_sysctl_write(table);
-                               break;
-                       default:
-                               break;
-                       }
-               }
+               if (proc_first_pos_non_zero_ignore(ppos, table))
+                       goto out;
 
                if (left > PAGE_SIZE - 1)
                        left = PAGE_SIZE - 1;
@@ -2249,6 +2291,146 @@ static int do_proc_dointvec(struct ctl_table *table, int write,
                        buffer, lenp, ppos, conv, data);
 }
 
+static int do_proc_douintvec_w(unsigned int *tbl_data,
+                              struct ctl_table *table,
+                              void __user *buffer,
+                              size_t *lenp, loff_t *ppos,
+                              int (*conv)(unsigned long *lvalp,
+                                          unsigned int *valp,
+                                          int write, void *data),
+                              void *data)
+{
+       unsigned long lval;
+       int err = 0;
+       size_t left;
+       bool neg;
+       char *kbuf = NULL, *p;
+
+       left = *lenp;
+
+       if (proc_first_pos_non_zero_ignore(ppos, table))
+               goto bail_early;
+
+       if (left > PAGE_SIZE - 1)
+               left = PAGE_SIZE - 1;
+
+       p = kbuf = memdup_user_nul(buffer, left);
+       if (IS_ERR(kbuf))
+               return -EINVAL;
+
+       left -= proc_skip_spaces(&p);
+       if (!left) {
+               err = -EINVAL;
+               goto out_free;
+       }
+
+       err = proc_get_long(&p, &left, &lval, &neg,
+                            proc_wspace_sep,
+                            sizeof(proc_wspace_sep), NULL);
+       if (err || neg) {
+               err = -EINVAL;
+               goto out_free;
+       }
+
+       if (conv(&lval, tbl_data, 1, data)) {
+               err = -EINVAL;
+               goto out_free;
+       }
+
+       if (!err && left)
+               left -= proc_skip_spaces(&p);
+
+out_free:
+       kfree(kbuf);
+       if (err)
+               return -EINVAL;
+
+       return 0;
+
+       /* This is in keeping with old __do_proc_dointvec() */
+bail_early:
+       *ppos += *lenp;
+       return err;
+}
+
+static int do_proc_douintvec_r(unsigned int *tbl_data, void __user *buffer,
+                              size_t *lenp, loff_t *ppos,
+                              int (*conv)(unsigned long *lvalp,
+                                          unsigned int *valp,
+                                          int write, void *data),
+                              void *data)
+{
+       unsigned long lval;
+       int err = 0;
+       size_t left;
+
+       left = *lenp;
+
+       if (conv(&lval, tbl_data, 0, data)) {
+               err = -EINVAL;
+               goto out;
+       }
+
+       err = proc_put_long(&buffer, &left, lval, false);
+       if (err || !left)
+               goto out;
+
+       err = proc_put_char(&buffer, &left, '\n');
+
+out:
+       *lenp -= left;
+       *ppos += *lenp;
+
+       return err;
+}
+
+static int __do_proc_douintvec(void *tbl_data, struct ctl_table *table,
+                              int write, void __user *buffer,
+                              size_t *lenp, loff_t *ppos,
+                              int (*conv)(unsigned long *lvalp,
+                                          unsigned int *valp,
+                                          int write, void *data),
+                              void *data)
+{
+       unsigned int *i, vleft;
+
+       if (!tbl_data || !table->maxlen || !*lenp || (*ppos && !write)) {
+               *lenp = 0;
+               return 0;
+       }
+
+       i = (unsigned int *) tbl_data;
+       vleft = table->maxlen / sizeof(*i);
+
+       /*
+        * Arrays are not supported, keep this simple. *Do not* add
+        * support for them.
+        */
+       if (vleft != 1) {
+               *lenp = 0;
+               return -EINVAL;
+       }
+
+       if (!conv)
+               conv = do_proc_douintvec_conv;
+
+       if (write)
+               return do_proc_douintvec_w(i, table, buffer, lenp, ppos,
+                                          conv, data);
+       return do_proc_douintvec_r(i, buffer, lenp, ppos, conv, data);
+}
+
+static int do_proc_douintvec(struct ctl_table *table, int write,
+                            void __user *buffer, size_t *lenp, loff_t *ppos,
+                            int (*conv)(unsigned long *lvalp,
+                                        unsigned int *valp,
+                                        int write, void *data),
+                            void *data)
+{
+       return __do_proc_douintvec(table->data, table, write,
+                                  buffer, lenp, ppos, conv, data);
+}
+
 /**
  * proc_dointvec - read a vector of integers
  * @table: the sysctl table
@@ -2284,8 +2466,8 @@ int proc_dointvec(struct ctl_table *table, int write,
 int proc_douintvec(struct ctl_table *table, int write,
                     void __user *buffer, size_t *lenp, loff_t *ppos)
 {
-       return do_proc_dointvec(table, write, buffer, lenp, ppos,
-                               do_proc_douintvec_conv, NULL);
+       return do_proc_douintvec(table, write, buffer, lenp, ppos,
+                                do_proc_douintvec_conv, NULL);
 }
 
 /*
@@ -2390,6 +2572,65 @@ int proc_dointvec_minmax(struct ctl_table *table, int write,
                                do_proc_dointvec_minmax_conv, &param);
 }
 
+struct do_proc_douintvec_minmax_conv_param {
+       unsigned int *min;
+       unsigned int *max;
+};
+
+static int do_proc_douintvec_minmax_conv(unsigned long *lvalp,
+                                        unsigned int *valp,
+                                        int write, void *data)
+{
+       struct do_proc_douintvec_minmax_conv_param *param = data;
+
+       if (write) {
+               unsigned int val = *lvalp;
+
+               if ((param->min && *param->min > val) ||
+                   (param->max && *param->max < val))
+                       return -ERANGE;
+
+               if (*lvalp > UINT_MAX)
+                       return -EINVAL;
+               *valp = val;
+       } else {
+               unsigned int val = *valp;
+               *lvalp = (unsigned long) val;
+       }
+
+       return 0;
+}
+
+/**
+ * proc_douintvec_minmax - read a vector of unsigned ints 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) unsigned integer
+ * values from/to the user buffer, treated as an ASCII string. Negative
+ * strings are not allowed.
+ *
+ * This routine will ensure the values are within the range specified by
+ * table->extra1 (min) and table->extra2 (max). There is a final sanity
+ * check for UINT_MAX to avoid having to support wrap around uses from
+ * userspace.
+ *
+ * Returns 0 on success.
+ */
+int proc_douintvec_minmax(struct ctl_table *table, int write,
+                         void __user *buffer, size_t *lenp, loff_t *ppos)
+{
+       struct do_proc_douintvec_minmax_conv_param param = {
+               .min = (unsigned int *) table->extra1,
+               .max = (unsigned int *) table->extra2,
+       };
+       return do_proc_douintvec(table, write, buffer, lenp, ppos,
+                                do_proc_douintvec_minmax_conv, &param);
+}
+
 static void validate_coredump_safety(void)
 {
 #ifdef CONFIG_COREDUMP
@@ -2447,17 +2688,8 @@ static int __do_proc_doulongvec_minmax(void *data, struct ctl_table *table, int
        left = *lenp;
 
        if (write) {
-               if (*ppos) {
-                       switch (sysctl_writes_strict) {
-                       case SYSCTL_WRITES_STRICT:
-                               goto out;
-                       case SYSCTL_WRITES_WARN:
-                               warn_sysctl_write(table);
-                               break;
-                       default:
-                               break;
-                       }
-               }
+               if (proc_first_pos_non_zero_ignore(ppos, table))
+                       goto out;
 
                if (left > PAGE_SIZE - 1)
                        left = PAGE_SIZE - 1;
@@ -2898,6 +3130,12 @@ int proc_dointvec_minmax(struct ctl_table *table, int write,
        return -ENOSYS;
 }
 
+int proc_douintvec_minmax(struct ctl_table *table, int write,
+                         void __user *buffer, size_t *lenp, loff_t *ppos)
+{
+       return -ENOSYS;
+}
+
 int proc_dointvec_jiffies(struct ctl_table *table, int write,
                    void __user *buffer, size_t *lenp, loff_t *ppos)
 {
@@ -2940,6 +3178,7 @@ EXPORT_SYMBOL(proc_dointvec);
 EXPORT_SYMBOL(proc_douintvec);
 EXPORT_SYMBOL(proc_dointvec_jiffies);
 EXPORT_SYMBOL(proc_dointvec_minmax);
+EXPORT_SYMBOL_GPL(proc_douintvec_minmax);
 EXPORT_SYMBOL(proc_dointvec_userhz_jiffies);
 EXPORT_SYMBOL(proc_dointvec_ms_jiffies);
 EXPORT_SYMBOL(proc_dostring);