1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * linux/fs/hfsplus/catalog.c 4 * 5 * Copyright (C) 2001 6 * Brad Boyer (flar@allandria.com) 7 * (C) 2003 Ardis Technologies <roman@ardistech.com> 8 * 9 * Handling of catalog records 10 */ 11 12 13 #include "hfsplus_fs.h" 14 #include "hfsplus_raw.h" 15 16 int hfsplus_cat_case_cmp_key(const hfsplus_btree_key *k1, 17 const hfsplus_btree_key *k2) 18 { 19 __be32 k1p, k2p; 20 21 k1p = k1->cat.parent; 22 k2p = k2->cat.parent; 23 if (k1p != k2p) 24 return be32_to_cpu(k1p) < be32_to_cpu(k2p) ? -1 : 1; 25 26 return hfsplus_strcasecmp(&k1->cat.name, &k2->cat.name); 27 } 28 29 int hfsplus_cat_bin_cmp_key(const hfsplus_btree_key *k1, 30 const hfsplus_btree_key *k2) 31 { 32 __be32 k1p, k2p; 33 34 k1p = k1->cat.parent; 35 k2p = k2->cat.parent; 36 if (k1p != k2p) 37 return be32_to_cpu(k1p) < be32_to_cpu(k2p) ? -1 : 1; 38 39 return hfsplus_strcmp(&k1->cat.name, &k2->cat.name); 40 } 41 42 /* Generates key for catalog file/folders record. */ 43 int hfsplus_cat_build_key(struct super_block *sb, 44 hfsplus_btree_key *key, u32 parent, const struct qstr *str) 45 { 46 int len, err; 47 48 key->cat.parent = cpu_to_be32(parent); 49 err = hfsplus_asc2uni(sb, &key->cat.name, HFSPLUS_MAX_STRLEN, 50 str->name, str->len); 51 if (unlikely(err < 0)) 52 return err; 53 54 len = be16_to_cpu(key->cat.name.length); 55 key->key_len = cpu_to_be16(6 + 2 * len); 56 return 0; 57 } 58 59 /* Generates key for catalog thread record. */ 60 void hfsplus_cat_build_key_with_cnid(struct super_block *sb, 61 hfsplus_btree_key *key, u32 parent) 62 { 63 key->cat.parent = cpu_to_be32(parent); 64 key->cat.name.length = 0; 65 key->key_len = cpu_to_be16(6); 66 } 67 68 static void hfsplus_cat_build_key_uni(hfsplus_btree_key *key, u32 parent, 69 struct hfsplus_unistr *name) 70 { 71 int ustrlen; 72 73 ustrlen = be16_to_cpu(name->length); 74 key->cat.parent = cpu_to_be32(parent); 75 key->cat.name.length = cpu_to_be16(ustrlen); 76 ustrlen *= 2; 77 memcpy(key->cat.name.unicode, name->unicode, ustrlen); 78 key->key_len = cpu_to_be16(6 + ustrlen); 79 } 80 81 void hfsplus_cat_set_perms(struct inode *inode, struct hfsplus_perm *perms) 82 { 83 if (inode->i_flags & S_IMMUTABLE) 84 perms->rootflags |= HFSPLUS_FLG_IMMUTABLE; 85 else 86 perms->rootflags &= ~HFSPLUS_FLG_IMMUTABLE; 87 if (inode->i_flags & S_APPEND) 88 perms->rootflags |= HFSPLUS_FLG_APPEND; 89 else 90 perms->rootflags &= ~HFSPLUS_FLG_APPEND; 91 92 perms->userflags = HFSPLUS_I(inode)->userflags; 93 perms->mode = cpu_to_be16(inode->i_mode); 94 perms->owner = cpu_to_be32(i_uid_read(inode)); 95 perms->group = cpu_to_be32(i_gid_read(inode)); 96 97 if (S_ISREG(inode->i_mode)) 98 perms->dev = cpu_to_be32(inode->i_nlink); 99 else if (S_ISBLK(inode->i_mode) || S_ISCHR(inode->i_mode)) 100 perms->dev = cpu_to_be32(inode->i_rdev); 101 else 102 perms->dev = 0; 103 } 104 105 static int hfsplus_cat_build_record(hfsplus_cat_entry *entry, 106 u32 cnid, struct inode *inode) 107 { 108 struct hfsplus_sb_info *sbi = HFSPLUS_SB(inode->i_sb); 109 110 if (S_ISDIR(inode->i_mode)) { 111 struct hfsplus_cat_folder *folder; 112 113 folder = &entry->folder; 114 memset(folder, 0, sizeof(*folder)); 115 folder->type = cpu_to_be16(HFSPLUS_FOLDER); 116 if (test_bit(HFSPLUS_SB_HFSX, &sbi->flags)) 117 folder->flags |= cpu_to_be16(HFSPLUS_HAS_FOLDER_COUNT); 118 folder->id = cpu_to_be32(inode->i_ino); 119 HFSPLUS_I(inode)->create_date = 120 folder->create_date = 121 folder->content_mod_date = 122 folder->attribute_mod_date = 123 folder->access_date = hfsp_now2mt(); 124 hfsplus_cat_set_perms(inode, &folder->permissions); 125 if (inode == sbi->hidden_dir) 126 /* invisible and namelocked */ 127 folder->user_info.frFlags = cpu_to_be16(0x5000); 128 return sizeof(*folder); 129 } else { 130 struct hfsplus_cat_file *file; 131 132 file = &entry->file; 133 memset(file, 0, sizeof(*file)); 134 file->type = cpu_to_be16(HFSPLUS_FILE); 135 file->flags = cpu_to_be16(HFSPLUS_FILE_THREAD_EXISTS); 136 file->id = cpu_to_be32(cnid); 137 HFSPLUS_I(inode)->create_date = 138 file->create_date = 139 file->content_mod_date = 140 file->attribute_mod_date = 141 file->access_date = hfsp_now2mt(); 142 if (cnid == inode->i_ino) { 143 hfsplus_cat_set_perms(inode, &file->permissions); 144 if (S_ISLNK(inode->i_mode)) { 145 file->user_info.fdType = 146 cpu_to_be32(HFSP_SYMLINK_TYPE); 147 file->user_info.fdCreator = 148 cpu_to_be32(HFSP_SYMLINK_CREATOR); 149 } else { 150 file->user_info.fdType = 151 cpu_to_be32(sbi->type); 152 file->user_info.fdCreator = 153 cpu_to_be32(sbi->creator); 154 } 155 if (HFSPLUS_FLG_IMMUTABLE & 156 (file->permissions.rootflags | 157 file->permissions.userflags)) 158 file->flags |= 159 cpu_to_be16(HFSPLUS_FILE_LOCKED); 160 } else { 161 file->user_info.fdType = 162 cpu_to_be32(HFSP_HARDLINK_TYPE); 163 file->user_info.fdCreator = 164 cpu_to_be32(HFSP_HFSPLUS_CREATOR); 165 file->user_info.fdFlags = 166 cpu_to_be16(0x100); 167 file->create_date = 168 HFSPLUS_I(sbi->hidden_dir)->create_date; 169 file->permissions.dev = 170 cpu_to_be32(HFSPLUS_I(inode)->linkid); 171 } 172 return sizeof(*file); 173 } 174 } 175 176 static int hfsplus_fill_cat_thread(struct super_block *sb, 177 hfsplus_cat_entry *entry, int type, 178 u32 parentid, const struct qstr *str) 179 { 180 int err; 181 182 entry->type = cpu_to_be16(type); 183 entry->thread.reserved = 0; 184 entry->thread.parentID = cpu_to_be32(parentid); 185 err = hfsplus_asc2uni(sb, &entry->thread.nodeName, HFSPLUS_MAX_STRLEN, 186 str->name, str->len); 187 if (unlikely(err < 0)) 188 return err; 189 190 return 10 + be16_to_cpu(entry->thread.nodeName.length) * 2; 191 } 192 193 /* Try to get a catalog entry for given catalog id */ 194 int hfsplus_find_cat(struct super_block *sb, u32 cnid, 195 struct hfs_find_data *fd) 196 { 197 hfsplus_cat_entry tmp; 198 int err; 199 u16 type; 200 201 hfsplus_cat_build_key_with_cnid(sb, fd->search_key, cnid); 202 err = hfs_brec_read(fd, &tmp, sizeof(hfsplus_cat_entry)); 203 if (err) 204 return err; 205 206 type = be16_to_cpu(tmp.type); 207 if (type != HFSPLUS_FOLDER_THREAD && type != HFSPLUS_FILE_THREAD) { 208 pr_err("found bad thread record in catalog\n"); 209 return -EIO; 210 } 211 212 if (be16_to_cpu(tmp.thread.nodeName.length) > 255) { 213 pr_err("catalog name length corrupted\n"); 214 return -EIO; 215 } 216 217 hfsplus_cat_build_key_uni(fd->search_key, 218 be32_to_cpu(tmp.thread.parentID), 219 &tmp.thread.nodeName); 220 return hfs_brec_find(fd, hfs_find_rec_by_key); 221 } 222 223 static void hfsplus_subfolders_inc(struct inode *dir) 224 { 225 struct hfsplus_sb_info *sbi = HFSPLUS_SB(dir->i_sb); 226 227 if (test_bit(HFSPLUS_SB_HFSX, &sbi->flags)) { 228 /* 229 * Increment subfolder count. Note, the value is only meaningful 230 * for folders with HFSPLUS_HAS_FOLDER_COUNT flag set. 231 */ 232 HFSPLUS_I(dir)->subfolders++; 233 } 234 } 235 236 static void hfsplus_subfolders_dec(struct inode *dir) 237 { 238 struct hfsplus_sb_info *sbi = HFSPLUS_SB(dir->i_sb); 239 240 if (test_bit(HFSPLUS_SB_HFSX, &sbi->flags)) { 241 /* 242 * Decrement subfolder count. Note, the value is only meaningful 243 * for folders with HFSPLUS_HAS_FOLDER_COUNT flag set. 244 * 245 * Check for zero. Some subfolders may have been created 246 * by an implementation ignorant of this counter. 247 */ 248 if (HFSPLUS_I(dir)->subfolders) 249 HFSPLUS_I(dir)->subfolders--; 250 } 251 } 252 253 int hfsplus_create_cat(u32 cnid, struct inode *dir, 254 const struct qstr *str, struct inode *inode) 255 { 256 struct super_block *sb = dir->i_sb; 257 struct hfs_find_data fd; 258 hfsplus_cat_entry entry; 259 int entry_size; 260 int err; 261 262 hfs_dbg(CAT_MOD, "create_cat: %s,%u(%d)\n", 263 str->name, cnid, inode->i_nlink); 264 err = hfs_find_init(HFSPLUS_SB(sb)->cat_tree, &fd); 265 if (err) 266 return err; 267 268 hfsplus_cat_build_key_with_cnid(sb, fd.search_key, cnid); 269 entry_size = hfsplus_fill_cat_thread(sb, &entry, 270 S_ISDIR(inode->i_mode) ? 271 HFSPLUS_FOLDER_THREAD : HFSPLUS_FILE_THREAD, 272 dir->i_ino, str); 273 if (unlikely(entry_size < 0)) { 274 err = entry_size; 275 goto err2; 276 } 277 278 err = hfs_brec_find(&fd, hfs_find_rec_by_key); 279 if (err != -ENOENT) { 280 if (!err) 281 err = -EEXIST; 282 goto err2; 283 } 284 err = hfs_brec_insert(&fd, &entry, entry_size); 285 if (err) 286 goto err2; 287 288 err = hfsplus_cat_build_key(sb, fd.search_key, dir->i_ino, str); 289 if (unlikely(err)) 290 goto err1; 291 292 entry_size = hfsplus_cat_build_record(&entry, cnid, inode); 293 err = hfs_brec_find(&fd, hfs_find_rec_by_key); 294 if (err != -ENOENT) { 295 /* panic? */ 296 if (!err) 297 err = -EEXIST; 298 goto err1; 299 } 300 err = hfs_brec_insert(&fd, &entry, entry_size); 301 if (err) 302 goto err1; 303 304 dir->i_size++; 305 if (S_ISDIR(inode->i_mode)) 306 hfsplus_subfolders_inc(dir); 307 dir->i_mtime = dir->i_ctime = current_time(dir); 308 hfsplus_mark_inode_dirty(dir, HFSPLUS_I_CAT_DIRTY); 309 310 hfs_find_exit(&fd); 311 return 0; 312 313 err1: 314 hfsplus_cat_build_key_with_cnid(sb, fd.search_key, cnid); 315 if (!hfs_brec_find(&fd, hfs_find_rec_by_key)) 316 hfs_brec_remove(&fd); 317 err2: 318 hfs_find_exit(&fd); 319 return err; 320 } 321 322 int hfsplus_delete_cat(u32 cnid, struct inode *dir, const struct qstr *str) 323 { 324 struct super_block *sb = dir->i_sb; 325 struct hfs_find_data fd; 326 struct hfsplus_fork_raw fork; 327 struct list_head *pos; 328 int err, off; 329 u16 type; 330 331 hfs_dbg(CAT_MOD, "delete_cat: %s,%u\n", str ? str->name : NULL, cnid); 332 err = hfs_find_init(HFSPLUS_SB(sb)->cat_tree, &fd); 333 if (err) 334 return err; 335 336 if (!str) { 337 int len; 338 339 hfsplus_cat_build_key_with_cnid(sb, fd.search_key, cnid); 340 err = hfs_brec_find(&fd, hfs_find_rec_by_key); 341 if (err) 342 goto out; 343 344 off = fd.entryoffset + 345 offsetof(struct hfsplus_cat_thread, nodeName); 346 fd.search_key->cat.parent = cpu_to_be32(dir->i_ino); 347 hfs_bnode_read(fd.bnode, 348 &fd.search_key->cat.name.length, off, 2); 349 len = be16_to_cpu(fd.search_key->cat.name.length) * 2; 350 hfs_bnode_read(fd.bnode, 351 &fd.search_key->cat.name.unicode, 352 off + 2, len); 353 fd.search_key->key_len = cpu_to_be16(6 + len); 354 } else { 355 err = hfsplus_cat_build_key(sb, fd.search_key, dir->i_ino, str); 356 if (unlikely(err)) 357 goto out; 358 } 359 360 err = hfs_brec_find(&fd, hfs_find_rec_by_key); 361 if (err) 362 goto out; 363 364 type = hfs_bnode_read_u16(fd.bnode, fd.entryoffset); 365 if (type == HFSPLUS_FILE) { 366 #if 0 367 off = fd.entryoffset + offsetof(hfsplus_cat_file, data_fork); 368 hfs_bnode_read(fd.bnode, &fork, off, sizeof(fork)); 369 hfsplus_free_fork(sb, cnid, &fork, HFSPLUS_TYPE_DATA); 370 #endif 371 372 off = fd.entryoffset + 373 offsetof(struct hfsplus_cat_file, rsrc_fork); 374 hfs_bnode_read(fd.bnode, &fork, off, sizeof(fork)); 375 hfsplus_free_fork(sb, cnid, &fork, HFSPLUS_TYPE_RSRC); 376 } 377 378 /* we only need to take spinlock for exclusion with ->release() */ 379 spin_lock(&HFSPLUS_I(dir)->open_dir_lock); 380 list_for_each(pos, &HFSPLUS_I(dir)->open_dir_list) { 381 struct hfsplus_readdir_data *rd = 382 list_entry(pos, struct hfsplus_readdir_data, list); 383 if (fd.tree->keycmp(fd.search_key, (void *)&rd->key) < 0) 384 rd->file->f_pos--; 385 } 386 spin_unlock(&HFSPLUS_I(dir)->open_dir_lock); 387 388 err = hfs_brec_remove(&fd); 389 if (err) 390 goto out; 391 392 hfsplus_cat_build_key_with_cnid(sb, fd.search_key, cnid); 393 err = hfs_brec_find(&fd, hfs_find_rec_by_key); 394 if (err) 395 goto out; 396 397 err = hfs_brec_remove(&fd); 398 if (err) 399 goto out; 400 401 dir->i_size--; 402 if (type == HFSPLUS_FOLDER) 403 hfsplus_subfolders_dec(dir); 404 dir->i_mtime = dir->i_ctime = current_time(dir); 405 hfsplus_mark_inode_dirty(dir, HFSPLUS_I_CAT_DIRTY); 406 407 if (type == HFSPLUS_FILE || type == HFSPLUS_FOLDER) { 408 if (HFSPLUS_SB(sb)->attr_tree) 409 hfsplus_delete_all_attrs(dir, cnid); 410 } 411 412 out: 413 hfs_find_exit(&fd); 414 415 return err; 416 } 417 418 int hfsplus_rename_cat(u32 cnid, 419 struct inode *src_dir, const struct qstr *src_name, 420 struct inode *dst_dir, const struct qstr *dst_name) 421 { 422 struct super_block *sb = src_dir->i_sb; 423 struct hfs_find_data src_fd, dst_fd; 424 hfsplus_cat_entry entry; 425 int entry_size, type; 426 int err; 427 428 hfs_dbg(CAT_MOD, "rename_cat: %u - %lu,%s - %lu,%s\n", 429 cnid, src_dir->i_ino, src_name->name, 430 dst_dir->i_ino, dst_name->name); 431 err = hfs_find_init(HFSPLUS_SB(sb)->cat_tree, &src_fd); 432 if (err) 433 return err; 434 dst_fd = src_fd; 435 436 /* find the old dir entry and read the data */ 437 err = hfsplus_cat_build_key(sb, src_fd.search_key, 438 src_dir->i_ino, src_name); 439 if (unlikely(err)) 440 goto out; 441 442 err = hfs_brec_find(&src_fd, hfs_find_rec_by_key); 443 if (err) 444 goto out; 445 if (src_fd.entrylength > sizeof(entry) || src_fd.entrylength < 0) { 446 err = -EIO; 447 goto out; 448 } 449 450 hfs_bnode_read(src_fd.bnode, &entry, src_fd.entryoffset, 451 src_fd.entrylength); 452 type = be16_to_cpu(entry.type); 453 454 /* create new dir entry with the data from the old entry */ 455 err = hfsplus_cat_build_key(sb, dst_fd.search_key, 456 dst_dir->i_ino, dst_name); 457 if (unlikely(err)) 458 goto out; 459 460 err = hfs_brec_find(&dst_fd, hfs_find_rec_by_key); 461 if (err != -ENOENT) { 462 if (!err) 463 err = -EEXIST; 464 goto out; 465 } 466 467 err = hfs_brec_insert(&dst_fd, &entry, src_fd.entrylength); 468 if (err) 469 goto out; 470 dst_dir->i_size++; 471 if (type == HFSPLUS_FOLDER) 472 hfsplus_subfolders_inc(dst_dir); 473 dst_dir->i_mtime = dst_dir->i_ctime = current_time(dst_dir); 474 475 /* finally remove the old entry */ 476 err = hfsplus_cat_build_key(sb, src_fd.search_key, 477 src_dir->i_ino, src_name); 478 if (unlikely(err)) 479 goto out; 480 481 err = hfs_brec_find(&src_fd, hfs_find_rec_by_key); 482 if (err) 483 goto out; 484 err = hfs_brec_remove(&src_fd); 485 if (err) 486 goto out; 487 src_dir->i_size--; 488 if (type == HFSPLUS_FOLDER) 489 hfsplus_subfolders_dec(src_dir); 490 src_dir->i_mtime = src_dir->i_ctime = current_time(src_dir); 491 492 /* remove old thread entry */ 493 hfsplus_cat_build_key_with_cnid(sb, src_fd.search_key, cnid); 494 err = hfs_brec_find(&src_fd, hfs_find_rec_by_key); 495 if (err) 496 goto out; 497 type = hfs_bnode_read_u16(src_fd.bnode, src_fd.entryoffset); 498 err = hfs_brec_remove(&src_fd); 499 if (err) 500 goto out; 501 502 /* create new thread entry */ 503 hfsplus_cat_build_key_with_cnid(sb, dst_fd.search_key, cnid); 504 entry_size = hfsplus_fill_cat_thread(sb, &entry, type, 505 dst_dir->i_ino, dst_name); 506 if (unlikely(entry_size < 0)) { 507 err = entry_size; 508 goto out; 509 } 510 511 err = hfs_brec_find(&dst_fd, hfs_find_rec_by_key); 512 if (err != -ENOENT) { 513 if (!err) 514 err = -EEXIST; 515 goto out; 516 } 517 err = hfs_brec_insert(&dst_fd, &entry, entry_size); 518 519 hfsplus_mark_inode_dirty(dst_dir, HFSPLUS_I_CAT_DIRTY); 520 hfsplus_mark_inode_dirty(src_dir, HFSPLUS_I_CAT_DIRTY); 521 out: 522 hfs_bnode_put(dst_fd.bnode); 523 hfs_find_exit(&src_fd); 524 return err; 525 } 526