1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * linux/fs/adfs/dir_f.c 4 * 5 * Copyright (C) 1997-1999 Russell King 6 * 7 * E and F format directory handling 8 */ 9 #include "adfs.h" 10 #include "dir_f.h" 11 12 /* 13 * Read an (unaligned) value of length 1..4 bytes 14 */ 15 static 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 31 static 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 in 66 * assembler, but a bitch in C... This is one 67 * of them. 68 */ 69 static u8 70 adfs_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 whole 81 * word of the last directory entry. This 82 * 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. These 95 * 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 bh 108 * Note that contary to the RISC OS PRMs, 109 * the first few bytes are NOT included 110 * in the check. All bytes are in the 111 * 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 124 static 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 */ 141 static 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 164 bad_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 entry 173 */ 174 static inline void 175 adfs_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 entry 199 */ 200 static inline void 201 adfs_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 responsible 212 * for holding the relevant locks. 213 */ 214 static int 215 __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 232 static int 233 adfs_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 242 static int 243 adfs_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 254 static 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 271 static 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 298 static 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 317 const 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
Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.