diff options
Diffstat (limited to 'fs/overlayfs/readdir.c')
-rw-r--r-- | fs/overlayfs/readdir.c | 181 |
1 files changed, 75 insertions, 106 deletions
diff --git a/fs/overlayfs/readdir.c b/fs/overlayfs/readdir.c index c0205990a9f5..907870e81a72 100644 --- a/fs/overlayfs/readdir.c +++ b/fs/overlayfs/readdir.c | |||
@@ -24,7 +24,6 @@ struct ovl_cache_entry { | |||
24 | struct list_head l_node; | 24 | struct list_head l_node; |
25 | struct rb_node node; | 25 | struct rb_node node; |
26 | bool is_whiteout; | 26 | bool is_whiteout; |
27 | bool is_cursor; | ||
28 | char name[]; | 27 | char name[]; |
29 | }; | 28 | }; |
30 | 29 | ||
@@ -40,6 +39,7 @@ struct ovl_readdir_data { | |||
40 | struct rb_root root; | 39 | struct rb_root root; |
41 | struct list_head *list; | 40 | struct list_head *list; |
42 | struct list_head middle; | 41 | struct list_head middle; |
42 | struct dentry *dir; | ||
43 | int count; | 43 | int count; |
44 | int err; | 44 | int err; |
45 | }; | 45 | }; |
@@ -48,7 +48,7 @@ struct ovl_dir_file { | |||
48 | bool is_real; | 48 | bool is_real; |
49 | bool is_upper; | 49 | bool is_upper; |
50 | struct ovl_dir_cache *cache; | 50 | struct ovl_dir_cache *cache; |
51 | struct ovl_cache_entry cursor; | 51 | struct list_head *cursor; |
52 | struct file *realfile; | 52 | struct file *realfile; |
53 | struct file *upperfile; | 53 | struct file *upperfile; |
54 | }; | 54 | }; |
@@ -79,23 +79,49 @@ static struct ovl_cache_entry *ovl_cache_entry_find(struct rb_root *root, | |||
79 | return NULL; | 79 | return NULL; |
80 | } | 80 | } |
81 | 81 | ||
82 | static struct ovl_cache_entry *ovl_cache_entry_new(const char *name, int len, | 82 | static struct ovl_cache_entry *ovl_cache_entry_new(struct dentry *dir, |
83 | const char *name, int len, | ||
83 | u64 ino, unsigned int d_type) | 84 | u64 ino, unsigned int d_type) |
84 | { | 85 | { |
85 | struct ovl_cache_entry *p; | 86 | struct ovl_cache_entry *p; |
86 | size_t size = offsetof(struct ovl_cache_entry, name[len + 1]); | 87 | size_t size = offsetof(struct ovl_cache_entry, name[len + 1]); |
87 | 88 | ||
88 | p = kmalloc(size, GFP_KERNEL); | 89 | p = kmalloc(size, GFP_KERNEL); |
89 | if (p) { | 90 | if (!p) |
90 | memcpy(p->name, name, len); | 91 | return NULL; |
91 | p->name[len] = '\0'; | 92 | |
92 | p->len = len; | 93 | memcpy(p->name, name, len); |
93 | p->type = d_type; | 94 | p->name[len] = '\0'; |
94 | p->ino = ino; | 95 | p->len = len; |
95 | p->is_whiteout = false; | 96 | p->type = d_type; |
96 | p->is_cursor = false; | 97 | p->ino = ino; |
97 | } | 98 | p->is_whiteout = false; |
99 | |||
100 | if (d_type == DT_CHR) { | ||
101 | struct dentry *dentry; | ||
102 | const struct cred *old_cred; | ||
103 | struct cred *override_cred; | ||
104 | |||
105 | override_cred = prepare_creds(); | ||
106 | if (!override_cred) { | ||
107 | kfree(p); | ||
108 | return NULL; | ||
109 | } | ||
110 | |||
111 | /* | ||
112 | * CAP_DAC_OVERRIDE for lookup | ||
113 | */ | ||
114 | cap_raise(override_cred->cap_effective, CAP_DAC_OVERRIDE); | ||
115 | old_cred = override_creds(override_cred); | ||
98 | 116 | ||
117 | dentry = lookup_one_len(name, dir, len); | ||
118 | if (!IS_ERR(dentry)) { | ||
119 | p->is_whiteout = ovl_is_whiteout(dentry); | ||
120 | dput(dentry); | ||
121 | } | ||
122 | revert_creds(old_cred); | ||
123 | put_cred(override_cred); | ||
124 | } | ||
99 | return p; | 125 | return p; |
100 | } | 126 | } |
101 | 127 | ||
@@ -122,7 +148,7 @@ static int ovl_cache_entry_add_rb(struct ovl_readdir_data *rdd, | |||
122 | return 0; | 148 | return 0; |
123 | } | 149 | } |
124 | 150 | ||
125 | p = ovl_cache_entry_new(name, len, ino, d_type); | 151 | p = ovl_cache_entry_new(rdd->dir, name, len, ino, d_type); |
126 | if (p == NULL) | 152 | if (p == NULL) |
127 | return -ENOMEM; | 153 | return -ENOMEM; |
128 | 154 | ||
@@ -143,7 +169,7 @@ static int ovl_fill_lower(struct ovl_readdir_data *rdd, | |||
143 | if (p) { | 169 | if (p) { |
144 | list_move_tail(&p->l_node, &rdd->middle); | 170 | list_move_tail(&p->l_node, &rdd->middle); |
145 | } else { | 171 | } else { |
146 | p = ovl_cache_entry_new(name, namelen, ino, d_type); | 172 | p = ovl_cache_entry_new(rdd->dir, name, namelen, ino, d_type); |
147 | if (p == NULL) | 173 | if (p == NULL) |
148 | rdd->err = -ENOMEM; | 174 | rdd->err = -ENOMEM; |
149 | else | 175 | else |
@@ -168,7 +194,6 @@ static void ovl_cache_put(struct ovl_dir_file *od, struct dentry *dentry) | |||
168 | { | 194 | { |
169 | struct ovl_dir_cache *cache = od->cache; | 195 | struct ovl_dir_cache *cache = od->cache; |
170 | 196 | ||
171 | list_del_init(&od->cursor.l_node); | ||
172 | WARN_ON(cache->refcount <= 0); | 197 | WARN_ON(cache->refcount <= 0); |
173 | cache->refcount--; | 198 | cache->refcount--; |
174 | if (!cache->refcount) { | 199 | if (!cache->refcount) { |
@@ -204,6 +229,7 @@ static inline int ovl_dir_read(struct path *realpath, | |||
204 | if (IS_ERR(realfile)) | 229 | if (IS_ERR(realfile)) |
205 | return PTR_ERR(realfile); | 230 | return PTR_ERR(realfile); |
206 | 231 | ||
232 | rdd->dir = realpath->dentry; | ||
207 | rdd->ctx.pos = 0; | 233 | rdd->ctx.pos = 0; |
208 | do { | 234 | do { |
209 | rdd->count = 0; | 235 | rdd->count = 0; |
@@ -227,108 +253,58 @@ static void ovl_dir_reset(struct file *file) | |||
227 | if (cache && ovl_dentry_version_get(dentry) != cache->version) { | 253 | if (cache && ovl_dentry_version_get(dentry) != cache->version) { |
228 | ovl_cache_put(od, dentry); | 254 | ovl_cache_put(od, dentry); |
229 | od->cache = NULL; | 255 | od->cache = NULL; |
256 | od->cursor = NULL; | ||
230 | } | 257 | } |
231 | WARN_ON(!od->is_real && type != OVL_PATH_MERGE); | 258 | WARN_ON(!od->is_real && !OVL_TYPE_MERGE(type)); |
232 | if (od->is_real && type == OVL_PATH_MERGE) | 259 | if (od->is_real && OVL_TYPE_MERGE(type)) |
233 | od->is_real = false; | 260 | od->is_real = false; |
234 | } | 261 | } |
235 | 262 | ||
236 | static int ovl_dir_mark_whiteouts(struct dentry *dir, | ||
237 | struct ovl_readdir_data *rdd) | ||
238 | { | ||
239 | struct ovl_cache_entry *p; | ||
240 | struct dentry *dentry; | ||
241 | const struct cred *old_cred; | ||
242 | struct cred *override_cred; | ||
243 | |||
244 | override_cred = prepare_creds(); | ||
245 | if (!override_cred) { | ||
246 | ovl_cache_free(rdd->list); | ||
247 | return -ENOMEM; | ||
248 | } | ||
249 | |||
250 | /* | ||
251 | * CAP_DAC_OVERRIDE for lookup | ||
252 | */ | ||
253 | cap_raise(override_cred->cap_effective, CAP_DAC_OVERRIDE); | ||
254 | old_cred = override_creds(override_cred); | ||
255 | |||
256 | mutex_lock(&dir->d_inode->i_mutex); | ||
257 | list_for_each_entry(p, rdd->list, l_node) { | ||
258 | if (p->is_cursor) | ||
259 | continue; | ||
260 | |||
261 | if (p->type != DT_CHR) | ||
262 | continue; | ||
263 | |||
264 | dentry = lookup_one_len(p->name, dir, p->len); | ||
265 | if (IS_ERR(dentry)) | ||
266 | continue; | ||
267 | |||
268 | p->is_whiteout = ovl_is_whiteout(dentry); | ||
269 | dput(dentry); | ||
270 | } | ||
271 | mutex_unlock(&dir->d_inode->i_mutex); | ||
272 | |||
273 | revert_creds(old_cred); | ||
274 | put_cred(override_cred); | ||
275 | |||
276 | return 0; | ||
277 | } | ||
278 | |||
279 | static int ovl_dir_read_merged(struct dentry *dentry, struct list_head *list) | 263 | static int ovl_dir_read_merged(struct dentry *dentry, struct list_head *list) |
280 | { | 264 | { |
281 | int err; | 265 | int err; |
282 | struct path lowerpath; | 266 | struct path realpath; |
283 | struct path upperpath; | ||
284 | struct ovl_readdir_data rdd = { | 267 | struct ovl_readdir_data rdd = { |
285 | .ctx.actor = ovl_fill_merge, | 268 | .ctx.actor = ovl_fill_merge, |
286 | .list = list, | 269 | .list = list, |
287 | .root = RB_ROOT, | 270 | .root = RB_ROOT, |
288 | .is_merge = false, | 271 | .is_merge = false, |
289 | }; | 272 | }; |
273 | int idx, next; | ||
290 | 274 | ||
291 | ovl_path_lower(dentry, &lowerpath); | 275 | for (idx = 0; idx != -1; idx = next) { |
292 | ovl_path_upper(dentry, &upperpath); | 276 | next = ovl_path_next(idx, dentry, &realpath); |
293 | 277 | ||
294 | if (upperpath.dentry) { | 278 | if (next != -1) { |
295 | err = ovl_dir_read(&upperpath, &rdd); | 279 | err = ovl_dir_read(&realpath, &rdd); |
296 | if (err) | ||
297 | goto out; | ||
298 | |||
299 | if (lowerpath.dentry) { | ||
300 | err = ovl_dir_mark_whiteouts(upperpath.dentry, &rdd); | ||
301 | if (err) | 280 | if (err) |
302 | goto out; | 281 | break; |
282 | } else { | ||
283 | /* | ||
284 | * Insert lowest layer entries before upper ones, this | ||
285 | * allows offsets to be reasonably constant | ||
286 | */ | ||
287 | list_add(&rdd.middle, rdd.list); | ||
288 | rdd.is_merge = true; | ||
289 | err = ovl_dir_read(&realpath, &rdd); | ||
290 | list_del(&rdd.middle); | ||
303 | } | 291 | } |
304 | } | 292 | } |
305 | if (lowerpath.dentry) { | ||
306 | /* | ||
307 | * Insert lowerpath entries before upperpath ones, this allows | ||
308 | * offsets to be reasonably constant | ||
309 | */ | ||
310 | list_add(&rdd.middle, rdd.list); | ||
311 | rdd.is_merge = true; | ||
312 | err = ovl_dir_read(&lowerpath, &rdd); | ||
313 | list_del(&rdd.middle); | ||
314 | } | ||
315 | out: | ||
316 | return err; | 293 | return err; |
317 | } | 294 | } |
318 | 295 | ||
319 | static void ovl_seek_cursor(struct ovl_dir_file *od, loff_t pos) | 296 | static void ovl_seek_cursor(struct ovl_dir_file *od, loff_t pos) |
320 | { | 297 | { |
321 | struct ovl_cache_entry *p; | 298 | struct list_head *p; |
322 | loff_t off = 0; | 299 | loff_t off = 0; |
323 | 300 | ||
324 | list_for_each_entry(p, &od->cache->entries, l_node) { | 301 | list_for_each(p, &od->cache->entries) { |
325 | if (p->is_cursor) | ||
326 | continue; | ||
327 | if (off >= pos) | 302 | if (off >= pos) |
328 | break; | 303 | break; |
329 | off++; | 304 | off++; |
330 | } | 305 | } |
331 | list_move_tail(&od->cursor.l_node, &p->l_node); | 306 | /* Cursor is safe since the cache is stable */ |
307 | od->cursor = p; | ||
332 | } | 308 | } |
333 | 309 | ||
334 | static struct ovl_dir_cache *ovl_cache_get(struct dentry *dentry) | 310 | static struct ovl_dir_cache *ovl_cache_get(struct dentry *dentry) |
@@ -367,6 +343,7 @@ static int ovl_iterate(struct file *file, struct dir_context *ctx) | |||
367 | { | 343 | { |
368 | struct ovl_dir_file *od = file->private_data; | 344 | struct ovl_dir_file *od = file->private_data; |
369 | struct dentry *dentry = file->f_path.dentry; | 345 | struct dentry *dentry = file->f_path.dentry; |
346 | struct ovl_cache_entry *p; | ||
370 | 347 | ||
371 | if (!ctx->pos) | 348 | if (!ctx->pos) |
372 | ovl_dir_reset(file); | 349 | ovl_dir_reset(file); |
@@ -385,19 +362,13 @@ static int ovl_iterate(struct file *file, struct dir_context *ctx) | |||
385 | ovl_seek_cursor(od, ctx->pos); | 362 | ovl_seek_cursor(od, ctx->pos); |
386 | } | 363 | } |
387 | 364 | ||
388 | while (od->cursor.l_node.next != &od->cache->entries) { | 365 | while (od->cursor != &od->cache->entries) { |
389 | struct ovl_cache_entry *p; | 366 | p = list_entry(od->cursor, struct ovl_cache_entry, l_node); |
390 | 367 | if (!p->is_whiteout) | |
391 | p = list_entry(od->cursor.l_node.next, struct ovl_cache_entry, l_node); | 368 | if (!dir_emit(ctx, p->name, p->len, p->ino, p->type)) |
392 | /* Skip cursors */ | 369 | break; |
393 | if (!p->is_cursor) { | 370 | od->cursor = p->l_node.next; |
394 | if (!p->is_whiteout) { | 371 | ctx->pos++; |
395 | if (!dir_emit(ctx, p->name, p->len, p->ino, p->type)) | ||
396 | break; | ||
397 | } | ||
398 | ctx->pos++; | ||
399 | } | ||
400 | list_move(&od->cursor.l_node, &p->l_node); | ||
401 | } | 372 | } |
402 | return 0; | 373 | return 0; |
403 | } | 374 | } |
@@ -452,7 +423,7 @@ static int ovl_dir_fsync(struct file *file, loff_t start, loff_t end, | |||
452 | /* | 423 | /* |
453 | * Need to check if we started out being a lower dir, but got copied up | 424 | * Need to check if we started out being a lower dir, but got copied up |
454 | */ | 425 | */ |
455 | if (!od->is_upper && ovl_path_type(dentry) != OVL_PATH_LOWER) { | 426 | if (!od->is_upper && OVL_TYPE_UPPER(ovl_path_type(dentry))) { |
456 | struct inode *inode = file_inode(file); | 427 | struct inode *inode = file_inode(file); |
457 | 428 | ||
458 | realfile = lockless_dereference(od->upperfile); | 429 | realfile = lockless_dereference(od->upperfile); |
@@ -516,11 +487,9 @@ static int ovl_dir_open(struct inode *inode, struct file *file) | |||
516 | kfree(od); | 487 | kfree(od); |
517 | return PTR_ERR(realfile); | 488 | return PTR_ERR(realfile); |
518 | } | 489 | } |
519 | INIT_LIST_HEAD(&od->cursor.l_node); | ||
520 | od->realfile = realfile; | 490 | od->realfile = realfile; |
521 | od->is_real = (type != OVL_PATH_MERGE); | 491 | od->is_real = !OVL_TYPE_MERGE(type); |
522 | od->is_upper = (type != OVL_PATH_LOWER); | 492 | od->is_upper = OVL_TYPE_UPPER(type); |
523 | od->cursor.is_cursor = true; | ||
524 | file->private_data = od; | 493 | file->private_data = od; |
525 | 494 | ||
526 | return 0; | 495 | return 0; |