perf stat aggregation: Add separate die member
[linux-2.6-microblaze.git] / fs / hfsplus / dir.c
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  *  linux/fs/hfsplus/dir.c
4  *
5  * Copyright (C) 2001
6  * Brad Boyer (flar@allandria.com)
7  * (C) 2003 Ardis Technologies <roman@ardistech.com>
8  *
9  * Handling of directories
10  */
11
12 #include <linux/errno.h>
13 #include <linux/fs.h>
14 #include <linux/slab.h>
15 #include <linux/random.h>
16 #include <linux/nls.h>
17
18 #include "hfsplus_fs.h"
19 #include "hfsplus_raw.h"
20 #include "xattr.h"
21
22 static inline void hfsplus_instantiate(struct dentry *dentry,
23                                        struct inode *inode, u32 cnid)
24 {
25         dentry->d_fsdata = (void *)(unsigned long)cnid;
26         d_instantiate(dentry, inode);
27 }
28
29 /* Find the entry inside dir named dentry->d_name */
30 static struct dentry *hfsplus_lookup(struct inode *dir, struct dentry *dentry,
31                                      unsigned int flags)
32 {
33         struct inode *inode = NULL;
34         struct hfs_find_data fd;
35         struct super_block *sb;
36         hfsplus_cat_entry entry;
37         int err;
38         u32 cnid, linkid = 0;
39         u16 type;
40
41         sb = dir->i_sb;
42
43         dentry->d_fsdata = NULL;
44         err = hfs_find_init(HFSPLUS_SB(sb)->cat_tree, &fd);
45         if (err)
46                 return ERR_PTR(err);
47         err = hfsplus_cat_build_key(sb, fd.search_key, dir->i_ino,
48                         &dentry->d_name);
49         if (unlikely(err < 0))
50                 goto fail;
51 again:
52         err = hfs_brec_read(&fd, &entry, sizeof(entry));
53         if (err) {
54                 if (err == -ENOENT) {
55                         hfs_find_exit(&fd);
56                         /* No such entry */
57                         inode = NULL;
58                         goto out;
59                 }
60                 goto fail;
61         }
62         type = be16_to_cpu(entry.type);
63         if (type == HFSPLUS_FOLDER) {
64                 if (fd.entrylength < sizeof(struct hfsplus_cat_folder)) {
65                         err = -EIO;
66                         goto fail;
67                 }
68                 cnid = be32_to_cpu(entry.folder.id);
69                 dentry->d_fsdata = (void *)(unsigned long)cnid;
70         } else if (type == HFSPLUS_FILE) {
71                 if (fd.entrylength < sizeof(struct hfsplus_cat_file)) {
72                         err = -EIO;
73                         goto fail;
74                 }
75                 cnid = be32_to_cpu(entry.file.id);
76                 if (entry.file.user_info.fdType ==
77                                 cpu_to_be32(HFSP_HARDLINK_TYPE) &&
78                                 entry.file.user_info.fdCreator ==
79                                 cpu_to_be32(HFSP_HFSPLUS_CREATOR) &&
80                                 HFSPLUS_SB(sb)->hidden_dir &&
81                                 (entry.file.create_date ==
82                                         HFSPLUS_I(HFSPLUS_SB(sb)->hidden_dir)->
83                                                 create_date ||
84                                 entry.file.create_date ==
85                                         HFSPLUS_I(d_inode(sb->s_root))->
86                                                 create_date)) {
87                         struct qstr str;
88                         char name[32];
89
90                         if (dentry->d_fsdata) {
91                                 /*
92                                  * We found a link pointing to another link,
93                                  * so ignore it and treat it as regular file.
94                                  */
95                                 cnid = (unsigned long)dentry->d_fsdata;
96                                 linkid = 0;
97                         } else {
98                                 dentry->d_fsdata = (void *)(unsigned long)cnid;
99                                 linkid =
100                                         be32_to_cpu(entry.file.permissions.dev);
101                                 str.len = sprintf(name, "iNode%d", linkid);
102                                 str.name = name;
103                                 err = hfsplus_cat_build_key(sb, fd.search_key,
104                                         HFSPLUS_SB(sb)->hidden_dir->i_ino,
105                                         &str);
106                                 if (unlikely(err < 0))
107                                         goto fail;
108                                 goto again;
109                         }
110                 } else if (!dentry->d_fsdata)
111                         dentry->d_fsdata = (void *)(unsigned long)cnid;
112         } else {
113                 pr_err("invalid catalog entry type in lookup\n");
114                 err = -EIO;
115                 goto fail;
116         }
117         hfs_find_exit(&fd);
118         inode = hfsplus_iget(dir->i_sb, cnid);
119         if (IS_ERR(inode))
120                 return ERR_CAST(inode);
121         if (S_ISREG(inode->i_mode))
122                 HFSPLUS_I(inode)->linkid = linkid;
123 out:
124         return d_splice_alias(inode, dentry);
125 fail:
126         hfs_find_exit(&fd);
127         return ERR_PTR(err);
128 }
129
130 static int hfsplus_readdir(struct file *file, struct dir_context *ctx)
131 {
132         struct inode *inode = file_inode(file);
133         struct super_block *sb = inode->i_sb;
134         int len, err;
135         char *strbuf;
136         hfsplus_cat_entry entry;
137         struct hfs_find_data fd;
138         struct hfsplus_readdir_data *rd;
139         u16 type;
140
141         if (file->f_pos >= inode->i_size)
142                 return 0;
143
144         err = hfs_find_init(HFSPLUS_SB(sb)->cat_tree, &fd);
145         if (err)
146                 return err;
147         strbuf = kmalloc(NLS_MAX_CHARSET_SIZE * HFSPLUS_MAX_STRLEN + 1, GFP_KERNEL);
148         if (!strbuf) {
149                 err = -ENOMEM;
150                 goto out;
151         }
152         hfsplus_cat_build_key_with_cnid(sb, fd.search_key, inode->i_ino);
153         err = hfs_brec_find(&fd, hfs_find_rec_by_key);
154         if (err)
155                 goto out;
156
157         if (ctx->pos == 0) {
158                 /* This is completely artificial... */
159                 if (!dir_emit_dot(file, ctx))
160                         goto out;
161                 ctx->pos = 1;
162         }
163         if (ctx->pos == 1) {
164                 if (fd.entrylength > sizeof(entry) || fd.entrylength < 0) {
165                         err = -EIO;
166                         goto out;
167                 }
168
169                 hfs_bnode_read(fd.bnode, &entry, fd.entryoffset,
170                         fd.entrylength);
171                 if (be16_to_cpu(entry.type) != HFSPLUS_FOLDER_THREAD) {
172                         pr_err("bad catalog folder thread\n");
173                         err = -EIO;
174                         goto out;
175                 }
176                 if (fd.entrylength < HFSPLUS_MIN_THREAD_SZ) {
177                         pr_err("truncated catalog thread\n");
178                         err = -EIO;
179                         goto out;
180                 }
181                 if (!dir_emit(ctx, "..", 2,
182                             be32_to_cpu(entry.thread.parentID), DT_DIR))
183                         goto out;
184                 ctx->pos = 2;
185         }
186         if (ctx->pos >= inode->i_size)
187                 goto out;
188         err = hfs_brec_goto(&fd, ctx->pos - 1);
189         if (err)
190                 goto out;
191         for (;;) {
192                 if (be32_to_cpu(fd.key->cat.parent) != inode->i_ino) {
193                         pr_err("walked past end of dir\n");
194                         err = -EIO;
195                         goto out;
196                 }
197
198                 if (fd.entrylength > sizeof(entry) || fd.entrylength < 0) {
199                         err = -EIO;
200                         goto out;
201                 }
202
203                 hfs_bnode_read(fd.bnode, &entry, fd.entryoffset,
204                         fd.entrylength);
205                 type = be16_to_cpu(entry.type);
206                 len = NLS_MAX_CHARSET_SIZE * HFSPLUS_MAX_STRLEN;
207                 err = hfsplus_uni2asc(sb, &fd.key->cat.name, strbuf, &len);
208                 if (err)
209                         goto out;
210                 if (type == HFSPLUS_FOLDER) {
211                         if (fd.entrylength <
212                                         sizeof(struct hfsplus_cat_folder)) {
213                                 pr_err("small dir entry\n");
214                                 err = -EIO;
215                                 goto out;
216                         }
217                         if (HFSPLUS_SB(sb)->hidden_dir &&
218                             HFSPLUS_SB(sb)->hidden_dir->i_ino ==
219                                         be32_to_cpu(entry.folder.id))
220                                 goto next;
221                         if (!dir_emit(ctx, strbuf, len,
222                                     be32_to_cpu(entry.folder.id), DT_DIR))
223                                 break;
224                 } else if (type == HFSPLUS_FILE) {
225                         u16 mode;
226                         unsigned type = DT_UNKNOWN;
227
228                         if (fd.entrylength < sizeof(struct hfsplus_cat_file)) {
229                                 pr_err("small file entry\n");
230                                 err = -EIO;
231                                 goto out;
232                         }
233
234                         mode = be16_to_cpu(entry.file.permissions.mode);
235                         if (S_ISREG(mode))
236                                 type = DT_REG;
237                         else if (S_ISLNK(mode))
238                                 type = DT_LNK;
239                         else if (S_ISFIFO(mode))
240                                 type = DT_FIFO;
241                         else if (S_ISCHR(mode))
242                                 type = DT_CHR;
243                         else if (S_ISBLK(mode))
244                                 type = DT_BLK;
245                         else if (S_ISSOCK(mode))
246                                 type = DT_SOCK;
247
248                         if (!dir_emit(ctx, strbuf, len,
249                                       be32_to_cpu(entry.file.id), type))
250                                 break;
251                 } else {
252                         pr_err("bad catalog entry type\n");
253                         err = -EIO;
254                         goto out;
255                 }
256 next:
257                 ctx->pos++;
258                 if (ctx->pos >= inode->i_size)
259                         goto out;
260                 err = hfs_brec_goto(&fd, 1);
261                 if (err)
262                         goto out;
263         }
264         rd = file->private_data;
265         if (!rd) {
266                 rd = kmalloc(sizeof(struct hfsplus_readdir_data), GFP_KERNEL);
267                 if (!rd) {
268                         err = -ENOMEM;
269                         goto out;
270                 }
271                 file->private_data = rd;
272                 rd->file = file;
273                 spin_lock(&HFSPLUS_I(inode)->open_dir_lock);
274                 list_add(&rd->list, &HFSPLUS_I(inode)->open_dir_list);
275                 spin_unlock(&HFSPLUS_I(inode)->open_dir_lock);
276         }
277         /*
278          * Can be done after the list insertion; exclusion with
279          * hfsplus_delete_cat() is provided by directory lock.
280          */
281         memcpy(&rd->key, fd.key, sizeof(struct hfsplus_cat_key));
282 out:
283         kfree(strbuf);
284         hfs_find_exit(&fd);
285         return err;
286 }
287
288 static int hfsplus_dir_release(struct inode *inode, struct file *file)
289 {
290         struct hfsplus_readdir_data *rd = file->private_data;
291         if (rd) {
292                 spin_lock(&HFSPLUS_I(inode)->open_dir_lock);
293                 list_del(&rd->list);
294                 spin_unlock(&HFSPLUS_I(inode)->open_dir_lock);
295                 kfree(rd);
296         }
297         return 0;
298 }
299
300 static int hfsplus_link(struct dentry *src_dentry, struct inode *dst_dir,
301                         struct dentry *dst_dentry)
302 {
303         struct hfsplus_sb_info *sbi = HFSPLUS_SB(dst_dir->i_sb);
304         struct inode *inode = d_inode(src_dentry);
305         struct inode *src_dir = d_inode(src_dentry->d_parent);
306         struct qstr str;
307         char name[32];
308         u32 cnid, id;
309         int res;
310
311         if (HFSPLUS_IS_RSRC(inode))
312                 return -EPERM;
313         if (!S_ISREG(inode->i_mode))
314                 return -EPERM;
315
316         mutex_lock(&sbi->vh_mutex);
317         if (inode->i_ino == (u32)(unsigned long)src_dentry->d_fsdata) {
318                 for (;;) {
319                         get_random_bytes(&id, sizeof(cnid));
320                         id &= 0x3fffffff;
321                         str.name = name;
322                         str.len = sprintf(name, "iNode%d", id);
323                         res = hfsplus_rename_cat(inode->i_ino,
324                                                  src_dir, &src_dentry->d_name,
325                                                  sbi->hidden_dir, &str);
326                         if (!res)
327                                 break;
328                         if (res != -EEXIST)
329                                 goto out;
330                 }
331                 HFSPLUS_I(inode)->linkid = id;
332                 cnid = sbi->next_cnid++;
333                 src_dentry->d_fsdata = (void *)(unsigned long)cnid;
334                 res = hfsplus_create_cat(cnid, src_dir,
335                         &src_dentry->d_name, inode);
336                 if (res)
337                         /* panic? */
338                         goto out;
339                 sbi->file_count++;
340         }
341         cnid = sbi->next_cnid++;
342         res = hfsplus_create_cat(cnid, dst_dir, &dst_dentry->d_name, inode);
343         if (res)
344                 goto out;
345
346         inc_nlink(inode);
347         hfsplus_instantiate(dst_dentry, inode, cnid);
348         ihold(inode);
349         inode->i_ctime = current_time(inode);
350         mark_inode_dirty(inode);
351         sbi->file_count++;
352         hfsplus_mark_mdb_dirty(dst_dir->i_sb);
353 out:
354         mutex_unlock(&sbi->vh_mutex);
355         return res;
356 }
357
358 static int hfsplus_unlink(struct inode *dir, struct dentry *dentry)
359 {
360         struct hfsplus_sb_info *sbi = HFSPLUS_SB(dir->i_sb);
361         struct inode *inode = d_inode(dentry);
362         struct qstr str;
363         char name[32];
364         u32 cnid;
365         int res;
366
367         if (HFSPLUS_IS_RSRC(inode))
368                 return -EPERM;
369
370         mutex_lock(&sbi->vh_mutex);
371         cnid = (u32)(unsigned long)dentry->d_fsdata;
372         if (inode->i_ino == cnid &&
373             atomic_read(&HFSPLUS_I(inode)->opencnt)) {
374                 str.name = name;
375                 str.len = sprintf(name, "temp%lu", inode->i_ino);
376                 res = hfsplus_rename_cat(inode->i_ino,
377                                          dir, &dentry->d_name,
378                                          sbi->hidden_dir, &str);
379                 if (!res) {
380                         inode->i_flags |= S_DEAD;
381                         drop_nlink(inode);
382                 }
383                 goto out;
384         }
385         res = hfsplus_delete_cat(cnid, dir, &dentry->d_name);
386         if (res)
387                 goto out;
388
389         if (inode->i_nlink > 0)
390                 drop_nlink(inode);
391         if (inode->i_ino == cnid)
392                 clear_nlink(inode);
393         if (!inode->i_nlink) {
394                 if (inode->i_ino != cnid) {
395                         sbi->file_count--;
396                         if (!atomic_read(&HFSPLUS_I(inode)->opencnt)) {
397                                 res = hfsplus_delete_cat(inode->i_ino,
398                                                          sbi->hidden_dir,
399                                                          NULL);
400                                 if (!res)
401                                         hfsplus_delete_inode(inode);
402                         } else
403                                 inode->i_flags |= S_DEAD;
404                 } else
405                         hfsplus_delete_inode(inode);
406         } else
407                 sbi->file_count--;
408         inode->i_ctime = current_time(inode);
409         mark_inode_dirty(inode);
410 out:
411         mutex_unlock(&sbi->vh_mutex);
412         return res;
413 }
414
415 static int hfsplus_rmdir(struct inode *dir, struct dentry *dentry)
416 {
417         struct hfsplus_sb_info *sbi = HFSPLUS_SB(dir->i_sb);
418         struct inode *inode = d_inode(dentry);
419         int res;
420
421         if (inode->i_size != 2)
422                 return -ENOTEMPTY;
423
424         mutex_lock(&sbi->vh_mutex);
425         res = hfsplus_delete_cat(inode->i_ino, dir, &dentry->d_name);
426         if (res)
427                 goto out;
428         clear_nlink(inode);
429         inode->i_ctime = current_time(inode);
430         hfsplus_delete_inode(inode);
431         mark_inode_dirty(inode);
432 out:
433         mutex_unlock(&sbi->vh_mutex);
434         return res;
435 }
436
437 static int hfsplus_symlink(struct inode *dir, struct dentry *dentry,
438                            const char *symname)
439 {
440         struct hfsplus_sb_info *sbi = HFSPLUS_SB(dir->i_sb);
441         struct inode *inode;
442         int res = -ENOMEM;
443
444         mutex_lock(&sbi->vh_mutex);
445         inode = hfsplus_new_inode(dir->i_sb, dir, S_IFLNK | S_IRWXUGO);
446         if (!inode)
447                 goto out;
448
449         res = page_symlink(inode, symname, strlen(symname) + 1);
450         if (res)
451                 goto out_err;
452
453         res = hfsplus_create_cat(inode->i_ino, dir, &dentry->d_name, inode);
454         if (res)
455                 goto out_err;
456
457         res = hfsplus_init_security(inode, dir, &dentry->d_name);
458         if (res == -EOPNOTSUPP)
459                 res = 0; /* Operation is not supported. */
460         else if (res) {
461                 /* Try to delete anyway without error analysis. */
462                 hfsplus_delete_cat(inode->i_ino, dir, &dentry->d_name);
463                 goto out_err;
464         }
465
466         hfsplus_instantiate(dentry, inode, inode->i_ino);
467         mark_inode_dirty(inode);
468         goto out;
469
470 out_err:
471         clear_nlink(inode);
472         hfsplus_delete_inode(inode);
473         iput(inode);
474 out:
475         mutex_unlock(&sbi->vh_mutex);
476         return res;
477 }
478
479 static int hfsplus_mknod(struct inode *dir, struct dentry *dentry,
480                          umode_t mode, dev_t rdev)
481 {
482         struct hfsplus_sb_info *sbi = HFSPLUS_SB(dir->i_sb);
483         struct inode *inode;
484         int res = -ENOMEM;
485
486         mutex_lock(&sbi->vh_mutex);
487         inode = hfsplus_new_inode(dir->i_sb, dir, mode);
488         if (!inode)
489                 goto out;
490
491         if (S_ISBLK(mode) || S_ISCHR(mode) || S_ISFIFO(mode) || S_ISSOCK(mode))
492                 init_special_inode(inode, mode, rdev);
493
494         res = hfsplus_create_cat(inode->i_ino, dir, &dentry->d_name, inode);
495         if (res)
496                 goto failed_mknod;
497
498         res = hfsplus_init_security(inode, dir, &dentry->d_name);
499         if (res == -EOPNOTSUPP)
500                 res = 0; /* Operation is not supported. */
501         else if (res) {
502                 /* Try to delete anyway without error analysis. */
503                 hfsplus_delete_cat(inode->i_ino, dir, &dentry->d_name);
504                 goto failed_mknod;
505         }
506
507         hfsplus_instantiate(dentry, inode, inode->i_ino);
508         mark_inode_dirty(inode);
509         goto out;
510
511 failed_mknod:
512         clear_nlink(inode);
513         hfsplus_delete_inode(inode);
514         iput(inode);
515 out:
516         mutex_unlock(&sbi->vh_mutex);
517         return res;
518 }
519
520 static int hfsplus_create(struct inode *dir, struct dentry *dentry, umode_t mode,
521                           bool excl)
522 {
523         return hfsplus_mknod(dir, dentry, mode, 0);
524 }
525
526 static int hfsplus_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode)
527 {
528         return hfsplus_mknod(dir, dentry, mode | S_IFDIR, 0);
529 }
530
531 static int hfsplus_rename(struct inode *old_dir, struct dentry *old_dentry,
532                           struct inode *new_dir, struct dentry *new_dentry,
533                           unsigned int flags)
534 {
535         int res;
536
537         if (flags & ~RENAME_NOREPLACE)
538                 return -EINVAL;
539
540         /* Unlink destination if it already exists */
541         if (d_really_is_positive(new_dentry)) {
542                 if (d_is_dir(new_dentry))
543                         res = hfsplus_rmdir(new_dir, new_dentry);
544                 else
545                         res = hfsplus_unlink(new_dir, new_dentry);
546                 if (res)
547                         return res;
548         }
549
550         res = hfsplus_rename_cat((u32)(unsigned long)old_dentry->d_fsdata,
551                                  old_dir, &old_dentry->d_name,
552                                  new_dir, &new_dentry->d_name);
553         if (!res)
554                 new_dentry->d_fsdata = old_dentry->d_fsdata;
555         return res;
556 }
557
558 const struct inode_operations hfsplus_dir_inode_operations = {
559         .lookup                 = hfsplus_lookup,
560         .create                 = hfsplus_create,
561         .link                   = hfsplus_link,
562         .unlink                 = hfsplus_unlink,
563         .mkdir                  = hfsplus_mkdir,
564         .rmdir                  = hfsplus_rmdir,
565         .symlink                = hfsplus_symlink,
566         .mknod                  = hfsplus_mknod,
567         .rename                 = hfsplus_rename,
568         .getattr                = hfsplus_getattr,
569         .listxattr              = hfsplus_listxattr,
570 };
571
572 const struct file_operations hfsplus_dir_operations = {
573         .fsync          = hfsplus_file_fsync,
574         .read           = generic_read_dir,
575         .iterate_shared = hfsplus_readdir,
576         .unlocked_ioctl = hfsplus_ioctl,
577         .llseek         = generic_file_llseek,
578         .release        = hfsplus_dir_release,
579 };