1d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only 21da177e4SLinus Torvalds /* 31da177e4SLinus Torvalds * linux/fs/adfs/dir_f.c 41da177e4SLinus Torvalds * 51da177e4SLinus Torvalds * Copyright (C) 1997-1999 Russell King 61da177e4SLinus Torvalds * 71da177e4SLinus Torvalds * E and F format directory handling 81da177e4SLinus Torvalds */ 91da177e4SLinus Torvalds #include "adfs.h" 101da177e4SLinus Torvalds #include "dir_f.h" 111da177e4SLinus Torvalds 121da177e4SLinus Torvalds /* 131da177e4SLinus Torvalds * Read an (unaligned) value of length 1..4 bytes 141da177e4SLinus Torvalds */ 151da177e4SLinus Torvalds static inline unsigned int adfs_readval(unsigned char *p, int len) 161da177e4SLinus Torvalds { 171da177e4SLinus Torvalds unsigned int val = 0; 181da177e4SLinus Torvalds 191da177e4SLinus Torvalds switch (len) { 201da177e4SLinus Torvalds case 4: val |= p[3] << 24; 2174f79099SGustavo A. R. Silva /* fall through */ 221da177e4SLinus Torvalds case 3: val |= p[2] << 16; 2374f79099SGustavo A. R. Silva /* fall through */ 241da177e4SLinus Torvalds case 2: val |= p[1] << 8; 2574f79099SGustavo A. R. Silva /* fall through */ 261da177e4SLinus Torvalds default: val |= p[0]; 271da177e4SLinus Torvalds } 281da177e4SLinus Torvalds return val; 291da177e4SLinus Torvalds } 301da177e4SLinus Torvalds 311da177e4SLinus Torvalds static inline void adfs_writeval(unsigned char *p, int len, unsigned int val) 321da177e4SLinus Torvalds { 331da177e4SLinus Torvalds switch (len) { 341da177e4SLinus Torvalds case 4: p[3] = val >> 24; 3574f79099SGustavo A. R. Silva /* fall through */ 361da177e4SLinus Torvalds case 3: p[2] = val >> 16; 3774f79099SGustavo A. R. Silva /* fall through */ 381da177e4SLinus Torvalds case 2: p[1] = val >> 8; 3974f79099SGustavo A. R. Silva /* fall through */ 401da177e4SLinus Torvalds default: p[0] = val; 411da177e4SLinus Torvalds } 421da177e4SLinus Torvalds } 431da177e4SLinus Torvalds 441da177e4SLinus Torvalds #define ror13(v) ((v >> 13) | (v << 19)) 451da177e4SLinus Torvalds 461da177e4SLinus Torvalds #define dir_u8(idx) \ 471da177e4SLinus Torvalds ({ int _buf = idx >> blocksize_bits; \ 481da177e4SLinus Torvalds int _off = idx - (_buf << blocksize_bits);\ 491da177e4SLinus Torvalds *(u8 *)(bh[_buf]->b_data + _off); \ 501da177e4SLinus Torvalds }) 511da177e4SLinus Torvalds 521da177e4SLinus Torvalds #define dir_u32(idx) \ 531da177e4SLinus Torvalds ({ int _buf = idx >> blocksize_bits; \ 541da177e4SLinus Torvalds int _off = idx - (_buf << blocksize_bits);\ 551da177e4SLinus Torvalds *(__le32 *)(bh[_buf]->b_data + _off); \ 561da177e4SLinus Torvalds }) 571da177e4SLinus Torvalds 581da177e4SLinus Torvalds #define bufoff(_bh,_idx) \ 591da177e4SLinus Torvalds ({ int _buf = _idx >> blocksize_bits; \ 601da177e4SLinus Torvalds int _off = _idx - (_buf << blocksize_bits);\ 61016936b3SRussell King (void *)(_bh[_buf]->b_data + _off); \ 621da177e4SLinus Torvalds }) 631da177e4SLinus Torvalds 641da177e4SLinus Torvalds /* 651da177e4SLinus Torvalds * There are some algorithms that are nice in 661da177e4SLinus Torvalds * assembler, but a bitch in C... This is one 671da177e4SLinus Torvalds * of them. 681da177e4SLinus Torvalds */ 691da177e4SLinus Torvalds static u8 701da177e4SLinus Torvalds adfs_dir_checkbyte(const struct adfs_dir *dir) 711da177e4SLinus Torvalds { 721da177e4SLinus Torvalds struct buffer_head * const *bh = dir->bh; 731da177e4SLinus Torvalds const int blocksize_bits = dir->sb->s_blocksize_bits; 741da177e4SLinus Torvalds union { __le32 *ptr32; u8 *ptr8; } ptr, end; 751da177e4SLinus Torvalds u32 dircheck = 0; 761da177e4SLinus Torvalds int last = 5 - 26; 771da177e4SLinus Torvalds int i = 0; 781da177e4SLinus Torvalds 791da177e4SLinus Torvalds /* 801da177e4SLinus Torvalds * Accumulate each word up to the last whole 811da177e4SLinus Torvalds * word of the last directory entry. This 821da177e4SLinus Torvalds * can spread across several buffer heads. 831da177e4SLinus Torvalds */ 841da177e4SLinus Torvalds do { 851da177e4SLinus Torvalds last += 26; 861da177e4SLinus Torvalds do { 871da177e4SLinus Torvalds dircheck = le32_to_cpu(dir_u32(i)) ^ ror13(dircheck); 881da177e4SLinus Torvalds 891da177e4SLinus Torvalds i += sizeof(u32); 901da177e4SLinus Torvalds } while (i < (last & ~3)); 911da177e4SLinus Torvalds } while (dir_u8(last) != 0); 921da177e4SLinus Torvalds 931da177e4SLinus Torvalds /* 941da177e4SLinus Torvalds * Accumulate the last few bytes. These 951da177e4SLinus Torvalds * bytes will be within the same bh. 961da177e4SLinus Torvalds */ 971da177e4SLinus Torvalds if (i != last) { 981da177e4SLinus Torvalds ptr.ptr8 = bufoff(bh, i); 991da177e4SLinus Torvalds end.ptr8 = ptr.ptr8 + last - i; 1001da177e4SLinus Torvalds 101e5949050SHarvey Harrison do { 1021da177e4SLinus Torvalds dircheck = *ptr.ptr8++ ^ ror13(dircheck); 103e5949050SHarvey Harrison } while (ptr.ptr8 < end.ptr8); 1041da177e4SLinus Torvalds } 1051da177e4SLinus Torvalds 1061da177e4SLinus Torvalds /* 1071da177e4SLinus Torvalds * The directory tail is in the final bh 1081da177e4SLinus Torvalds * Note that contary to the RISC OS PRMs, 1091da177e4SLinus Torvalds * the first few bytes are NOT included 1101da177e4SLinus Torvalds * in the check. All bytes are in the 1111da177e4SLinus Torvalds * same bh. 1121da177e4SLinus Torvalds */ 1131da177e4SLinus Torvalds ptr.ptr8 = bufoff(bh, 2008); 1141da177e4SLinus Torvalds end.ptr8 = ptr.ptr8 + 36; 1151da177e4SLinus Torvalds 1161da177e4SLinus Torvalds do { 1171da177e4SLinus Torvalds __le32 v = *ptr.ptr32++; 1181da177e4SLinus Torvalds dircheck = le32_to_cpu(v) ^ ror13(dircheck); 1191da177e4SLinus Torvalds } while (ptr.ptr32 < end.ptr32); 1201da177e4SLinus Torvalds 1211da177e4SLinus Torvalds return (dircheck ^ (dircheck >> 8) ^ (dircheck >> 16) ^ (dircheck >> 24)) & 0xff; 1221da177e4SLinus Torvalds } 1231da177e4SLinus Torvalds 124ffc8df34SRussell King static int adfs_f_validate(struct adfs_dir *dir) 125ffc8df34SRussell King { 126ffc8df34SRussell King struct adfs_dirheader *head = dir->dirhead; 127ffc8df34SRussell King struct adfs_newdirtail *tail = dir->newtail; 128ffc8df34SRussell King 129ffc8df34SRussell King if (head->startmasseq != tail->endmasseq || 1307a0e4048SRussell King tail->dirlastmask || tail->reserved[0] || tail->reserved[1] || 131ffc8df34SRussell King (memcmp(&head->startname, "Nick", 4) && 132ffc8df34SRussell King memcmp(&head->startname, "Hugo", 4)) || 133ffc8df34SRussell King memcmp(&head->startname, &tail->endname, 4) || 134ffc8df34SRussell King adfs_dir_checkbyte(dir) != tail->dircheckbyte) 135ffc8df34SRussell King return -EIO; 136ffc8df34SRussell King 137ffc8df34SRussell King return 0; 138ffc8df34SRussell King } 139ffc8df34SRussell King 1405ed70bb4SRussell King /* Read and check that a directory is valid */ 1415ed70bb4SRussell King static int adfs_dir_read(struct super_block *sb, u32 indaddr, 1421da177e4SLinus Torvalds unsigned int size, struct adfs_dir *dir) 1431da177e4SLinus Torvalds { 1441da177e4SLinus Torvalds const unsigned int blocksize_bits = sb->s_blocksize_bits; 145419a6e5eSRussell King int ret; 1461da177e4SLinus Torvalds 1471da177e4SLinus Torvalds /* 1481da177e4SLinus Torvalds * Directories which are not a multiple of 2048 bytes 1491da177e4SLinus Torvalds * are considered bad v2 [3.6] 1501da177e4SLinus Torvalds */ 1511da177e4SLinus Torvalds if (size & 2047) 1521da177e4SLinus Torvalds goto bad_dir; 1531da177e4SLinus Torvalds 154419a6e5eSRussell King ret = adfs_dir_read_buffers(sb, indaddr, size, dir); 155419a6e5eSRussell King if (ret) 156419a6e5eSRussell King return ret; 1571da177e4SLinus Torvalds 158016936b3SRussell King dir->dirhead = bufoff(dir->bh, 0); 159016936b3SRussell King dir->newtail = bufoff(dir->bh, 2007); 1601da177e4SLinus Torvalds 161ffc8df34SRussell King if (adfs_f_validate(dir)) 1621da177e4SLinus Torvalds goto bad_dir; 1631da177e4SLinus Torvalds 1641da177e4SLinus Torvalds return 0; 1651da177e4SLinus Torvalds 1661da177e4SLinus Torvalds bad_dir: 1675ed70bb4SRussell King adfs_error(sb, "dir %06x is corrupted", indaddr); 1681dd9f5baSRussell King adfs_dir_relse(dir); 1691da177e4SLinus Torvalds 1701da177e4SLinus Torvalds return -EIO; 1711da177e4SLinus Torvalds } 1721da177e4SLinus Torvalds 1731da177e4SLinus Torvalds /* 1741da177e4SLinus Torvalds * convert a disk-based directory entry to a Linux ADFS directory entry 1751da177e4SLinus Torvalds */ 1761da177e4SLinus Torvalds static inline void 177da23ef05SStuart Swales adfs_dir2obj(struct adfs_dir *dir, struct object_info *obj, 178da23ef05SStuart Swales struct adfs_direntry *de) 1791da177e4SLinus Torvalds { 180adb514a4SRussell King unsigned int name_len; 181adb514a4SRussell King 182adb514a4SRussell King for (name_len = 0; name_len < ADFS_F_NAME_LEN; name_len++) { 183adb514a4SRussell King if (de->dirobname[name_len] < ' ') 184adb514a4SRussell King break; 185adb514a4SRussell King 186adb514a4SRussell King obj->name[name_len] = de->dirobname[name_len]; 187adb514a4SRussell King } 188adb514a4SRussell King 189adb514a4SRussell King obj->name_len = name_len; 1905ed70bb4SRussell King obj->indaddr = adfs_readval(de->dirinddiscadd, 3); 1911da177e4SLinus Torvalds obj->loadaddr = adfs_readval(de->dirload, 4); 1921da177e4SLinus Torvalds obj->execaddr = adfs_readval(de->direxec, 4); 1931da177e4SLinus Torvalds obj->size = adfs_readval(de->dirlen, 4); 1941da177e4SLinus Torvalds obj->attr = de->newdiratts; 195da23ef05SStuart Swales 196411c49bcSRussell King adfs_object_fixup(dir, obj); 1971da177e4SLinus Torvalds } 1981da177e4SLinus Torvalds 1991da177e4SLinus Torvalds /* 2001da177e4SLinus Torvalds * convert a Linux ADFS directory entry to a disk-based directory entry 2011da177e4SLinus Torvalds */ 2021da177e4SLinus Torvalds static inline void 2031da177e4SLinus Torvalds adfs_obj2dir(struct adfs_direntry *de, struct object_info *obj) 2041da177e4SLinus Torvalds { 2055ed70bb4SRussell King adfs_writeval(de->dirinddiscadd, 3, obj->indaddr); 2061da177e4SLinus Torvalds adfs_writeval(de->dirload, 4, obj->loadaddr); 2071da177e4SLinus Torvalds adfs_writeval(de->direxec, 4, obj->execaddr); 2081da177e4SLinus Torvalds adfs_writeval(de->dirlen, 4, obj->size); 2091da177e4SLinus Torvalds de->newdiratts = obj->attr; 2101da177e4SLinus Torvalds } 2111da177e4SLinus Torvalds 2121da177e4SLinus Torvalds /* 2131da177e4SLinus Torvalds * get a directory entry. Note that the caller is responsible 2141da177e4SLinus Torvalds * for holding the relevant locks. 2151da177e4SLinus Torvalds */ 2161da177e4SLinus Torvalds static int 2171da177e4SLinus Torvalds __adfs_dir_get(struct adfs_dir *dir, int pos, struct object_info *obj) 2181da177e4SLinus Torvalds { 2191da177e4SLinus Torvalds struct adfs_direntry de; 220a317120bSRussell King int ret; 2211da177e4SLinus Torvalds 222a317120bSRussell King ret = adfs_dir_copyfrom(&de, dir, pos, 26); 223a317120bSRussell King if (ret) 224a317120bSRussell King return ret; 2251da177e4SLinus Torvalds 2261da177e4SLinus Torvalds if (!de.dirobname[0]) 2271da177e4SLinus Torvalds return -ENOENT; 2281da177e4SLinus Torvalds 229da23ef05SStuart Swales adfs_dir2obj(dir, obj, &de); 2301da177e4SLinus Torvalds 2311da177e4SLinus Torvalds return 0; 2321da177e4SLinus Torvalds } 2331da177e4SLinus Torvalds 2341da177e4SLinus Torvalds static int 2351da177e4SLinus Torvalds __adfs_dir_put(struct adfs_dir *dir, int pos, struct object_info *obj) 2361da177e4SLinus Torvalds { 2371da177e4SLinus Torvalds struct adfs_direntry de; 238a317120bSRussell King int ret; 2391da177e4SLinus Torvalds 240a317120bSRussell King ret = adfs_dir_copyfrom(&de, dir, pos, 26); 241a317120bSRussell King if (ret) 242a317120bSRussell King return ret; 2431da177e4SLinus Torvalds 2441da177e4SLinus Torvalds adfs_obj2dir(&de, obj); 2451da177e4SLinus Torvalds 246a317120bSRussell King return adfs_dir_copyto(dir, pos, &de, 26); 2471da177e4SLinus Torvalds } 2481da177e4SLinus Torvalds 2491da177e4SLinus Torvalds /* 2501da177e4SLinus Torvalds * the caller is responsible for holding the necessary 2511da177e4SLinus Torvalds * locks. 2521da177e4SLinus Torvalds */ 2535ed70bb4SRussell King static int adfs_dir_find_entry(struct adfs_dir *dir, u32 indaddr) 2541da177e4SLinus Torvalds { 2551da177e4SLinus Torvalds int pos, ret; 2561da177e4SLinus Torvalds 2571da177e4SLinus Torvalds ret = -ENOENT; 2581da177e4SLinus Torvalds 2591da177e4SLinus Torvalds for (pos = 5; pos < ADFS_NUM_DIR_ENTRIES * 26 + 5; pos += 26) { 2601da177e4SLinus Torvalds struct object_info obj; 2611da177e4SLinus Torvalds 2621da177e4SLinus Torvalds if (!__adfs_dir_get(dir, pos, &obj)) 2631da177e4SLinus Torvalds break; 2641da177e4SLinus Torvalds 2655ed70bb4SRussell King if (obj.indaddr == indaddr) { 2661da177e4SLinus Torvalds ret = pos; 2671da177e4SLinus Torvalds break; 2681da177e4SLinus Torvalds } 2691da177e4SLinus Torvalds } 2701da177e4SLinus Torvalds 2711da177e4SLinus Torvalds return ret; 2721da177e4SLinus Torvalds } 2731da177e4SLinus Torvalds 2745ed70bb4SRussell King static int adfs_f_read(struct super_block *sb, u32 indaddr, unsigned int size, 2755ed70bb4SRussell King struct adfs_dir *dir) 2761da177e4SLinus Torvalds { 2771da177e4SLinus Torvalds int ret; 2781da177e4SLinus Torvalds 2795ed70bb4SRussell King if (size != ADFS_NEWDIR_SIZE) 2801da177e4SLinus Torvalds return -EIO; 2811da177e4SLinus Torvalds 2825ed70bb4SRussell King ret = adfs_dir_read(sb, indaddr, size, dir); 2831da177e4SLinus Torvalds if (ret) 2841da177e4SLinus Torvalds adfs_error(sb, "unable to read directory"); 2851da177e4SLinus Torvalds else 286016936b3SRussell King dir->parent_id = adfs_readval(dir->newtail->dirparent, 3); 2871da177e4SLinus Torvalds 2881da177e4SLinus Torvalds return ret; 2891da177e4SLinus Torvalds } 2901da177e4SLinus Torvalds 2911da177e4SLinus Torvalds static int 2921da177e4SLinus Torvalds adfs_f_setpos(struct adfs_dir *dir, unsigned int fpos) 2931da177e4SLinus Torvalds { 2941da177e4SLinus Torvalds if (fpos >= ADFS_NUM_DIR_ENTRIES) 2951da177e4SLinus Torvalds return -ENOENT; 2961da177e4SLinus Torvalds 2971da177e4SLinus Torvalds dir->pos = 5 + fpos * 26; 2981da177e4SLinus Torvalds return 0; 2991da177e4SLinus Torvalds } 3001da177e4SLinus Torvalds 3011da177e4SLinus Torvalds static int 3021da177e4SLinus Torvalds adfs_f_getnext(struct adfs_dir *dir, struct object_info *obj) 3031da177e4SLinus Torvalds { 3041da177e4SLinus Torvalds unsigned int ret; 3051da177e4SLinus Torvalds 3061da177e4SLinus Torvalds ret = __adfs_dir_get(dir, dir->pos, obj); 3071da177e4SLinus Torvalds if (ret == 0) 3081da177e4SLinus Torvalds dir->pos += 26; 3091da177e4SLinus Torvalds 3101da177e4SLinus Torvalds return ret; 3111da177e4SLinus Torvalds } 3121da177e4SLinus Torvalds 3134287e4deSRussell King static int adfs_f_iterate(struct adfs_dir *dir, struct dir_context *ctx) 3144287e4deSRussell King { 3154287e4deSRussell King struct object_info obj; 3164287e4deSRussell King int pos = 5 + (ctx->pos - 2) * 26; 3174287e4deSRussell King 3184287e4deSRussell King while (ctx->pos < 2 + ADFS_NUM_DIR_ENTRIES) { 3194287e4deSRussell King if (__adfs_dir_get(dir, pos, &obj)) 3204287e4deSRussell King break; 3214287e4deSRussell King if (!dir_emit(ctx, obj.name, obj.name_len, 3224287e4deSRussell King obj.indaddr, DT_UNKNOWN)) 3234287e4deSRussell King break; 3244287e4deSRussell King pos += 26; 3254287e4deSRussell King ctx->pos++; 3264287e4deSRussell King } 3274287e4deSRussell King return 0; 3284287e4deSRussell King } 3294287e4deSRussell King 3301da177e4SLinus Torvalds static int 3311da177e4SLinus Torvalds adfs_f_update(struct adfs_dir *dir, struct object_info *obj) 3321da177e4SLinus Torvalds { 333c3c8149bSRussell King int ret; 3341da177e4SLinus Torvalds 3355ed70bb4SRussell King ret = adfs_dir_find_entry(dir, obj->indaddr); 3361da177e4SLinus Torvalds if (ret < 0) { 3371da177e4SLinus Torvalds adfs_error(dir->sb, "unable to locate entry to update"); 338ffc8df34SRussell King return ret; 3391da177e4SLinus Torvalds } 3401da177e4SLinus Torvalds 3411da177e4SLinus Torvalds __adfs_dir_put(dir, ret, obj); 3421da177e4SLinus Torvalds 3431da177e4SLinus Torvalds /* 3441da177e4SLinus Torvalds * Increment directory sequence number 3451da177e4SLinus Torvalds */ 346016936b3SRussell King dir->dirhead->startmasseq += 1; 347016936b3SRussell King dir->newtail->endmasseq += 1; 3481da177e4SLinus Torvalds 3491da177e4SLinus Torvalds ret = adfs_dir_checkbyte(dir); 3501da177e4SLinus Torvalds /* 3511da177e4SLinus Torvalds * Update directory check byte 3521da177e4SLinus Torvalds */ 353016936b3SRussell King dir->newtail->dircheckbyte = ret; 3541da177e4SLinus Torvalds 355ffc8df34SRussell King ret = adfs_f_validate(dir); 356ffc8df34SRussell King if (ret) 3571da177e4SLinus Torvalds adfs_error(dir->sb, "whoops! I broke a directory!"); 358ffc8df34SRussell King 359ffc8df34SRussell King return ret; 3601da177e4SLinus Torvalds } 3611da177e4SLinus Torvalds 3620125f504SJulia Lawall const struct adfs_dir_ops adfs_f_dir_ops = { 3631da177e4SLinus Torvalds .read = adfs_f_read, 3644287e4deSRussell King .iterate = adfs_f_iterate, 3651da177e4SLinus Torvalds .setpos = adfs_f_setpos, 3661da177e4SLinus Torvalds .getnext = adfs_f_getnext, 3671da177e4SLinus Torvalds .update = adfs_f_update, 3681da177e4SLinus Torvalds }; 369