162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * linux/fs/adfs/dir_f.c 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 1997-1999 Russell King 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * E and F format directory handling 862306a36Sopenharmony_ci */ 962306a36Sopenharmony_ci#include "adfs.h" 1062306a36Sopenharmony_ci#include "dir_f.h" 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci/* 1362306a36Sopenharmony_ci * Read an (unaligned) value of length 1..4 bytes 1462306a36Sopenharmony_ci */ 1562306a36Sopenharmony_cistatic inline unsigned int adfs_readval(unsigned char *p, int len) 1662306a36Sopenharmony_ci{ 1762306a36Sopenharmony_ci unsigned int val = 0; 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_ci switch (len) { 2062306a36Sopenharmony_ci case 4: val |= p[3] << 24; 2162306a36Sopenharmony_ci fallthrough; 2262306a36Sopenharmony_ci case 3: val |= p[2] << 16; 2362306a36Sopenharmony_ci fallthrough; 2462306a36Sopenharmony_ci case 2: val |= p[1] << 8; 2562306a36Sopenharmony_ci fallthrough; 2662306a36Sopenharmony_ci default: val |= p[0]; 2762306a36Sopenharmony_ci } 2862306a36Sopenharmony_ci return val; 2962306a36Sopenharmony_ci} 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_cistatic inline void adfs_writeval(unsigned char *p, int len, unsigned int val) 3262306a36Sopenharmony_ci{ 3362306a36Sopenharmony_ci switch (len) { 3462306a36Sopenharmony_ci case 4: p[3] = val >> 24; 3562306a36Sopenharmony_ci fallthrough; 3662306a36Sopenharmony_ci case 3: p[2] = val >> 16; 3762306a36Sopenharmony_ci fallthrough; 3862306a36Sopenharmony_ci case 2: p[1] = val >> 8; 3962306a36Sopenharmony_ci fallthrough; 4062306a36Sopenharmony_ci default: p[0] = val; 4162306a36Sopenharmony_ci } 4262306a36Sopenharmony_ci} 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_ci#define ror13(v) ((v >> 13) | (v << 19)) 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_ci#define dir_u8(idx) \ 4762306a36Sopenharmony_ci ({ int _buf = idx >> blocksize_bits; \ 4862306a36Sopenharmony_ci int _off = idx - (_buf << blocksize_bits);\ 4962306a36Sopenharmony_ci *(u8 *)(bh[_buf]->b_data + _off); \ 5062306a36Sopenharmony_ci }) 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_ci#define dir_u32(idx) \ 5362306a36Sopenharmony_ci ({ int _buf = idx >> blocksize_bits; \ 5462306a36Sopenharmony_ci int _off = idx - (_buf << blocksize_bits);\ 5562306a36Sopenharmony_ci *(__le32 *)(bh[_buf]->b_data + _off); \ 5662306a36Sopenharmony_ci }) 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci#define bufoff(_bh,_idx) \ 5962306a36Sopenharmony_ci ({ int _buf = _idx >> blocksize_bits; \ 6062306a36Sopenharmony_ci int _off = _idx - (_buf << blocksize_bits);\ 6162306a36Sopenharmony_ci (void *)(_bh[_buf]->b_data + _off); \ 6262306a36Sopenharmony_ci }) 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci/* 6562306a36Sopenharmony_ci * There are some algorithms that are nice in 6662306a36Sopenharmony_ci * assembler, but a bitch in C... This is one 6762306a36Sopenharmony_ci * of them. 6862306a36Sopenharmony_ci */ 6962306a36Sopenharmony_cistatic u8 7062306a36Sopenharmony_ciadfs_dir_checkbyte(const struct adfs_dir *dir) 7162306a36Sopenharmony_ci{ 7262306a36Sopenharmony_ci struct buffer_head * const *bh = dir->bh; 7362306a36Sopenharmony_ci const int blocksize_bits = dir->sb->s_blocksize_bits; 7462306a36Sopenharmony_ci union { __le32 *ptr32; u8 *ptr8; } ptr, end; 7562306a36Sopenharmony_ci u32 dircheck = 0; 7662306a36Sopenharmony_ci int last = 5 - 26; 7762306a36Sopenharmony_ci int i = 0; 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci /* 8062306a36Sopenharmony_ci * Accumulate each word up to the last whole 8162306a36Sopenharmony_ci * word of the last directory entry. This 8262306a36Sopenharmony_ci * can spread across several buffer heads. 8362306a36Sopenharmony_ci */ 8462306a36Sopenharmony_ci do { 8562306a36Sopenharmony_ci last += 26; 8662306a36Sopenharmony_ci do { 8762306a36Sopenharmony_ci dircheck = le32_to_cpu(dir_u32(i)) ^ ror13(dircheck); 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci i += sizeof(u32); 9062306a36Sopenharmony_ci } while (i < (last & ~3)); 9162306a36Sopenharmony_ci } while (dir_u8(last) != 0); 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci /* 9462306a36Sopenharmony_ci * Accumulate the last few bytes. These 9562306a36Sopenharmony_ci * bytes will be within the same bh. 9662306a36Sopenharmony_ci */ 9762306a36Sopenharmony_ci if (i != last) { 9862306a36Sopenharmony_ci ptr.ptr8 = bufoff(bh, i); 9962306a36Sopenharmony_ci end.ptr8 = ptr.ptr8 + last - i; 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci do { 10262306a36Sopenharmony_ci dircheck = *ptr.ptr8++ ^ ror13(dircheck); 10362306a36Sopenharmony_ci } while (ptr.ptr8 < end.ptr8); 10462306a36Sopenharmony_ci } 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci /* 10762306a36Sopenharmony_ci * The directory tail is in the final bh 10862306a36Sopenharmony_ci * Note that contary to the RISC OS PRMs, 10962306a36Sopenharmony_ci * the first few bytes are NOT included 11062306a36Sopenharmony_ci * in the check. All bytes are in the 11162306a36Sopenharmony_ci * same bh. 11262306a36Sopenharmony_ci */ 11362306a36Sopenharmony_ci ptr.ptr8 = bufoff(bh, 2008); 11462306a36Sopenharmony_ci end.ptr8 = ptr.ptr8 + 36; 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci do { 11762306a36Sopenharmony_ci __le32 v = *ptr.ptr32++; 11862306a36Sopenharmony_ci dircheck = le32_to_cpu(v) ^ ror13(dircheck); 11962306a36Sopenharmony_ci } while (ptr.ptr32 < end.ptr32); 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci return (dircheck ^ (dircheck >> 8) ^ (dircheck >> 16) ^ (dircheck >> 24)) & 0xff; 12262306a36Sopenharmony_ci} 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_cistatic int adfs_f_validate(struct adfs_dir *dir) 12562306a36Sopenharmony_ci{ 12662306a36Sopenharmony_ci struct adfs_dirheader *head = dir->dirhead; 12762306a36Sopenharmony_ci struct adfs_newdirtail *tail = dir->newtail; 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci if (head->startmasseq != tail->endmasseq || 13062306a36Sopenharmony_ci tail->dirlastmask || tail->reserved[0] || tail->reserved[1] || 13162306a36Sopenharmony_ci (memcmp(&head->startname, "Nick", 4) && 13262306a36Sopenharmony_ci memcmp(&head->startname, "Hugo", 4)) || 13362306a36Sopenharmony_ci memcmp(&head->startname, &tail->endname, 4) || 13462306a36Sopenharmony_ci adfs_dir_checkbyte(dir) != tail->dircheckbyte) 13562306a36Sopenharmony_ci return -EIO; 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci return 0; 13862306a36Sopenharmony_ci} 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci/* Read and check that a directory is valid */ 14162306a36Sopenharmony_cistatic int adfs_f_read(struct super_block *sb, u32 indaddr, unsigned int size, 14262306a36Sopenharmony_ci struct adfs_dir *dir) 14362306a36Sopenharmony_ci{ 14462306a36Sopenharmony_ci const unsigned int blocksize_bits = sb->s_blocksize_bits; 14562306a36Sopenharmony_ci int ret; 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_ci if (size && size != ADFS_NEWDIR_SIZE) 14862306a36Sopenharmony_ci return -EIO; 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_ci ret = adfs_dir_read_buffers(sb, indaddr, ADFS_NEWDIR_SIZE, dir); 15162306a36Sopenharmony_ci if (ret) 15262306a36Sopenharmony_ci return ret; 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ci dir->dirhead = bufoff(dir->bh, 0); 15562306a36Sopenharmony_ci dir->newtail = bufoff(dir->bh, 2007); 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci if (adfs_f_validate(dir)) 15862306a36Sopenharmony_ci goto bad_dir; 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ci dir->parent_id = adfs_readval(dir->newtail->dirparent, 3); 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci return 0; 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_cibad_dir: 16562306a36Sopenharmony_ci adfs_error(sb, "dir %06x is corrupted", indaddr); 16662306a36Sopenharmony_ci adfs_dir_relse(dir); 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ci return -EIO; 16962306a36Sopenharmony_ci} 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_ci/* 17262306a36Sopenharmony_ci * convert a disk-based directory entry to a Linux ADFS directory entry 17362306a36Sopenharmony_ci */ 17462306a36Sopenharmony_cistatic inline void 17562306a36Sopenharmony_ciadfs_dir2obj(struct adfs_dir *dir, struct object_info *obj, 17662306a36Sopenharmony_ci struct adfs_direntry *de) 17762306a36Sopenharmony_ci{ 17862306a36Sopenharmony_ci unsigned int name_len; 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci for (name_len = 0; name_len < ADFS_F_NAME_LEN; name_len++) { 18162306a36Sopenharmony_ci if (de->dirobname[name_len] < ' ') 18262306a36Sopenharmony_ci break; 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_ci obj->name[name_len] = de->dirobname[name_len]; 18562306a36Sopenharmony_ci } 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_ci obj->name_len = name_len; 18862306a36Sopenharmony_ci obj->indaddr = adfs_readval(de->dirinddiscadd, 3); 18962306a36Sopenharmony_ci obj->loadaddr = adfs_readval(de->dirload, 4); 19062306a36Sopenharmony_ci obj->execaddr = adfs_readval(de->direxec, 4); 19162306a36Sopenharmony_ci obj->size = adfs_readval(de->dirlen, 4); 19262306a36Sopenharmony_ci obj->attr = de->newdiratts; 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_ci adfs_object_fixup(dir, obj); 19562306a36Sopenharmony_ci} 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_ci/* 19862306a36Sopenharmony_ci * convert a Linux ADFS directory entry to a disk-based directory entry 19962306a36Sopenharmony_ci */ 20062306a36Sopenharmony_cistatic inline void 20162306a36Sopenharmony_ciadfs_obj2dir(struct adfs_direntry *de, struct object_info *obj) 20262306a36Sopenharmony_ci{ 20362306a36Sopenharmony_ci adfs_writeval(de->dirinddiscadd, 3, obj->indaddr); 20462306a36Sopenharmony_ci adfs_writeval(de->dirload, 4, obj->loadaddr); 20562306a36Sopenharmony_ci adfs_writeval(de->direxec, 4, obj->execaddr); 20662306a36Sopenharmony_ci adfs_writeval(de->dirlen, 4, obj->size); 20762306a36Sopenharmony_ci de->newdiratts = obj->attr; 20862306a36Sopenharmony_ci} 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_ci/* 21162306a36Sopenharmony_ci * get a directory entry. Note that the caller is responsible 21262306a36Sopenharmony_ci * for holding the relevant locks. 21362306a36Sopenharmony_ci */ 21462306a36Sopenharmony_cistatic int 21562306a36Sopenharmony_ci__adfs_dir_get(struct adfs_dir *dir, int pos, struct object_info *obj) 21662306a36Sopenharmony_ci{ 21762306a36Sopenharmony_ci struct adfs_direntry de; 21862306a36Sopenharmony_ci int ret; 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_ci ret = adfs_dir_copyfrom(&de, dir, pos, 26); 22162306a36Sopenharmony_ci if (ret) 22262306a36Sopenharmony_ci return ret; 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_ci if (!de.dirobname[0]) 22562306a36Sopenharmony_ci return -ENOENT; 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_ci adfs_dir2obj(dir, obj, &de); 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_ci return 0; 23062306a36Sopenharmony_ci} 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_cistatic int 23362306a36Sopenharmony_ciadfs_f_setpos(struct adfs_dir *dir, unsigned int fpos) 23462306a36Sopenharmony_ci{ 23562306a36Sopenharmony_ci if (fpos >= ADFS_NUM_DIR_ENTRIES) 23662306a36Sopenharmony_ci return -ENOENT; 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_ci dir->pos = 5 + fpos * 26; 23962306a36Sopenharmony_ci return 0; 24062306a36Sopenharmony_ci} 24162306a36Sopenharmony_ci 24262306a36Sopenharmony_cistatic int 24362306a36Sopenharmony_ciadfs_f_getnext(struct adfs_dir *dir, struct object_info *obj) 24462306a36Sopenharmony_ci{ 24562306a36Sopenharmony_ci unsigned int ret; 24662306a36Sopenharmony_ci 24762306a36Sopenharmony_ci ret = __adfs_dir_get(dir, dir->pos, obj); 24862306a36Sopenharmony_ci if (ret == 0) 24962306a36Sopenharmony_ci dir->pos += 26; 25062306a36Sopenharmony_ci 25162306a36Sopenharmony_ci return ret; 25262306a36Sopenharmony_ci} 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_cistatic int adfs_f_iterate(struct adfs_dir *dir, struct dir_context *ctx) 25562306a36Sopenharmony_ci{ 25662306a36Sopenharmony_ci struct object_info obj; 25762306a36Sopenharmony_ci int pos = 5 + (ctx->pos - 2) * 26; 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_ci while (ctx->pos < 2 + ADFS_NUM_DIR_ENTRIES) { 26062306a36Sopenharmony_ci if (__adfs_dir_get(dir, pos, &obj)) 26162306a36Sopenharmony_ci break; 26262306a36Sopenharmony_ci if (!dir_emit(ctx, obj.name, obj.name_len, 26362306a36Sopenharmony_ci obj.indaddr, DT_UNKNOWN)) 26462306a36Sopenharmony_ci break; 26562306a36Sopenharmony_ci pos += 26; 26662306a36Sopenharmony_ci ctx->pos++; 26762306a36Sopenharmony_ci } 26862306a36Sopenharmony_ci return 0; 26962306a36Sopenharmony_ci} 27062306a36Sopenharmony_ci 27162306a36Sopenharmony_cistatic int adfs_f_update(struct adfs_dir *dir, struct object_info *obj) 27262306a36Sopenharmony_ci{ 27362306a36Sopenharmony_ci struct adfs_direntry de; 27462306a36Sopenharmony_ci int offset, ret; 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_ci offset = 5 - (int)sizeof(de); 27762306a36Sopenharmony_ci 27862306a36Sopenharmony_ci do { 27962306a36Sopenharmony_ci offset += sizeof(de); 28062306a36Sopenharmony_ci ret = adfs_dir_copyfrom(&de, dir, offset, sizeof(de)); 28162306a36Sopenharmony_ci if (ret) { 28262306a36Sopenharmony_ci adfs_error(dir->sb, "error reading directory entry"); 28362306a36Sopenharmony_ci return -ENOENT; 28462306a36Sopenharmony_ci } 28562306a36Sopenharmony_ci if (!de.dirobname[0]) { 28662306a36Sopenharmony_ci adfs_error(dir->sb, "unable to locate entry to update"); 28762306a36Sopenharmony_ci return -ENOENT; 28862306a36Sopenharmony_ci } 28962306a36Sopenharmony_ci } while (adfs_readval(de.dirinddiscadd, 3) != obj->indaddr); 29062306a36Sopenharmony_ci 29162306a36Sopenharmony_ci /* Update the directory entry with the new object state */ 29262306a36Sopenharmony_ci adfs_obj2dir(&de, obj); 29362306a36Sopenharmony_ci 29462306a36Sopenharmony_ci /* Write the directory entry back to the directory */ 29562306a36Sopenharmony_ci return adfs_dir_copyto(dir, offset, &de, 26); 29662306a36Sopenharmony_ci} 29762306a36Sopenharmony_ci 29862306a36Sopenharmony_cistatic int adfs_f_commit(struct adfs_dir *dir) 29962306a36Sopenharmony_ci{ 30062306a36Sopenharmony_ci int ret; 30162306a36Sopenharmony_ci 30262306a36Sopenharmony_ci /* Increment directory sequence number */ 30362306a36Sopenharmony_ci dir->dirhead->startmasseq += 1; 30462306a36Sopenharmony_ci dir->newtail->endmasseq += 1; 30562306a36Sopenharmony_ci 30662306a36Sopenharmony_ci /* Update directory check byte */ 30762306a36Sopenharmony_ci dir->newtail->dircheckbyte = adfs_dir_checkbyte(dir); 30862306a36Sopenharmony_ci 30962306a36Sopenharmony_ci /* Make sure the directory still validates correctly */ 31062306a36Sopenharmony_ci ret = adfs_f_validate(dir); 31162306a36Sopenharmony_ci if (ret) 31262306a36Sopenharmony_ci adfs_msg(dir->sb, KERN_ERR, "error: update broke directory"); 31362306a36Sopenharmony_ci 31462306a36Sopenharmony_ci return ret; 31562306a36Sopenharmony_ci} 31662306a36Sopenharmony_ci 31762306a36Sopenharmony_ciconst struct adfs_dir_ops adfs_f_dir_ops = { 31862306a36Sopenharmony_ci .read = adfs_f_read, 31962306a36Sopenharmony_ci .iterate = adfs_f_iterate, 32062306a36Sopenharmony_ci .setpos = adfs_f_setpos, 32162306a36Sopenharmony_ci .getnext = adfs_f_getnext, 32262306a36Sopenharmony_ci .update = adfs_f_update, 32362306a36Sopenharmony_ci .commit = adfs_f_commit, 32462306a36Sopenharmony_ci}; 325