325 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * linux/fs/adfs/dir_f.c4 *5 * Copyright (C) 1997-1999 Russell King6 *7 * E and F format directory handling8 */9#include "adfs.h"10#include "dir_f.h"11 12/*13 * Read an (unaligned) value of length 1..4 bytes14 */15static inline unsigned int adfs_readval(unsigned char *p, int len)16{17 unsigned int val = 0;18 19 switch (len) {20 case 4: val |= p[3] << 24;21 fallthrough;22 case 3: val |= p[2] << 16;23 fallthrough;24 case 2: val |= p[1] << 8;25 fallthrough;26 default: val |= p[0];27 }28 return val;29}30 31static inline void adfs_writeval(unsigned char *p, int len, unsigned int val)32{33 switch (len) {34 case 4: p[3] = val >> 24;35 fallthrough;36 case 3: p[2] = val >> 16;37 fallthrough;38 case 2: p[1] = val >> 8;39 fallthrough;40 default: p[0] = val;41 }42}43 44#define ror13(v) ((v >> 13) | (v << 19))45 46#define dir_u8(idx) \47 ({ int _buf = idx >> blocksize_bits; \48 int _off = idx - (_buf << blocksize_bits);\49 *(u8 *)(bh[_buf]->b_data + _off); \50 })51 52#define dir_u32(idx) \53 ({ int _buf = idx >> blocksize_bits; \54 int _off = idx - (_buf << blocksize_bits);\55 *(__le32 *)(bh[_buf]->b_data + _off); \56 })57 58#define bufoff(_bh,_idx) \59 ({ int _buf = _idx >> blocksize_bits; \60 int _off = _idx - (_buf << blocksize_bits);\61 (void *)(_bh[_buf]->b_data + _off); \62 })63 64/*65 * There are some algorithms that are nice in66 * assembler, but a bitch in C... This is one67 * of them.68 */69static u870adfs_dir_checkbyte(const struct adfs_dir *dir)71{72 struct buffer_head * const *bh = dir->bh;73 const int blocksize_bits = dir->sb->s_blocksize_bits;74 union { __le32 *ptr32; u8 *ptr8; } ptr, end;75 u32 dircheck = 0;76 int last = 5 - 26;77 int i = 0;78 79 /*80 * Accumulate each word up to the last whole81 * word of the last directory entry. This82 * can spread across several buffer heads.83 */84 do {85 last += 26;86 do {87 dircheck = le32_to_cpu(dir_u32(i)) ^ ror13(dircheck);88 89 i += sizeof(u32);90 } while (i < (last & ~3));91 } while (dir_u8(last) != 0);92 93 /*94 * Accumulate the last few bytes. These95 * bytes will be within the same bh.96 */97 if (i != last) {98 ptr.ptr8 = bufoff(bh, i);99 end.ptr8 = ptr.ptr8 + last - i;100 101 do {102 dircheck = *ptr.ptr8++ ^ ror13(dircheck);103 } while (ptr.ptr8 < end.ptr8);104 }105 106 /*107 * The directory tail is in the final bh108 * Note that contary to the RISC OS PRMs,109 * the first few bytes are NOT included110 * in the check. All bytes are in the111 * same bh.112 */113 ptr.ptr8 = bufoff(bh, 2008);114 end.ptr8 = ptr.ptr8 + 36;115 116 do {117 __le32 v = *ptr.ptr32++;118 dircheck = le32_to_cpu(v) ^ ror13(dircheck);119 } while (ptr.ptr32 < end.ptr32);120 121 return (dircheck ^ (dircheck >> 8) ^ (dircheck >> 16) ^ (dircheck >> 24)) & 0xff;122}123 124static int adfs_f_validate(struct adfs_dir *dir)125{126 struct adfs_dirheader *head = dir->dirhead;127 struct adfs_newdirtail *tail = dir->newtail;128 129 if (head->startmasseq != tail->endmasseq ||130 tail->dirlastmask || tail->reserved[0] || tail->reserved[1] ||131 (memcmp(&head->startname, "Nick", 4) &&132 memcmp(&head->startname, "Hugo", 4)) ||133 memcmp(&head->startname, &tail->endname, 4) ||134 adfs_dir_checkbyte(dir) != tail->dircheckbyte)135 return -EIO;136 137 return 0;138}139 140/* Read and check that a directory is valid */141static int adfs_f_read(struct super_block *sb, u32 indaddr, unsigned int size,142 struct adfs_dir *dir)143{144 const unsigned int blocksize_bits = sb->s_blocksize_bits;145 int ret;146 147 if (size && size != ADFS_NEWDIR_SIZE)148 return -EIO;149 150 ret = adfs_dir_read_buffers(sb, indaddr, ADFS_NEWDIR_SIZE, dir);151 if (ret)152 return ret;153 154 dir->dirhead = bufoff(dir->bh, 0);155 dir->newtail = bufoff(dir->bh, 2007);156 157 if (adfs_f_validate(dir))158 goto bad_dir;159 160 dir->parent_id = adfs_readval(dir->newtail->dirparent, 3);161 162 return 0;163 164bad_dir:165 adfs_error(sb, "dir %06x is corrupted", indaddr);166 adfs_dir_relse(dir);167 168 return -EIO;169}170 171/*172 * convert a disk-based directory entry to a Linux ADFS directory entry173 */174static inline void175adfs_dir2obj(struct adfs_dir *dir, struct object_info *obj,176 struct adfs_direntry *de)177{178 unsigned int name_len;179 180 for (name_len = 0; name_len < ADFS_F_NAME_LEN; name_len++) {181 if (de->dirobname[name_len] < ' ')182 break;183 184 obj->name[name_len] = de->dirobname[name_len];185 }186 187 obj->name_len = name_len;188 obj->indaddr = adfs_readval(de->dirinddiscadd, 3);189 obj->loadaddr = adfs_readval(de->dirload, 4);190 obj->execaddr = adfs_readval(de->direxec, 4);191 obj->size = adfs_readval(de->dirlen, 4);192 obj->attr = de->newdiratts;193 194 adfs_object_fixup(dir, obj);195}196 197/*198 * convert a Linux ADFS directory entry to a disk-based directory entry199 */200static inline void201adfs_obj2dir(struct adfs_direntry *de, struct object_info *obj)202{203 adfs_writeval(de->dirinddiscadd, 3, obj->indaddr);204 adfs_writeval(de->dirload, 4, obj->loadaddr);205 adfs_writeval(de->direxec, 4, obj->execaddr);206 adfs_writeval(de->dirlen, 4, obj->size);207 de->newdiratts = obj->attr;208}209 210/*211 * get a directory entry. Note that the caller is responsible212 * for holding the relevant locks.213 */214static int215__adfs_dir_get(struct adfs_dir *dir, int pos, struct object_info *obj)216{217 struct adfs_direntry de;218 int ret;219 220 ret = adfs_dir_copyfrom(&de, dir, pos, 26);221 if (ret)222 return ret;223 224 if (!de.dirobname[0])225 return -ENOENT;226 227 adfs_dir2obj(dir, obj, &de);228 229 return 0;230}231 232static int233adfs_f_setpos(struct adfs_dir *dir, unsigned int fpos)234{235 if (fpos >= ADFS_NUM_DIR_ENTRIES)236 return -ENOENT;237 238 dir->pos = 5 + fpos * 26;239 return 0;240}241 242static int243adfs_f_getnext(struct adfs_dir *dir, struct object_info *obj)244{245 unsigned int ret;246 247 ret = __adfs_dir_get(dir, dir->pos, obj);248 if (ret == 0)249 dir->pos += 26;250 251 return ret;252}253 254static int adfs_f_iterate(struct adfs_dir *dir, struct dir_context *ctx)255{256 struct object_info obj;257 int pos = 5 + (ctx->pos - 2) * 26;258 259 while (ctx->pos < 2 + ADFS_NUM_DIR_ENTRIES) {260 if (__adfs_dir_get(dir, pos, &obj))261 break;262 if (!dir_emit(ctx, obj.name, obj.name_len,263 obj.indaddr, DT_UNKNOWN))264 break;265 pos += 26;266 ctx->pos++;267 }268 return 0;269}270 271static int adfs_f_update(struct adfs_dir *dir, struct object_info *obj)272{273 struct adfs_direntry de;274 int offset, ret;275 276 offset = 5 - (int)sizeof(de);277 278 do {279 offset += sizeof(de);280 ret = adfs_dir_copyfrom(&de, dir, offset, sizeof(de));281 if (ret) {282 adfs_error(dir->sb, "error reading directory entry");283 return -ENOENT;284 }285 if (!de.dirobname[0]) {286 adfs_error(dir->sb, "unable to locate entry to update");287 return -ENOENT;288 }289 } while (adfs_readval(de.dirinddiscadd, 3) != obj->indaddr);290 291 /* Update the directory entry with the new object state */292 adfs_obj2dir(&de, obj);293 294 /* Write the directory entry back to the directory */295 return adfs_dir_copyto(dir, offset, &de, 26);296}297 298static int adfs_f_commit(struct adfs_dir *dir)299{300 int ret;301 302 /* Increment directory sequence number */303 dir->dirhead->startmasseq += 1;304 dir->newtail->endmasseq += 1;305 306 /* Update directory check byte */307 dir->newtail->dircheckbyte = adfs_dir_checkbyte(dir);308 309 /* Make sure the directory still validates correctly */310 ret = adfs_f_validate(dir);311 if (ret)312 adfs_msg(dir->sb, KERN_ERR, "error: update broke directory");313 314 return ret;315}316 317const struct adfs_dir_ops adfs_f_dir_ops = {318 .read = adfs_f_read,319 .iterate = adfs_f_iterate,320 .setpos = adfs_f_setpos,321 .getnext = adfs_f_getnext,322 .update = adfs_f_update,323 .commit = adfs_f_commit,324};325