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 1245ed70bb4SRussell King /* Read and check that a directory is valid */ 1255ed70bb4SRussell King static int adfs_dir_read(struct super_block *sb, u32 indaddr, 1261da177e4SLinus Torvalds unsigned int size, struct adfs_dir *dir) 1271da177e4SLinus Torvalds { 1281da177e4SLinus Torvalds const unsigned int blocksize_bits = sb->s_blocksize_bits; 129419a6e5eSRussell King int ret; 1301da177e4SLinus Torvalds 1311da177e4SLinus Torvalds /* 1321da177e4SLinus Torvalds * Directories which are not a multiple of 2048 bytes 1331da177e4SLinus Torvalds * are considered bad v2 [3.6] 1341da177e4SLinus Torvalds */ 1351da177e4SLinus Torvalds if (size & 2047) 1361da177e4SLinus Torvalds goto bad_dir; 1371da177e4SLinus Torvalds 138419a6e5eSRussell King ret = adfs_dir_read_buffers(sb, indaddr, size, dir); 139419a6e5eSRussell King if (ret) 140419a6e5eSRussell King return ret; 1411da177e4SLinus Torvalds 142016936b3SRussell King dir->dirhead = bufoff(dir->bh, 0); 143016936b3SRussell King dir->newtail = bufoff(dir->bh, 2007); 1441da177e4SLinus Torvalds 145016936b3SRussell King if (dir->dirhead->startmasseq != dir->newtail->endmasseq || 146016936b3SRussell King memcmp(&dir->dirhead->startname, &dir->newtail->endname, 4)) 1471da177e4SLinus Torvalds goto bad_dir; 1481da177e4SLinus Torvalds 149016936b3SRussell King if (memcmp(&dir->dirhead->startname, "Nick", 4) && 150016936b3SRussell King memcmp(&dir->dirhead->startname, "Hugo", 4)) 1511da177e4SLinus Torvalds goto bad_dir; 1521da177e4SLinus Torvalds 153016936b3SRussell King if (adfs_dir_checkbyte(dir) != dir->newtail->dircheckbyte) 1541da177e4SLinus Torvalds goto bad_dir; 1551da177e4SLinus Torvalds 1561da177e4SLinus Torvalds return 0; 1571da177e4SLinus Torvalds 1581da177e4SLinus Torvalds bad_dir: 1595ed70bb4SRussell King adfs_error(sb, "dir %06x is corrupted", indaddr); 1601dd9f5baSRussell King adfs_dir_relse(dir); 1611da177e4SLinus Torvalds 1621da177e4SLinus Torvalds return -EIO; 1631da177e4SLinus Torvalds } 1641da177e4SLinus Torvalds 1651da177e4SLinus Torvalds /* 1661da177e4SLinus Torvalds * convert a disk-based directory entry to a Linux ADFS directory entry 1671da177e4SLinus Torvalds */ 1681da177e4SLinus Torvalds static inline void 169da23ef05SStuart Swales adfs_dir2obj(struct adfs_dir *dir, struct object_info *obj, 170da23ef05SStuart Swales struct adfs_direntry *de) 1711da177e4SLinus Torvalds { 172adb514a4SRussell King unsigned int name_len; 173adb514a4SRussell King 174adb514a4SRussell King for (name_len = 0; name_len < ADFS_F_NAME_LEN; name_len++) { 175adb514a4SRussell King if (de->dirobname[name_len] < ' ') 176adb514a4SRussell King break; 177adb514a4SRussell King 178adb514a4SRussell King obj->name[name_len] = de->dirobname[name_len]; 179adb514a4SRussell King } 180adb514a4SRussell King 181adb514a4SRussell King obj->name_len = name_len; 1825ed70bb4SRussell King obj->indaddr = adfs_readval(de->dirinddiscadd, 3); 1831da177e4SLinus Torvalds obj->loadaddr = adfs_readval(de->dirload, 4); 1841da177e4SLinus Torvalds obj->execaddr = adfs_readval(de->direxec, 4); 1851da177e4SLinus Torvalds obj->size = adfs_readval(de->dirlen, 4); 1861da177e4SLinus Torvalds obj->attr = de->newdiratts; 187da23ef05SStuart Swales 188411c49bcSRussell King adfs_object_fixup(dir, obj); 1891da177e4SLinus Torvalds } 1901da177e4SLinus Torvalds 1911da177e4SLinus Torvalds /* 1921da177e4SLinus Torvalds * convert a Linux ADFS directory entry to a disk-based directory entry 1931da177e4SLinus Torvalds */ 1941da177e4SLinus Torvalds static inline void 1951da177e4SLinus Torvalds adfs_obj2dir(struct adfs_direntry *de, struct object_info *obj) 1961da177e4SLinus Torvalds { 1975ed70bb4SRussell King adfs_writeval(de->dirinddiscadd, 3, obj->indaddr); 1981da177e4SLinus Torvalds adfs_writeval(de->dirload, 4, obj->loadaddr); 1991da177e4SLinus Torvalds adfs_writeval(de->direxec, 4, obj->execaddr); 2001da177e4SLinus Torvalds adfs_writeval(de->dirlen, 4, obj->size); 2011da177e4SLinus Torvalds de->newdiratts = obj->attr; 2021da177e4SLinus Torvalds } 2031da177e4SLinus Torvalds 2041da177e4SLinus Torvalds /* 2051da177e4SLinus Torvalds * get a directory entry. Note that the caller is responsible 2061da177e4SLinus Torvalds * for holding the relevant locks. 2071da177e4SLinus Torvalds */ 2081da177e4SLinus Torvalds static int 2091da177e4SLinus Torvalds __adfs_dir_get(struct adfs_dir *dir, int pos, struct object_info *obj) 2101da177e4SLinus Torvalds { 2111da177e4SLinus Torvalds struct adfs_direntry de; 212a317120bSRussell King int ret; 2131da177e4SLinus Torvalds 214a317120bSRussell King ret = adfs_dir_copyfrom(&de, dir, pos, 26); 215a317120bSRussell King if (ret) 216a317120bSRussell King return ret; 2171da177e4SLinus Torvalds 2181da177e4SLinus Torvalds if (!de.dirobname[0]) 2191da177e4SLinus Torvalds return -ENOENT; 2201da177e4SLinus Torvalds 221da23ef05SStuart Swales adfs_dir2obj(dir, obj, &de); 2221da177e4SLinus Torvalds 2231da177e4SLinus Torvalds return 0; 2241da177e4SLinus Torvalds } 2251da177e4SLinus Torvalds 2261da177e4SLinus Torvalds static int 2271da177e4SLinus Torvalds __adfs_dir_put(struct adfs_dir *dir, int pos, struct object_info *obj) 2281da177e4SLinus Torvalds { 2291da177e4SLinus Torvalds struct adfs_direntry de; 230a317120bSRussell King int ret; 2311da177e4SLinus Torvalds 232a317120bSRussell King ret = adfs_dir_copyfrom(&de, dir, pos, 26); 233a317120bSRussell King if (ret) 234a317120bSRussell King return ret; 2351da177e4SLinus Torvalds 2361da177e4SLinus Torvalds adfs_obj2dir(&de, obj); 2371da177e4SLinus Torvalds 238a317120bSRussell King return adfs_dir_copyto(dir, pos, &de, 26); 2391da177e4SLinus Torvalds } 2401da177e4SLinus Torvalds 2411da177e4SLinus Torvalds /* 2421da177e4SLinus Torvalds * the caller is responsible for holding the necessary 2431da177e4SLinus Torvalds * locks. 2441da177e4SLinus Torvalds */ 2455ed70bb4SRussell King static int adfs_dir_find_entry(struct adfs_dir *dir, u32 indaddr) 2461da177e4SLinus Torvalds { 2471da177e4SLinus Torvalds int pos, ret; 2481da177e4SLinus Torvalds 2491da177e4SLinus Torvalds ret = -ENOENT; 2501da177e4SLinus Torvalds 2511da177e4SLinus Torvalds for (pos = 5; pos < ADFS_NUM_DIR_ENTRIES * 26 + 5; pos += 26) { 2521da177e4SLinus Torvalds struct object_info obj; 2531da177e4SLinus Torvalds 2541da177e4SLinus Torvalds if (!__adfs_dir_get(dir, pos, &obj)) 2551da177e4SLinus Torvalds break; 2561da177e4SLinus Torvalds 2575ed70bb4SRussell King if (obj.indaddr == indaddr) { 2581da177e4SLinus Torvalds ret = pos; 2591da177e4SLinus Torvalds break; 2601da177e4SLinus Torvalds } 2611da177e4SLinus Torvalds } 2621da177e4SLinus Torvalds 2631da177e4SLinus Torvalds return ret; 2641da177e4SLinus Torvalds } 2651da177e4SLinus Torvalds 2665ed70bb4SRussell King static int adfs_f_read(struct super_block *sb, u32 indaddr, unsigned int size, 2675ed70bb4SRussell King struct adfs_dir *dir) 2681da177e4SLinus Torvalds { 2691da177e4SLinus Torvalds int ret; 2701da177e4SLinus Torvalds 2715ed70bb4SRussell King if (size != ADFS_NEWDIR_SIZE) 2721da177e4SLinus Torvalds return -EIO; 2731da177e4SLinus Torvalds 2745ed70bb4SRussell King ret = adfs_dir_read(sb, indaddr, size, dir); 2751da177e4SLinus Torvalds if (ret) 2761da177e4SLinus Torvalds adfs_error(sb, "unable to read directory"); 2771da177e4SLinus Torvalds else 278016936b3SRussell King dir->parent_id = adfs_readval(dir->newtail->dirparent, 3); 2791da177e4SLinus Torvalds 2801da177e4SLinus Torvalds return ret; 2811da177e4SLinus Torvalds } 2821da177e4SLinus Torvalds 2831da177e4SLinus Torvalds static int 2841da177e4SLinus Torvalds adfs_f_setpos(struct adfs_dir *dir, unsigned int fpos) 2851da177e4SLinus Torvalds { 2861da177e4SLinus Torvalds if (fpos >= ADFS_NUM_DIR_ENTRIES) 2871da177e4SLinus Torvalds return -ENOENT; 2881da177e4SLinus Torvalds 2891da177e4SLinus Torvalds dir->pos = 5 + fpos * 26; 2901da177e4SLinus Torvalds return 0; 2911da177e4SLinus Torvalds } 2921da177e4SLinus Torvalds 2931da177e4SLinus Torvalds static int 2941da177e4SLinus Torvalds adfs_f_getnext(struct adfs_dir *dir, struct object_info *obj) 2951da177e4SLinus Torvalds { 2961da177e4SLinus Torvalds unsigned int ret; 2971da177e4SLinus Torvalds 2981da177e4SLinus Torvalds ret = __adfs_dir_get(dir, dir->pos, obj); 2991da177e4SLinus Torvalds if (ret == 0) 3001da177e4SLinus Torvalds dir->pos += 26; 3011da177e4SLinus Torvalds 3021da177e4SLinus Torvalds return ret; 3031da177e4SLinus Torvalds } 3041da177e4SLinus Torvalds 3054287e4deSRussell King static int adfs_f_iterate(struct adfs_dir *dir, struct dir_context *ctx) 3064287e4deSRussell King { 3074287e4deSRussell King struct object_info obj; 3084287e4deSRussell King int pos = 5 + (ctx->pos - 2) * 26; 3094287e4deSRussell King 3104287e4deSRussell King while (ctx->pos < 2 + ADFS_NUM_DIR_ENTRIES) { 3114287e4deSRussell King if (__adfs_dir_get(dir, pos, &obj)) 3124287e4deSRussell King break; 3134287e4deSRussell King if (!dir_emit(ctx, obj.name, obj.name_len, 3144287e4deSRussell King obj.indaddr, DT_UNKNOWN)) 3154287e4deSRussell King break; 3164287e4deSRussell King pos += 26; 3174287e4deSRussell King ctx->pos++; 3184287e4deSRussell King } 3194287e4deSRussell King return 0; 3204287e4deSRussell King } 3214287e4deSRussell King 3221da177e4SLinus Torvalds static int 3231da177e4SLinus Torvalds adfs_f_update(struct adfs_dir *dir, struct object_info *obj) 3241da177e4SLinus Torvalds { 325c3c8149bSRussell King int ret; 3261da177e4SLinus Torvalds 3275ed70bb4SRussell King ret = adfs_dir_find_entry(dir, obj->indaddr); 3281da177e4SLinus Torvalds if (ret < 0) { 3291da177e4SLinus Torvalds adfs_error(dir->sb, "unable to locate entry to update"); 3301da177e4SLinus Torvalds goto out; 3311da177e4SLinus Torvalds } 3321da177e4SLinus Torvalds 3331da177e4SLinus Torvalds __adfs_dir_put(dir, ret, obj); 3341da177e4SLinus Torvalds 3351da177e4SLinus Torvalds /* 3361da177e4SLinus Torvalds * Increment directory sequence number 3371da177e4SLinus Torvalds */ 338016936b3SRussell King dir->dirhead->startmasseq += 1; 339016936b3SRussell King dir->newtail->endmasseq += 1; 3401da177e4SLinus Torvalds 3411da177e4SLinus Torvalds ret = adfs_dir_checkbyte(dir); 3421da177e4SLinus Torvalds /* 3431da177e4SLinus Torvalds * Update directory check byte 3441da177e4SLinus Torvalds */ 345016936b3SRussell King dir->newtail->dircheckbyte = ret; 3461da177e4SLinus Torvalds 3471da177e4SLinus Torvalds #if 1 348016936b3SRussell King if (dir->dirhead->startmasseq != dir->newtail->endmasseq || 349016936b3SRussell King memcmp(&dir->dirhead->startname, &dir->newtail->endname, 4)) 3501da177e4SLinus Torvalds goto bad_dir; 3511da177e4SLinus Torvalds 352016936b3SRussell King if (memcmp(&dir->dirhead->startname, "Nick", 4) && 353016936b3SRussell King memcmp(&dir->dirhead->startname, "Hugo", 4)) 3541da177e4SLinus Torvalds goto bad_dir; 3551da177e4SLinus Torvalds 356016936b3SRussell King if (adfs_dir_checkbyte(dir) != dir->newtail->dircheckbyte) 3571da177e4SLinus Torvalds goto bad_dir; 3581da177e4SLinus Torvalds #endif 3591da177e4SLinus Torvalds ret = 0; 3601da177e4SLinus Torvalds out: 3611da177e4SLinus Torvalds return ret; 3621da177e4SLinus Torvalds #if 1 3631da177e4SLinus Torvalds bad_dir: 3641da177e4SLinus Torvalds adfs_error(dir->sb, "whoops! I broke a directory!"); 3651da177e4SLinus Torvalds return -EIO; 3661da177e4SLinus Torvalds #endif 3671da177e4SLinus Torvalds } 3681da177e4SLinus Torvalds 3690125f504SJulia Lawall const struct adfs_dir_ops adfs_f_dir_ops = { 3701da177e4SLinus Torvalds .read = adfs_f_read, 3714287e4deSRussell King .iterate = adfs_f_iterate, 3721da177e4SLinus Torvalds .setpos = adfs_f_setpos, 3731da177e4SLinus Torvalds .getnext = adfs_f_getnext, 3741da177e4SLinus Torvalds .update = adfs_f_update, 3751da177e4SLinus Torvalds }; 376