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