|
@@ -882,7 +882,7 @@ static int sysctl_check_table(struct nsproxy *namespaces, struct ctl_table *tabl
|
|
|
#endif /* CONFIG_SYSCTL_SYSCALL_CHECK */
|
|
|
|
|
|
/**
|
|
|
- * __register_sysctl_table - register a sysctl table
|
|
|
+ * __register_sysctl_table - register a leaf sysctl table
|
|
|
* @root: List of sysctl headers to register on
|
|
|
* @namespaces: Data to compute which lists of sysctl entries are visible
|
|
|
* @path: The path to the directory the sysctl table is in.
|
|
@@ -900,29 +900,19 @@ static int sysctl_check_table(struct nsproxy *namespaces, struct ctl_table *tabl
|
|
|
*
|
|
|
* maxlen - the maximum size in bytes of the data
|
|
|
*
|
|
|
- * mode - the file permissions for the /proc/sys file, and for sysctl(2)
|
|
|
+ * mode - the file permissions for the /proc/sys file
|
|
|
*
|
|
|
- * child - a pointer to the child sysctl table if this entry is a directory, or
|
|
|
- * %NULL.
|
|
|
+ * child - must be %NULL.
|
|
|
*
|
|
|
* proc_handler - the text handler routine (described below)
|
|
|
*
|
|
|
- * de - for internal use by the sysctl routines
|
|
|
- *
|
|
|
* extra1, extra2 - extra pointers usable by the proc handler routines
|
|
|
*
|
|
|
* Leaf nodes in the sysctl tree will be represented by a single file
|
|
|
* under /proc; non-leaf nodes will be represented by directories.
|
|
|
*
|
|
|
- * sysctl(2) can automatically manage read and write requests through
|
|
|
- * the sysctl table. The data and maxlen fields of the ctl_table
|
|
|
- * struct enable minimal validation of the values being written to be
|
|
|
- * performed, and the mode field allows minimal authentication.
|
|
|
- *
|
|
|
- * There must be a proc_handler routine for any terminal nodes
|
|
|
- * mirrored under /proc/sys (non-terminals are handled by a built-in
|
|
|
- * directory handler). Several default handlers are available to
|
|
|
- * cover common cases -
|
|
|
+ * There must be a proc_handler routine for any terminal nodes.
|
|
|
+ * Several default handlers are available to cover common cases -
|
|
|
*
|
|
|
* proc_dostring(), proc_dointvec(), proc_dointvec_jiffies(),
|
|
|
* proc_dointvec_userhz_jiffies(), proc_dointvec_minmax(),
|
|
@@ -1059,6 +1049,100 @@ static char *append_path(const char *path, char *pos, const char *name)
|
|
|
return pos;
|
|
|
}
|
|
|
|
|
|
+static int count_subheaders(struct ctl_table *table)
|
|
|
+{
|
|
|
+ int has_files = 0;
|
|
|
+ int nr_subheaders = 0;
|
|
|
+ struct ctl_table *entry;
|
|
|
+
|
|
|
+ /* special case: no directory and empty directory */
|
|
|
+ if (!table || !table->procname)
|
|
|
+ return 1;
|
|
|
+
|
|
|
+ for (entry = table; entry->procname; entry++) {
|
|
|
+ if (entry->child)
|
|
|
+ nr_subheaders += count_subheaders(entry->child);
|
|
|
+ else
|
|
|
+ has_files = 1;
|
|
|
+ }
|
|
|
+ return nr_subheaders + has_files;
|
|
|
+}
|
|
|
+
|
|
|
+static int register_leaf_sysctl_tables(const char *path, char *pos,
|
|
|
+ struct ctl_table_header ***subheader,
|
|
|
+ struct ctl_table_root *root, struct nsproxy *namespaces,
|
|
|
+ struct ctl_table *table)
|
|
|
+{
|
|
|
+ struct ctl_table *ctl_table_arg = NULL;
|
|
|
+ struct ctl_table *entry, *files;
|
|
|
+ int nr_files = 0;
|
|
|
+ int nr_dirs = 0;
|
|
|
+ int err = -ENOMEM;
|
|
|
+
|
|
|
+ for (entry = table; entry->procname; entry++) {
|
|
|
+ if (entry->child)
|
|
|
+ nr_dirs++;
|
|
|
+ else
|
|
|
+ nr_files++;
|
|
|
+ }
|
|
|
+
|
|
|
+ files = table;
|
|
|
+ /* If there are mixed files and directories we need a new table */
|
|
|
+ if (nr_dirs && nr_files) {
|
|
|
+ struct ctl_table *new;
|
|
|
+ files = kzalloc(sizeof(struct ctl_table) * (nr_files + 1),
|
|
|
+ GFP_KERNEL);
|
|
|
+ if (!files)
|
|
|
+ goto out;
|
|
|
+
|
|
|
+ ctl_table_arg = files;
|
|
|
+ for (new = files, entry = table; entry->procname; entry++) {
|
|
|
+ if (entry->child)
|
|
|
+ continue;
|
|
|
+ *new = *entry;
|
|
|
+ new++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Register everything except a directory full of subdirectories */
|
|
|
+ if (nr_files || !nr_dirs) {
|
|
|
+ struct ctl_table_header *header;
|
|
|
+ header = __register_sysctl_table(root, namespaces, path, files);
|
|
|
+ if (!header) {
|
|
|
+ kfree(ctl_table_arg);
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Remember if we need to free the file table */
|
|
|
+ header->ctl_table_arg = ctl_table_arg;
|
|
|
+ **subheader = header;
|
|
|
+ (*subheader)++;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Recurse into the subdirectories. */
|
|
|
+ for (entry = table; entry->procname; entry++) {
|
|
|
+ char *child_pos;
|
|
|
+
|
|
|
+ if (!entry->child)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ err = -ENAMETOOLONG;
|
|
|
+ child_pos = append_path(path, pos, entry->procname);
|
|
|
+ if (!child_pos)
|
|
|
+ goto out;
|
|
|
+
|
|
|
+ err = register_leaf_sysctl_tables(path, child_pos, subheader,
|
|
|
+ root, namespaces, entry->child);
|
|
|
+ pos[0] = '\0';
|
|
|
+ if (err)
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+ err = 0;
|
|
|
+out:
|
|
|
+ /* On failure our caller will unregister all registered subheaders */
|
|
|
+ return err;
|
|
|
+}
|
|
|
+
|
|
|
/**
|
|
|
* __register_sysctl_paths - register a sysctl table hierarchy
|
|
|
* @root: List of sysctl headers to register on
|
|
@@ -1077,7 +1161,8 @@ struct ctl_table_header *__register_sysctl_paths(
|
|
|
const struct ctl_path *path, struct ctl_table *table)
|
|
|
{
|
|
|
struct ctl_table *ctl_table_arg = table;
|
|
|
- struct ctl_table_header *header = NULL;
|
|
|
+ int nr_subheaders = count_subheaders(table);
|
|
|
+ struct ctl_table_header *header = NULL, **subheaders, **subheader;
|
|
|
const struct ctl_path *component;
|
|
|
char *new_path, *pos;
|
|
|
|
|
@@ -1097,12 +1182,39 @@ struct ctl_table_header *__register_sysctl_paths(
|
|
|
goto out;
|
|
|
table = table->child;
|
|
|
}
|
|
|
- header = __register_sysctl_table(root, namespaces, new_path, table);
|
|
|
- if (header)
|
|
|
+ if (nr_subheaders == 1) {
|
|
|
+ header = __register_sysctl_table(root, namespaces, new_path, table);
|
|
|
+ if (header)
|
|
|
+ header->ctl_table_arg = ctl_table_arg;
|
|
|
+ } else {
|
|
|
+ header = kzalloc(sizeof(*header) +
|
|
|
+ sizeof(*subheaders)*nr_subheaders, GFP_KERNEL);
|
|
|
+ if (!header)
|
|
|
+ goto out;
|
|
|
+
|
|
|
+ subheaders = (struct ctl_table_header **) (header + 1);
|
|
|
+ subheader = subheaders;
|
|
|
header->ctl_table_arg = ctl_table_arg;
|
|
|
+
|
|
|
+ if (register_leaf_sysctl_tables(new_path, pos, &subheader,
|
|
|
+ root, namespaces, table))
|
|
|
+ goto err_register_leaves;
|
|
|
+ }
|
|
|
+
|
|
|
out:
|
|
|
kfree(new_path);
|
|
|
return header;
|
|
|
+
|
|
|
+err_register_leaves:
|
|
|
+ while (subheader > subheaders) {
|
|
|
+ struct ctl_table_header *subh = *(--subheader);
|
|
|
+ struct ctl_table *table = subh->ctl_table_arg;
|
|
|
+ unregister_sysctl_table(subh);
|
|
|
+ kfree(table);
|
|
|
+ }
|
|
|
+ kfree(header);
|
|
|
+ header = NULL;
|
|
|
+ goto out;
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -1149,11 +1261,28 @@ EXPORT_SYMBOL(register_sysctl_table);
|
|
|
*/
|
|
|
void unregister_sysctl_table(struct ctl_table_header * header)
|
|
|
{
|
|
|
+ int nr_subheaders;
|
|
|
might_sleep();
|
|
|
|
|
|
if (header == NULL)
|
|
|
return;
|
|
|
|
|
|
+ nr_subheaders = count_subheaders(header->ctl_table_arg);
|
|
|
+ if (unlikely(nr_subheaders > 1)) {
|
|
|
+ struct ctl_table_header **subheaders;
|
|
|
+ int i;
|
|
|
+
|
|
|
+ subheaders = (struct ctl_table_header **)(header + 1);
|
|
|
+ for (i = nr_subheaders -1; i >= 0; i--) {
|
|
|
+ struct ctl_table_header *subh = subheaders[i];
|
|
|
+ struct ctl_table *table = subh->ctl_table_arg;
|
|
|
+ unregister_sysctl_table(subh);
|
|
|
+ kfree(table);
|
|
|
+ }
|
|
|
+ kfree(header);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
spin_lock(&sysctl_lock);
|
|
|
start_unregistering(header);
|
|
|
if (!--header->parent->count) {
|