1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * linux/fs/adfs/dir_fplus.c 4 * 5 * Copyright (C) 1997-1999 Russell King 6 */ 7 #include "adfs.h" 8 #include "dir_fplus.h" 9 10 /* Return the byte offset to directory entry pos */ 11 static unsigned int adfs_fplus_offset(const struct adfs_bigdirheader *h, 12 unsigned int pos) 13 { 14 return offsetof(struct adfs_bigdirheader, bigdirname) + 15 ALIGN(le32_to_cpu(h->bigdirnamelen), 4) + 16 pos * sizeof(struct adfs_bigdirentry); 17 } 18 19 static int adfs_fplus_validate_header(const struct adfs_bigdirheader *h) 20 { 21 unsigned int size = le32_to_cpu(h->bigdirsize); 22 unsigned int len; 23 24 if (h->bigdirversion[0] != 0 || h->bigdirversion[1] != 0 || 25 h->bigdirversion[2] != 0 || 26 h->bigdirstartname != cpu_to_le32(BIGDIRSTARTNAME) || 27 !size || size & 2047 || size > SZ_4M) 28 return -EIO; 29 30 size -= sizeof(struct adfs_bigdirtail) + 31 offsetof(struct adfs_bigdirheader, bigdirname); 32 33 /* Check that bigdirnamelen fits within the directory */ 34 len = ALIGN(le32_to_cpu(h->bigdirnamelen), 4); 35 if (len > size) 36 return -EIO; 37 38 size -= len; 39 40 /* Check that bigdirnamesize fits within the directory */ 41 len = le32_to_cpu(h->bigdirnamesize); 42 if (len > size) 43 return -EIO; 44 45 size -= len; 46 47 /* 48 * Avoid division, we know that absolute maximum number of entries 49 * can not be so large to cause overflow of the multiplication below. 50 */ 51 len = le32_to_cpu(h->bigdirentries); 52 if (len > SZ_4M / sizeof(struct adfs_bigdirentry) || 53 len * sizeof(struct adfs_bigdirentry) > size) 54 return -EIO; 55 56 return 0; 57 } 58 59 static int adfs_fplus_validate_tail(const struct adfs_bigdirheader *h, 60 const struct adfs_bigdirtail *t) 61 { 62 if (t->bigdirendname != cpu_to_le32(BIGDIRENDNAME) || 63 t->bigdirendmasseq != h->startmasseq || 64 t->reserved[0] != 0 || t->reserved[1] != 0) 65 return -EIO; 66 67 return 0; 68 } 69 70 static int adfs_fplus_read(struct super_block *sb, u32 indaddr, 71 unsigned int size, struct adfs_dir *dir) 72 { 73 struct adfs_bigdirheader *h; 74 struct adfs_bigdirtail *t; 75 unsigned int dirsize; 76 int ret; 77 78 /* Read first buffer */ 79 ret = adfs_dir_read_buffers(sb, indaddr, sb->s_blocksize, dir); 80 if (ret) 81 return ret; 82 83 dir->bighead = h = (void *)dir->bhs[0]->b_data; 84 if (adfs_fplus_validate_header(h)) { 85 adfs_error(sb, "dir %06x has malformed header", indaddr); 86 goto out; 87 } 88 89 dirsize = le32_to_cpu(h->bigdirsize); 90 if (dirsize != size) { 91 adfs_msg(sb, KERN_WARNING, 92 "dir %06x header size %X does not match directory size %X", 93 indaddr, dirsize, size); 94 } 95 96 /* Read remaining buffers */ 97 ret = adfs_dir_read_buffers(sb, indaddr, dirsize, dir); 98 if (ret) 99 return ret; 100 101 dir->bigtail = t = (struct adfs_bigdirtail *) 102 (dir->bhs[dir->nr_buffers - 1]->b_data + (sb->s_blocksize - 8)); 103 104 ret = adfs_fplus_validate_tail(h, t); 105 if (ret) { 106 adfs_error(sb, "dir %06x has malformed tail", indaddr); 107 goto out; 108 } 109 110 dir->parent_id = le32_to_cpu(h->bigdirparent); 111 return 0; 112 113 out: 114 adfs_dir_relse(dir); 115 116 return ret; 117 } 118 119 static int 120 adfs_fplus_setpos(struct adfs_dir *dir, unsigned int fpos) 121 { 122 int ret = -ENOENT; 123 124 if (fpos <= le32_to_cpu(dir->bighead->bigdirentries)) { 125 dir->pos = fpos; 126 ret = 0; 127 } 128 129 return ret; 130 } 131 132 static int 133 adfs_fplus_getnext(struct adfs_dir *dir, struct object_info *obj) 134 { 135 struct adfs_bigdirheader *h = dir->bighead; 136 struct adfs_bigdirentry bde; 137 unsigned int offset; 138 int ret; 139 140 if (dir->pos >= le32_to_cpu(h->bigdirentries)) 141 return -ENOENT; 142 143 offset = adfs_fplus_offset(h, dir->pos); 144 145 ret = adfs_dir_copyfrom(&bde, dir, offset, 146 sizeof(struct adfs_bigdirentry)); 147 if (ret) 148 return ret; 149 150 obj->loadaddr = le32_to_cpu(bde.bigdirload); 151 obj->execaddr = le32_to_cpu(bde.bigdirexec); 152 obj->size = le32_to_cpu(bde.bigdirlen); 153 obj->indaddr = le32_to_cpu(bde.bigdirindaddr); 154 obj->attr = le32_to_cpu(bde.bigdirattr); 155 obj->name_len = le32_to_cpu(bde.bigdirobnamelen); 156 157 offset = adfs_fplus_offset(h, le32_to_cpu(h->bigdirentries)); 158 offset += le32_to_cpu(bde.bigdirobnameptr); 159 160 ret = adfs_dir_copyfrom(obj->name, dir, offset, obj->name_len); 161 if (ret) 162 return ret; 163 164 adfs_object_fixup(dir, obj); 165 166 dir->pos += 1; 167 168 return 0; 169 } 170 171 static int adfs_fplus_iterate(struct adfs_dir *dir, struct dir_context *ctx) 172 { 173 struct object_info obj; 174 175 if ((ctx->pos - 2) >> 32) 176 return 0; 177 178 if (adfs_fplus_setpos(dir, ctx->pos - 2)) 179 return 0; 180 181 while (!adfs_fplus_getnext(dir, &obj)) { 182 if (!dir_emit(ctx, obj.name, obj.name_len, 183 obj.indaddr, DT_UNKNOWN)) 184 break; 185 ctx->pos++; 186 } 187 188 return 0; 189 } 190 191 const struct adfs_dir_ops adfs_fplus_dir_ops = { 192 .read = adfs_fplus_read, 193 .iterate = adfs_fplus_iterate, 194 .setpos = adfs_fplus_setpos, 195 .getnext = adfs_fplus_getnext, 196 }; 197