diff options
-rw-r--r-- | fs/sysfs/dir.c | 219 | ||||
-rw-r--r-- | fs/sysfs/sysfs.h | 9 |
2 files changed, 120 insertions, 108 deletions
diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c index 7fdf6a7b7436..0daf255b7bf9 100644 --- a/fs/sysfs/dir.c +++ b/fs/sysfs/dir.c | |||
@@ -22,76 +22,103 @@ | |||
22 | #include <linux/mutex.h> | 22 | #include <linux/mutex.h> |
23 | #include <linux/slab.h> | 23 | #include <linux/slab.h> |
24 | #include <linux/security.h> | 24 | #include <linux/security.h> |
25 | #include <linux/hash.h> | ||
25 | #include "sysfs.h" | 26 | #include "sysfs.h" |
26 | 27 | ||
27 | DEFINE_MUTEX(sysfs_mutex); | 28 | DEFINE_MUTEX(sysfs_mutex); |
28 | DEFINE_SPINLOCK(sysfs_assoc_lock); | 29 | DEFINE_SPINLOCK(sysfs_assoc_lock); |
29 | 30 | ||
31 | #define to_sysfs_dirent(X) rb_entry((X), struct sysfs_dirent, s_rb); | ||
32 | |||
30 | static DEFINE_SPINLOCK(sysfs_ino_lock); | 33 | static DEFINE_SPINLOCK(sysfs_ino_lock); |
31 | static DEFINE_IDA(sysfs_ino_ida); | 34 | static DEFINE_IDA(sysfs_ino_ida); |
32 | 35 | ||
33 | /** | 36 | /** |
34 | * sysfs_link_sibling - link sysfs_dirent into sibling list | 37 | * sysfs_name_hash |
38 | * @ns: Namespace tag to hash | ||
39 | * @name: Null terminated string to hash | ||
40 | * | ||
41 | * Returns 31 bit hash of ns + name (so it fits in an off_t ) | ||
42 | */ | ||
43 | static unsigned int sysfs_name_hash(const void *ns, const char *name) | ||
44 | { | ||
45 | unsigned long hash = init_name_hash(); | ||
46 | unsigned int len = strlen(name); | ||
47 | while (len--) | ||
48 | hash = partial_name_hash(*name++, hash); | ||
49 | hash = ( end_name_hash(hash) ^ hash_ptr( (void *)ns, 31 ) ); | ||
50 | hash &= 0x7fffffffU; | ||
51 | /* Reserve hash numbers 0, 1 and INT_MAX for magic directory entries */ | ||
52 | if (hash < 1) | ||
53 | hash += 2; | ||
54 | if (hash >= INT_MAX) | ||
55 | hash = INT_MAX - 1; | ||
56 | return hash; | ||
57 | } | ||
58 | |||
59 | static int sysfs_name_compare(unsigned int hash, const void *ns, | ||
60 | const char *name, const struct sysfs_dirent *sd) | ||
61 | { | ||
62 | if (hash != sd->s_hash) | ||
63 | return hash - sd->s_hash; | ||
64 | if (ns != sd->s_ns) | ||
65 | return ns - sd->s_ns; | ||
66 | return strcmp(name, sd->s_name); | ||
67 | } | ||
68 | |||
69 | static int sysfs_sd_compare(const struct sysfs_dirent *left, | ||
70 | const struct sysfs_dirent *right) | ||
71 | { | ||
72 | return sysfs_name_compare(left->s_hash, left->s_ns, left->s_name, | ||
73 | right); | ||
74 | } | ||
75 | |||
76 | /** | ||
77 | * sysfs_link_subling - link sysfs_dirent into sibling rbtree | ||
35 | * @sd: sysfs_dirent of interest | 78 | * @sd: sysfs_dirent of interest |
36 | * | 79 | * |
37 | * Link @sd into its sibling list which starts from | 80 | * Link @sd into its sibling rbtree which starts from |
38 | * sd->s_parent->s_dir.children. | 81 | * sd->s_parent->s_dir.children. |
39 | * | 82 | * |
40 | * Locking: | 83 | * Locking: |
41 | * mutex_lock(sysfs_mutex) | 84 | * mutex_lock(sysfs_mutex) |
85 | * | ||
86 | * RETURNS: | ||
87 | * 0 on susccess -EEXIST on failure. | ||
42 | */ | 88 | */ |
43 | static void sysfs_link_sibling(struct sysfs_dirent *sd) | 89 | static int sysfs_link_sibling(struct sysfs_dirent *sd) |
44 | { | 90 | { |
45 | struct sysfs_dirent *parent_sd = sd->s_parent; | 91 | struct rb_node **node = &sd->s_parent->s_dir.children.rb_node; |
46 | 92 | struct rb_node *parent = NULL; | |
47 | struct rb_node **p; | ||
48 | struct rb_node *parent; | ||
49 | 93 | ||
50 | if (sysfs_type(sd) == SYSFS_DIR) | 94 | if (sysfs_type(sd) == SYSFS_DIR) |
51 | parent_sd->s_dir.subdirs++; | 95 | sd->s_parent->s_dir.subdirs++; |
52 | 96 | ||
53 | p = &parent_sd->s_dir.inode_tree.rb_node; | 97 | while (*node) { |
54 | parent = NULL; | 98 | struct sysfs_dirent *pos; |
55 | while (*p) { | 99 | int result; |
56 | parent = *p; | 100 | |
57 | #define node rb_entry(parent, struct sysfs_dirent, inode_node) | 101 | pos = to_sysfs_dirent(*node); |
58 | if (sd->s_ino < node->s_ino) { | 102 | parent = *node; |
59 | p = &node->inode_node.rb_left; | 103 | result = sysfs_sd_compare(sd, pos); |
60 | } else if (sd->s_ino > node->s_ino) { | 104 | if (result < 0) |
61 | p = &node->inode_node.rb_right; | 105 | node = &pos->s_rb.rb_left; |
62 | } else { | 106 | else if (result > 0) |
63 | printk(KERN_CRIT "sysfs: inserting duplicate inode '%lx'\n", | 107 | node = &pos->s_rb.rb_right; |
64 | (unsigned long) sd->s_ino); | 108 | else |
65 | BUG(); | 109 | return -EEXIST; |
66 | } | ||
67 | #undef node | ||
68 | } | ||
69 | rb_link_node(&sd->inode_node, parent, p); | ||
70 | rb_insert_color(&sd->inode_node, &parent_sd->s_dir.inode_tree); | ||
71 | |||
72 | p = &parent_sd->s_dir.name_tree.rb_node; | ||
73 | parent = NULL; | ||
74 | while (*p) { | ||
75 | int c; | ||
76 | parent = *p; | ||
77 | #define node rb_entry(parent, struct sysfs_dirent, name_node) | ||
78 | c = strcmp(sd->s_name, node->s_name); | ||
79 | if (c < 0) { | ||
80 | p = &node->name_node.rb_left; | ||
81 | } else { | ||
82 | p = &node->name_node.rb_right; | ||
83 | } | ||
84 | #undef node | ||
85 | } | 110 | } |
86 | rb_link_node(&sd->name_node, parent, p); | 111 | /* add new node and rebalance the tree */ |
87 | rb_insert_color(&sd->name_node, &parent_sd->s_dir.name_tree); | 112 | rb_link_node(&sd->s_rb, parent, node); |
113 | rb_insert_color(&sd->s_rb, &sd->s_parent->s_dir.children); | ||
114 | return 0; | ||
88 | } | 115 | } |
89 | 116 | ||
90 | /** | 117 | /** |
91 | * sysfs_unlink_sibling - unlink sysfs_dirent from sibling list | 118 | * sysfs_unlink_sibling - unlink sysfs_dirent from sibling rbtree |
92 | * @sd: sysfs_dirent of interest | 119 | * @sd: sysfs_dirent of interest |
93 | * | 120 | * |
94 | * Unlink @sd from its sibling list which starts from | 121 | * Unlink @sd from its sibling rbtree which starts from |
95 | * sd->s_parent->s_dir.children. | 122 | * sd->s_parent->s_dir.children. |
96 | * | 123 | * |
97 | * Locking: | 124 | * Locking: |
@@ -102,8 +129,7 @@ static void sysfs_unlink_sibling(struct sysfs_dirent *sd) | |||
102 | if (sysfs_type(sd) == SYSFS_DIR) | 129 | if (sysfs_type(sd) == SYSFS_DIR) |
103 | sd->s_parent->s_dir.subdirs--; | 130 | sd->s_parent->s_dir.subdirs--; |
104 | 131 | ||
105 | rb_erase(&sd->inode_node, &sd->s_parent->s_dir.inode_tree); | 132 | rb_erase(&sd->s_rb, &sd->s_parent->s_dir.children); |
106 | rb_erase(&sd->name_node, &sd->s_parent->s_dir.name_tree); | ||
107 | } | 133 | } |
108 | 134 | ||
109 | /** | 135 | /** |
@@ -402,6 +428,7 @@ void sysfs_addrm_start(struct sysfs_addrm_cxt *acxt, | |||
402 | int __sysfs_add_one(struct sysfs_addrm_cxt *acxt, struct sysfs_dirent *sd) | 428 | int __sysfs_add_one(struct sysfs_addrm_cxt *acxt, struct sysfs_dirent *sd) |
403 | { | 429 | { |
404 | struct sysfs_inode_attrs *ps_iattr; | 430 | struct sysfs_inode_attrs *ps_iattr; |
431 | int ret; | ||
405 | 432 | ||
406 | if (!!sysfs_ns_type(acxt->parent_sd) != !!sd->s_ns) { | 433 | if (!!sysfs_ns_type(acxt->parent_sd) != !!sd->s_ns) { |
407 | WARN(1, KERN_WARNING "sysfs: ns %s in '%s' for '%s'\n", | 434 | WARN(1, KERN_WARNING "sysfs: ns %s in '%s' for '%s'\n", |
@@ -410,12 +437,12 @@ int __sysfs_add_one(struct sysfs_addrm_cxt *acxt, struct sysfs_dirent *sd) | |||
410 | return -EINVAL; | 437 | return -EINVAL; |
411 | } | 438 | } |
412 | 439 | ||
413 | if (sysfs_find_dirent(acxt->parent_sd, sd->s_ns, sd->s_name)) | 440 | sd->s_hash = sysfs_name_hash(sd->s_ns, sd->s_name); |
414 | return -EEXIST; | ||
415 | |||
416 | sd->s_parent = sysfs_get(acxt->parent_sd); | 441 | sd->s_parent = sysfs_get(acxt->parent_sd); |
417 | 442 | ||
418 | sysfs_link_sibling(sd); | 443 | ret = sysfs_link_sibling(sd); |
444 | if (ret) | ||
445 | return ret; | ||
419 | 446 | ||
420 | /* Update timestamps on the parent */ | 447 | /* Update timestamps on the parent */ |
421 | ps_iattr = acxt->parent_sd->s_iattr; | 448 | ps_iattr = acxt->parent_sd->s_iattr; |
@@ -565,8 +592,8 @@ struct sysfs_dirent *sysfs_find_dirent(struct sysfs_dirent *parent_sd, | |||
565 | const void *ns, | 592 | const void *ns, |
566 | const unsigned char *name) | 593 | const unsigned char *name) |
567 | { | 594 | { |
568 | struct rb_node *p = parent_sd->s_dir.name_tree.rb_node; | 595 | struct rb_node *node = parent_sd->s_dir.children.rb_node; |
569 | struct sysfs_dirent *found = NULL; | 596 | unsigned int hash; |
570 | 597 | ||
571 | if (!!sysfs_ns_type(parent_sd) != !!ns) { | 598 | if (!!sysfs_ns_type(parent_sd) != !!ns) { |
572 | WARN(1, KERN_WARNING "sysfs: ns %s in '%s' for '%s'\n", | 599 | WARN(1, KERN_WARNING "sysfs: ns %s in '%s' for '%s'\n", |
@@ -575,33 +602,21 @@ struct sysfs_dirent *sysfs_find_dirent(struct sysfs_dirent *parent_sd, | |||
575 | return NULL; | 602 | return NULL; |
576 | } | 603 | } |
577 | 604 | ||
578 | while (p) { | 605 | hash = sysfs_name_hash(ns, name); |
579 | int c; | 606 | while (node) { |
580 | #define node rb_entry(p, struct sysfs_dirent, name_node) | 607 | struct sysfs_dirent *sd; |
581 | c = strcmp(name, node->s_name); | 608 | int result; |
582 | if (c < 0) { | 609 | |
583 | p = node->name_node.rb_left; | 610 | sd = to_sysfs_dirent(node); |
584 | } else if (c > 0) { | 611 | result = sysfs_name_compare(hash, ns, name, sd); |
585 | p = node->name_node.rb_right; | 612 | if (result < 0) |
586 | } else { | 613 | node = node->rb_left; |
587 | found = node; | 614 | else if (result > 0) |
588 | p = node->name_node.rb_left; | 615 | node = node->rb_right; |
589 | } | 616 | else |
590 | #undef node | 617 | return sd; |
591 | } | ||
592 | |||
593 | if (found) { | ||
594 | while (found->s_ns != ns) { | ||
595 | p = rb_next(&found->name_node); | ||
596 | if (!p) | ||
597 | return NULL; | ||
598 | found = rb_entry(p, struct sysfs_dirent, name_node); | ||
599 | if (strcmp(name, found->s_name)) | ||
600 | return NULL; | ||
601 | } | ||
602 | } | 618 | } |
603 | 619 | return NULL; | |
604 | return found; | ||
605 | } | 620 | } |
606 | 621 | ||
607 | /** | 622 | /** |
@@ -804,9 +819,9 @@ static void __sysfs_remove_dir(struct sysfs_dirent *dir_sd) | |||
804 | 819 | ||
805 | pr_debug("sysfs %s: removing dir\n", dir_sd->s_name); | 820 | pr_debug("sysfs %s: removing dir\n", dir_sd->s_name); |
806 | sysfs_addrm_start(&acxt, dir_sd); | 821 | sysfs_addrm_start(&acxt, dir_sd); |
807 | pos = rb_first(&dir_sd->s_dir.inode_tree); | 822 | pos = rb_first(&dir_sd->s_dir.children); |
808 | while (pos) { | 823 | while (pos) { |
809 | struct sysfs_dirent *sd = rb_entry(pos, struct sysfs_dirent, inode_node); | 824 | struct sysfs_dirent *sd = to_sysfs_dirent(pos); |
810 | pos = rb_next(pos); | 825 | pos = rb_next(pos); |
811 | if (sysfs_type(sd) != SYSFS_DIR) | 826 | if (sysfs_type(sd) != SYSFS_DIR) |
812 | sysfs_remove_one(&acxt, sd); | 827 | sysfs_remove_one(&acxt, sd); |
@@ -919,38 +934,36 @@ static int sysfs_dir_release(struct inode *inode, struct file *filp) | |||
919 | } | 934 | } |
920 | 935 | ||
921 | static struct sysfs_dirent *sysfs_dir_pos(const void *ns, | 936 | static struct sysfs_dirent *sysfs_dir_pos(const void *ns, |
922 | struct sysfs_dirent *parent_sd, ino_t ino, struct sysfs_dirent *pos) | 937 | struct sysfs_dirent *parent_sd, loff_t hash, struct sysfs_dirent *pos) |
923 | { | 938 | { |
924 | if (pos) { | 939 | if (pos) { |
925 | int valid = !(pos->s_flags & SYSFS_FLAG_REMOVED) && | 940 | int valid = !(pos->s_flags & SYSFS_FLAG_REMOVED) && |
926 | pos->s_parent == parent_sd && | 941 | pos->s_parent == parent_sd && |
927 | ino == pos->s_ino; | 942 | hash == pos->s_hash; |
928 | sysfs_put(pos); | 943 | sysfs_put(pos); |
929 | if (!valid) | 944 | if (!valid) |
930 | pos = NULL; | 945 | pos = NULL; |
931 | } | 946 | } |
932 | if (!pos && (ino > 1) && (ino < INT_MAX)) { | 947 | if (!pos && (hash > 1) && (hash < INT_MAX)) { |
933 | struct rb_node *p = parent_sd->s_dir.inode_tree.rb_node; | 948 | struct rb_node *node = parent_sd->s_dir.children.rb_node; |
934 | while (p) { | 949 | while (node) { |
935 | #define node rb_entry(p, struct sysfs_dirent, inode_node) | 950 | pos = to_sysfs_dirent(node); |
936 | if (ino < node->s_ino) { | 951 | |
937 | pos = node; | 952 | if (hash < pos->s_hash) |
938 | p = node->inode_node.rb_left; | 953 | node = node->rb_left; |
939 | } else if (ino > node->s_ino) { | 954 | else if (hash > pos->s_hash) |
940 | p = node->inode_node.rb_right; | 955 | node = node->rb_right; |
941 | } else { | 956 | else |
942 | pos = node; | ||
943 | break; | 957 | break; |
944 | } | ||
945 | #undef node | ||
946 | } | 958 | } |
947 | } | 959 | } |
960 | /* Skip over entries in the wrong namespace */ | ||
948 | while (pos && pos->s_ns != ns) { | 961 | while (pos && pos->s_ns != ns) { |
949 | struct rb_node *p = rb_next(&pos->inode_node); | 962 | struct rb_node *node = rb_next(&pos->s_rb); |
950 | if (!p) | 963 | if (!node) |
951 | pos = NULL; | 964 | pos = NULL; |
952 | else | 965 | else |
953 | pos = rb_entry(p, struct sysfs_dirent, inode_node); | 966 | pos = to_sysfs_dirent(node); |
954 | } | 967 | } |
955 | return pos; | 968 | return pos; |
956 | } | 969 | } |
@@ -960,11 +973,11 @@ static struct sysfs_dirent *sysfs_dir_next_pos(const void *ns, | |||
960 | { | 973 | { |
961 | pos = sysfs_dir_pos(ns, parent_sd, ino, pos); | 974 | pos = sysfs_dir_pos(ns, parent_sd, ino, pos); |
962 | if (pos) do { | 975 | if (pos) do { |
963 | struct rb_node *p = rb_next(&pos->inode_node); | 976 | struct rb_node *node = rb_next(&pos->s_rb); |
964 | if (!p) | 977 | if (!node) |
965 | pos = NULL; | 978 | pos = NULL; |
966 | else | 979 | else |
967 | pos = rb_entry(p, struct sysfs_dirent, inode_node); | 980 | pos = to_sysfs_dirent(node); |
968 | } while (pos && pos->s_ns != ns); | 981 | } while (pos && pos->s_ns != ns); |
969 | return pos; | 982 | return pos; |
970 | } | 983 | } |
@@ -1006,7 +1019,7 @@ static int sysfs_readdir(struct file * filp, void * dirent, filldir_t filldir) | |||
1006 | len = strlen(name); | 1019 | len = strlen(name); |
1007 | ino = pos->s_ino; | 1020 | ino = pos->s_ino; |
1008 | type = dt_type(pos); | 1021 | type = dt_type(pos); |
1009 | filp->f_pos = ino; | 1022 | filp->f_pos = pos->s_hash; |
1010 | filp->private_data = sysfs_get(pos); | 1023 | filp->private_data = sysfs_get(pos); |
1011 | 1024 | ||
1012 | mutex_unlock(&sysfs_mutex); | 1025 | mutex_unlock(&sysfs_mutex); |
diff --git a/fs/sysfs/sysfs.h b/fs/sysfs/sysfs.h index 7484a36ee678..2b5c923b4b90 100644 --- a/fs/sysfs/sysfs.h +++ b/fs/sysfs/sysfs.h | |||
@@ -20,9 +20,8 @@ struct sysfs_elem_dir { | |||
20 | struct kobject *kobj; | 20 | struct kobject *kobj; |
21 | 21 | ||
22 | unsigned long subdirs; | 22 | unsigned long subdirs; |
23 | 23 | /* children rbtree starts here and goes through sd->s_rb */ | |
24 | struct rb_root inode_tree; | 24 | struct rb_root children; |
25 | struct rb_root name_tree; | ||
26 | }; | 25 | }; |
27 | 26 | ||
28 | struct sysfs_elem_symlink { | 27 | struct sysfs_elem_symlink { |
@@ -62,8 +61,7 @@ struct sysfs_dirent { | |||
62 | struct sysfs_dirent *s_parent; | 61 | struct sysfs_dirent *s_parent; |
63 | const char *s_name; | 62 | const char *s_name; |
64 | 63 | ||
65 | struct rb_node inode_node; | 64 | struct rb_node s_rb; |
66 | struct rb_node name_node; | ||
67 | 65 | ||
68 | union { | 66 | union { |
69 | struct completion *completion; | 67 | struct completion *completion; |
@@ -71,6 +69,7 @@ struct sysfs_dirent { | |||
71 | } u; | 69 | } u; |
72 | 70 | ||
73 | const void *s_ns; /* namespace tag */ | 71 | const void *s_ns; /* namespace tag */ |
72 | unsigned int s_hash; /* ns + name hash */ | ||
74 | union { | 73 | union { |
75 | struct sysfs_elem_dir s_dir; | 74 | struct sysfs_elem_dir s_dir; |
76 | struct sysfs_elem_symlink s_symlink; | 75 | struct sysfs_elem_symlink s_symlink; |