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