diff options
author | Miklos Szeredi <mszeredi@suse.cz> | 2013-09-05 05:44:35 -0400 |
---|---|---|
committer | Al Viro <viro@zeniv.linux.org.uk> | 2013-09-05 16:22:44 -0400 |
commit | db14fc3abcd5dcc9b32ad5b9dd5b8f9e16295a39 (patch) | |
tree | 174f3dee50f4d89f893f3bca8937bd1837e265df | |
parent | 01ddc4ede5f09cdaed015d49ab66eec179085a2e (diff) |
vfs: add d_walk()
This one replaces three instances open coded tree walking (have_submounts,
select_parent, d_genocide) with a common helper.
In addition to slightly reducing the kernel size, this simplifies the
callers and makes them less bug prone.
Signed-off-by: Miklos Szeredi <mszeredi@suse.cz>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
-rw-r--r-- | fs/dcache.c | 309 |
1 files changed, 148 insertions, 161 deletions
diff --git a/fs/dcache.c b/fs/dcache.c index f792e9f20eca..043c5b478a77 100644 --- a/fs/dcache.c +++ b/fs/dcache.c | |||
@@ -1031,34 +1031,56 @@ static struct dentry *try_to_ascend(struct dentry *old, int locked, unsigned seq | |||
1031 | return new; | 1031 | return new; |
1032 | } | 1032 | } |
1033 | 1033 | ||
1034 | /** | ||
1035 | * enum d_walk_ret - action to talke during tree walk | ||
1036 | * @D_WALK_CONTINUE: contrinue walk | ||
1037 | * @D_WALK_QUIT: quit walk | ||
1038 | * @D_WALK_NORETRY: quit when retry is needed | ||
1039 | * @D_WALK_SKIP: skip this dentry and its children | ||
1040 | */ | ||
1041 | enum d_walk_ret { | ||
1042 | D_WALK_CONTINUE, | ||
1043 | D_WALK_QUIT, | ||
1044 | D_WALK_NORETRY, | ||
1045 | D_WALK_SKIP, | ||
1046 | }; | ||
1034 | 1047 | ||
1035 | /* | ||
1036 | * Search for at least 1 mount point in the dentry's subdirs. | ||
1037 | * We descend to the next level whenever the d_subdirs | ||
1038 | * list is non-empty and continue searching. | ||
1039 | */ | ||
1040 | |||
1041 | /** | 1048 | /** |
1042 | * have_submounts - check for mounts over a dentry | 1049 | * d_walk - walk the dentry tree |
1043 | * @parent: dentry to check. | 1050 | * @parent: start of walk |
1051 | * @data: data passed to @enter() and @finish() | ||
1052 | * @enter: callback when first entering the dentry | ||
1053 | * @finish: callback when successfully finished the walk | ||
1044 | * | 1054 | * |
1045 | * Return true if the parent or its subdirectories contain | 1055 | * The @enter() and @finish() callbacks are called with d_lock held. |
1046 | * a mount point | ||
1047 | */ | 1056 | */ |
1048 | int have_submounts(struct dentry *parent) | 1057 | static void d_walk(struct dentry *parent, void *data, |
1058 | enum d_walk_ret (*enter)(void *, struct dentry *), | ||
1059 | void (*finish)(void *)) | ||
1049 | { | 1060 | { |
1050 | struct dentry *this_parent; | 1061 | struct dentry *this_parent; |
1051 | struct list_head *next; | 1062 | struct list_head *next; |
1052 | unsigned seq; | 1063 | unsigned seq; |
1053 | int locked = 0; | 1064 | int locked = 0; |
1065 | enum d_walk_ret ret; | ||
1066 | bool retry = true; | ||
1054 | 1067 | ||
1055 | seq = read_seqbegin(&rename_lock); | 1068 | seq = read_seqbegin(&rename_lock); |
1056 | again: | 1069 | again: |
1057 | this_parent = parent; | 1070 | this_parent = parent; |
1058 | |||
1059 | if (d_mountpoint(parent)) | ||
1060 | goto positive; | ||
1061 | spin_lock(&this_parent->d_lock); | 1071 | spin_lock(&this_parent->d_lock); |
1072 | |||
1073 | ret = enter(data, this_parent); | ||
1074 | switch (ret) { | ||
1075 | case D_WALK_CONTINUE: | ||
1076 | break; | ||
1077 | case D_WALK_QUIT: | ||
1078 | case D_WALK_SKIP: | ||
1079 | goto out_unlock; | ||
1080 | case D_WALK_NORETRY: | ||
1081 | retry = false; | ||
1082 | break; | ||
1083 | } | ||
1062 | repeat: | 1084 | repeat: |
1063 | next = this_parent->d_subdirs.next; | 1085 | next = this_parent->d_subdirs.next; |
1064 | resume: | 1086 | resume: |
@@ -1068,12 +1090,22 @@ resume: | |||
1068 | next = tmp->next; | 1090 | next = tmp->next; |
1069 | 1091 | ||
1070 | spin_lock_nested(&dentry->d_lock, DENTRY_D_LOCK_NESTED); | 1092 | spin_lock_nested(&dentry->d_lock, DENTRY_D_LOCK_NESTED); |
1071 | /* Have we found a mount point ? */ | 1093 | |
1072 | if (d_mountpoint(dentry)) { | 1094 | ret = enter(data, dentry); |
1095 | switch (ret) { | ||
1096 | case D_WALK_CONTINUE: | ||
1097 | break; | ||
1098 | case D_WALK_QUIT: | ||
1073 | spin_unlock(&dentry->d_lock); | 1099 | spin_unlock(&dentry->d_lock); |
1074 | spin_unlock(&this_parent->d_lock); | 1100 | goto out_unlock; |
1075 | goto positive; | 1101 | case D_WALK_NORETRY: |
1102 | retry = false; | ||
1103 | break; | ||
1104 | case D_WALK_SKIP: | ||
1105 | spin_unlock(&dentry->d_lock); | ||
1106 | continue; | ||
1076 | } | 1107 | } |
1108 | |||
1077 | if (!list_empty(&dentry->d_subdirs)) { | 1109 | if (!list_empty(&dentry->d_subdirs)) { |
1078 | spin_unlock(&this_parent->d_lock); | 1110 | spin_unlock(&this_parent->d_lock); |
1079 | spin_release(&dentry->d_lock.dep_map, 1, _RET_IP_); | 1111 | spin_release(&dentry->d_lock.dep_map, 1, _RET_IP_); |
@@ -1094,26 +1126,61 @@ resume: | |||
1094 | next = child->d_u.d_child.next; | 1126 | next = child->d_u.d_child.next; |
1095 | goto resume; | 1127 | goto resume; |
1096 | } | 1128 | } |
1097 | spin_unlock(&this_parent->d_lock); | 1129 | if (!locked && read_seqretry(&rename_lock, seq)) { |
1098 | if (!locked && read_seqretry(&rename_lock, seq)) | 1130 | spin_unlock(&this_parent->d_lock); |
1099 | goto rename_retry; | ||
1100 | if (locked) | ||
1101 | write_sequnlock(&rename_lock); | ||
1102 | return 0; /* No mount points found in tree */ | ||
1103 | positive: | ||
1104 | if (!locked && read_seqretry(&rename_lock, seq)) | ||
1105 | goto rename_retry; | 1131 | goto rename_retry; |
1132 | } | ||
1133 | if (finish) | ||
1134 | finish(data); | ||
1135 | |||
1136 | out_unlock: | ||
1137 | spin_unlock(&this_parent->d_lock); | ||
1106 | if (locked) | 1138 | if (locked) |
1107 | write_sequnlock(&rename_lock); | 1139 | write_sequnlock(&rename_lock); |
1108 | return 1; | 1140 | return; |
1109 | 1141 | ||
1110 | rename_retry: | 1142 | rename_retry: |
1143 | if (!retry) | ||
1144 | return; | ||
1111 | if (locked) | 1145 | if (locked) |
1112 | goto again; | 1146 | goto again; |
1113 | locked = 1; | 1147 | locked = 1; |
1114 | write_seqlock(&rename_lock); | 1148 | write_seqlock(&rename_lock); |
1115 | goto again; | 1149 | goto again; |
1116 | } | 1150 | } |
1151 | |||
1152 | /* | ||
1153 | * Search for at least 1 mount point in the dentry's subdirs. | ||
1154 | * We descend to the next level whenever the d_subdirs | ||
1155 | * list is non-empty and continue searching. | ||
1156 | */ | ||
1157 | |||
1158 | /** | ||
1159 | * have_submounts - check for mounts over a dentry | ||
1160 | * @parent: dentry to check. | ||
1161 | * | ||
1162 | * Return true if the parent or its subdirectories contain | ||
1163 | * a mount point | ||
1164 | */ | ||
1165 | |||
1166 | static enum d_walk_ret check_mount(void *data, struct dentry *dentry) | ||
1167 | { | ||
1168 | int *ret = data; | ||
1169 | if (d_mountpoint(dentry)) { | ||
1170 | *ret = 1; | ||
1171 | return D_WALK_QUIT; | ||
1172 | } | ||
1173 | return D_WALK_CONTINUE; | ||
1174 | } | ||
1175 | |||
1176 | int have_submounts(struct dentry *parent) | ||
1177 | { | ||
1178 | int ret = 0; | ||
1179 | |||
1180 | d_walk(parent, &ret, check_mount, NULL); | ||
1181 | |||
1182 | return ret; | ||
1183 | } | ||
1117 | EXPORT_SYMBOL(have_submounts); | 1184 | EXPORT_SYMBOL(have_submounts); |
1118 | 1185 | ||
1119 | /* | 1186 | /* |
@@ -1130,93 +1197,46 @@ EXPORT_SYMBOL(have_submounts); | |||
1130 | * drop the lock and return early due to latency | 1197 | * drop the lock and return early due to latency |
1131 | * constraints. | 1198 | * constraints. |
1132 | */ | 1199 | */ |
1133 | static int select_parent(struct dentry *parent, struct list_head *dispose) | ||
1134 | { | ||
1135 | struct dentry *this_parent; | ||
1136 | struct list_head *next; | ||
1137 | unsigned seq; | ||
1138 | int found = 0; | ||
1139 | int locked = 0; | ||
1140 | 1200 | ||
1141 | seq = read_seqbegin(&rename_lock); | 1201 | struct select_data { |
1142 | again: | 1202 | struct dentry *start; |
1143 | this_parent = parent; | 1203 | struct list_head dispose; |
1144 | spin_lock(&this_parent->d_lock); | 1204 | int found; |
1145 | repeat: | 1205 | }; |
1146 | next = this_parent->d_subdirs.next; | ||
1147 | resume: | ||
1148 | while (next != &this_parent->d_subdirs) { | ||
1149 | struct list_head *tmp = next; | ||
1150 | struct dentry *dentry = list_entry(tmp, struct dentry, d_u.d_child); | ||
1151 | next = tmp->next; | ||
1152 | |||
1153 | spin_lock_nested(&dentry->d_lock, DENTRY_D_LOCK_NESTED); | ||
1154 | 1206 | ||
1155 | /* | 1207 | static enum d_walk_ret select_collect(void *_data, struct dentry *dentry) |
1156 | * move only zero ref count dentries to the dispose list. | 1208 | { |
1157 | * | 1209 | struct select_data *data = _data; |
1158 | * Those which are presently on the shrink list, being processed | 1210 | enum d_walk_ret ret = D_WALK_CONTINUE; |
1159 | * by shrink_dentry_list(), shouldn't be moved. Otherwise the | ||
1160 | * loop in shrink_dcache_parent() might not make any progress | ||
1161 | * and loop forever. | ||
1162 | */ | ||
1163 | if (dentry->d_lockref.count) { | ||
1164 | dentry_lru_del(dentry); | ||
1165 | } else if (!(dentry->d_flags & DCACHE_SHRINK_LIST)) { | ||
1166 | dentry_lru_move_list(dentry, dispose); | ||
1167 | dentry->d_flags |= DCACHE_SHRINK_LIST; | ||
1168 | found++; | ||
1169 | } | ||
1170 | /* | ||
1171 | * We can return to the caller if we have found some (this | ||
1172 | * ensures forward progress). We'll be coming back to find | ||
1173 | * the rest. | ||
1174 | */ | ||
1175 | if (found && need_resched()) { | ||
1176 | spin_unlock(&dentry->d_lock); | ||
1177 | goto out; | ||
1178 | } | ||
1179 | 1211 | ||
1180 | /* | 1212 | if (data->start == dentry) |
1181 | * Descend a level if the d_subdirs list is non-empty. | 1213 | goto out; |
1182 | */ | ||
1183 | if (!list_empty(&dentry->d_subdirs)) { | ||
1184 | spin_unlock(&this_parent->d_lock); | ||
1185 | spin_release(&dentry->d_lock.dep_map, 1, _RET_IP_); | ||
1186 | this_parent = dentry; | ||
1187 | spin_acquire(&this_parent->d_lock.dep_map, 0, 1, _RET_IP_); | ||
1188 | goto repeat; | ||
1189 | } | ||
1190 | 1214 | ||
1191 | spin_unlock(&dentry->d_lock); | ||
1192 | } | ||
1193 | /* | 1215 | /* |
1194 | * All done at this level ... ascend and resume the search. | 1216 | * move only zero ref count dentries to the dispose list. |
1217 | * | ||
1218 | * Those which are presently on the shrink list, being processed | ||
1219 | * by shrink_dentry_list(), shouldn't be moved. Otherwise the | ||
1220 | * loop in shrink_dcache_parent() might not make any progress | ||
1221 | * and loop forever. | ||
1195 | */ | 1222 | */ |
1196 | if (this_parent != parent) { | 1223 | if (dentry->d_lockref.count) { |
1197 | struct dentry *child = this_parent; | 1224 | dentry_lru_del(dentry); |
1198 | this_parent = try_to_ascend(this_parent, locked, seq); | 1225 | } else if (!(dentry->d_flags & DCACHE_SHRINK_LIST)) { |
1199 | if (!this_parent) | 1226 | dentry_lru_move_list(dentry, &data->dispose); |
1200 | goto rename_retry; | 1227 | dentry->d_flags |= DCACHE_SHRINK_LIST; |
1201 | next = child->d_u.d_child.next; | 1228 | data->found++; |
1202 | goto resume; | 1229 | ret = D_WALK_NORETRY; |
1203 | } | 1230 | } |
1231 | /* | ||
1232 | * We can return to the caller if we have found some (this | ||
1233 | * ensures forward progress). We'll be coming back to find | ||
1234 | * the rest. | ||
1235 | */ | ||
1236 | if (data->found && need_resched()) | ||
1237 | ret = D_WALK_QUIT; | ||
1204 | out: | 1238 | out: |
1205 | spin_unlock(&this_parent->d_lock); | 1239 | return ret; |
1206 | if (!locked && read_seqretry(&rename_lock, seq)) | ||
1207 | goto rename_retry; | ||
1208 | if (locked) | ||
1209 | write_sequnlock(&rename_lock); | ||
1210 | return found; | ||
1211 | |||
1212 | rename_retry: | ||
1213 | if (found) | ||
1214 | return found; | ||
1215 | if (locked) | ||
1216 | goto again; | ||
1217 | locked = 1; | ||
1218 | write_seqlock(&rename_lock); | ||
1219 | goto again; | ||
1220 | } | 1240 | } |
1221 | 1241 | ||
1222 | /** | 1242 | /** |
@@ -1225,13 +1245,20 @@ rename_retry: | |||
1225 | * | 1245 | * |
1226 | * Prune the dcache to remove unused children of the parent dentry. | 1246 | * Prune the dcache to remove unused children of the parent dentry. |
1227 | */ | 1247 | */ |
1228 | void shrink_dcache_parent(struct dentry * parent) | 1248 | void shrink_dcache_parent(struct dentry *parent) |
1229 | { | 1249 | { |
1230 | LIST_HEAD(dispose); | 1250 | for (;;) { |
1231 | int found; | 1251 | struct select_data data; |
1232 | 1252 | ||
1233 | while ((found = select_parent(parent, &dispose)) != 0) { | 1253 | INIT_LIST_HEAD(&data.dispose); |
1234 | shrink_dentry_list(&dispose); | 1254 | data.start = parent; |
1255 | data.found = 0; | ||
1256 | |||
1257 | d_walk(parent, &data, select_collect, NULL); | ||
1258 | if (!data.found) | ||
1259 | break; | ||
1260 | |||
1261 | shrink_dentry_list(&data.dispose); | ||
1235 | cond_resched(); | 1262 | cond_resched(); |
1236 | } | 1263 | } |
1237 | } | 1264 | } |
@@ -2928,64 +2955,24 @@ int is_subdir(struct dentry *new_dentry, struct dentry *old_dentry) | |||
2928 | return result; | 2955 | return result; |
2929 | } | 2956 | } |
2930 | 2957 | ||
2931 | void d_genocide(struct dentry *root) | 2958 | static enum d_walk_ret d_genocide_kill(void *data, struct dentry *dentry) |
2932 | { | 2959 | { |
2933 | struct dentry *this_parent; | 2960 | struct dentry *root = data; |
2934 | struct list_head *next; | 2961 | if (dentry != root) { |
2935 | unsigned seq; | 2962 | if (d_unhashed(dentry) || !dentry->d_inode) |
2936 | int locked = 0; | 2963 | return D_WALK_SKIP; |
2937 | 2964 | ||
2938 | seq = read_seqbegin(&rename_lock); | ||
2939 | again: | ||
2940 | this_parent = root; | ||
2941 | spin_lock(&this_parent->d_lock); | ||
2942 | repeat: | ||
2943 | next = this_parent->d_subdirs.next; | ||
2944 | resume: | ||
2945 | while (next != &this_parent->d_subdirs) { | ||
2946 | struct list_head *tmp = next; | ||
2947 | struct dentry *dentry = list_entry(tmp, struct dentry, d_u.d_child); | ||
2948 | next = tmp->next; | ||
2949 | |||
2950 | spin_lock_nested(&dentry->d_lock, DENTRY_D_LOCK_NESTED); | ||
2951 | if (d_unhashed(dentry) || !dentry->d_inode) { | ||
2952 | spin_unlock(&dentry->d_lock); | ||
2953 | continue; | ||
2954 | } | ||
2955 | if (!(dentry->d_flags & DCACHE_GENOCIDE)) { | 2965 | if (!(dentry->d_flags & DCACHE_GENOCIDE)) { |
2956 | dentry->d_flags |= DCACHE_GENOCIDE; | 2966 | dentry->d_flags |= DCACHE_GENOCIDE; |
2957 | dentry->d_lockref.count--; | 2967 | dentry->d_lockref.count--; |
2958 | } | 2968 | } |
2959 | if (!list_empty(&dentry->d_subdirs)) { | ||
2960 | spin_unlock(&this_parent->d_lock); | ||
2961 | spin_release(&dentry->d_lock.dep_map, 1, _RET_IP_); | ||
2962 | this_parent = dentry; | ||
2963 | spin_acquire(&this_parent->d_lock.dep_map, 0, 1, _RET_IP_); | ||
2964 | goto repeat; | ||
2965 | } | ||
2966 | spin_unlock(&dentry->d_lock); | ||
2967 | } | 2969 | } |
2968 | if (this_parent != root) { | 2970 | return D_WALK_CONTINUE; |
2969 | struct dentry *child = this_parent; | 2971 | } |
2970 | this_parent = try_to_ascend(this_parent, locked, seq); | ||
2971 | if (!this_parent) | ||
2972 | goto rename_retry; | ||
2973 | next = child->d_u.d_child.next; | ||
2974 | goto resume; | ||
2975 | } | ||
2976 | spin_unlock(&this_parent->d_lock); | ||
2977 | if (!locked && read_seqretry(&rename_lock, seq)) | ||
2978 | goto rename_retry; | ||
2979 | if (locked) | ||
2980 | write_sequnlock(&rename_lock); | ||
2981 | return; | ||
2982 | 2972 | ||
2983 | rename_retry: | 2973 | void d_genocide(struct dentry *parent) |
2984 | if (locked) | 2974 | { |
2985 | goto again; | 2975 | d_walk(parent, parent, d_genocide_kill, NULL); |
2986 | locked = 1; | ||
2987 | write_seqlock(&rename_lock); | ||
2988 | goto again; | ||
2989 | } | 2976 | } |
2990 | 2977 | ||
2991 | void d_tmpfile(struct dentry *dentry, struct inode *inode) | 2978 | void d_tmpfile(struct dentry *dentry, struct inode *inode) |