dir_fplus.c 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  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. /* Return the byte offset to directory entry pos */
  10. static unsigned int adfs_fplus_offset(const struct adfs_bigdirheader *h,
  11. unsigned int pos)
  12. {
  13. return offsetof(struct adfs_bigdirheader, bigdirname) +
  14. ALIGN(le32_to_cpu(h->bigdirnamelen), 4) +
  15. pos * sizeof(struct adfs_bigdirentry);
  16. }
  17. static int adfs_fplus_validate_header(const struct adfs_bigdirheader *h)
  18. {
  19. unsigned int size = le32_to_cpu(h->bigdirsize);
  20. unsigned int len;
  21. if (h->bigdirversion[0] != 0 || h->bigdirversion[1] != 0 ||
  22. h->bigdirversion[2] != 0 ||
  23. h->bigdirstartname != cpu_to_le32(BIGDIRSTARTNAME) ||
  24. !size || size & 2047 || size > SZ_4M)
  25. return -EIO;
  26. size -= sizeof(struct adfs_bigdirtail) +
  27. offsetof(struct adfs_bigdirheader, bigdirname);
  28. /* Check that bigdirnamelen fits within the directory */
  29. len = ALIGN(le32_to_cpu(h->bigdirnamelen), 4);
  30. if (len > size)
  31. return -EIO;
  32. size -= len;
  33. /* Check that bigdirnamesize fits within the directory */
  34. len = le32_to_cpu(h->bigdirnamesize);
  35. if (len > size)
  36. return -EIO;
  37. size -= len;
  38. /*
  39. * Avoid division, we know that absolute maximum number of entries
  40. * can not be so large to cause overflow of the multiplication below.
  41. */
  42. len = le32_to_cpu(h->bigdirentries);
  43. if (len > SZ_4M / sizeof(struct adfs_bigdirentry) ||
  44. len * sizeof(struct adfs_bigdirentry) > size)
  45. return -EIO;
  46. return 0;
  47. }
  48. static int adfs_fplus_validate_tail(const struct adfs_bigdirheader *h,
  49. const struct adfs_bigdirtail *t)
  50. {
  51. if (t->bigdirendname != cpu_to_le32(BIGDIRENDNAME) ||
  52. t->bigdirendmasseq != h->startmasseq ||
  53. t->reserved[0] != 0 || t->reserved[1] != 0)
  54. return -EIO;
  55. return 0;
  56. }
  57. static u8 adfs_fplus_checkbyte(struct adfs_dir *dir)
  58. {
  59. struct adfs_bigdirheader *h = dir->bighead;
  60. struct adfs_bigdirtail *t = dir->bigtail;
  61. unsigned int end, bs, bi, i;
  62. __le32 *bp;
  63. u32 dircheck;
  64. end = adfs_fplus_offset(h, le32_to_cpu(h->bigdirentries)) +
  65. le32_to_cpu(h->bigdirnamesize);
  66. /* Accumulate the contents of the header, entries and names */
  67. for (dircheck = 0, bi = 0; end; bi++) {
  68. bp = (void *)dir->bhs[bi]->b_data;
  69. bs = dir->bhs[bi]->b_size;
  70. if (bs > end)
  71. bs = end;
  72. for (i = 0; i < bs; i += sizeof(u32))
  73. dircheck = ror32(dircheck, 13) ^ le32_to_cpup(bp++);
  74. end -= bs;
  75. }
  76. /* Accumulate the contents of the tail except for the check byte */
  77. dircheck = ror32(dircheck, 13) ^ le32_to_cpu(t->bigdirendname);
  78. dircheck = ror32(dircheck, 13) ^ t->bigdirendmasseq;
  79. dircheck = ror32(dircheck, 13) ^ t->reserved[0];
  80. dircheck = ror32(dircheck, 13) ^ t->reserved[1];
  81. return dircheck ^ dircheck >> 8 ^ dircheck >> 16 ^ dircheck >> 24;
  82. }
  83. static int adfs_fplus_read(struct super_block *sb, u32 indaddr,
  84. unsigned int size, struct adfs_dir *dir)
  85. {
  86. struct adfs_bigdirheader *h;
  87. struct adfs_bigdirtail *t;
  88. unsigned int dirsize;
  89. int ret;
  90. /* Read first buffer */
  91. ret = adfs_dir_read_buffers(sb, indaddr, sb->s_blocksize, dir);
  92. if (ret)
  93. return ret;
  94. dir->bighead = h = (void *)dir->bhs[0]->b_data;
  95. ret = adfs_fplus_validate_header(h);
  96. if (ret) {
  97. adfs_error(sb, "dir %06x has malformed header", indaddr);
  98. goto out;
  99. }
  100. dirsize = le32_to_cpu(h->bigdirsize);
  101. if (size && dirsize != size) {
  102. adfs_msg(sb, KERN_WARNING,
  103. "dir %06x header size %X does not match directory size %X",
  104. indaddr, dirsize, size);
  105. }
  106. /* Read remaining buffers */
  107. ret = adfs_dir_read_buffers(sb, indaddr, dirsize, dir);
  108. if (ret)
  109. return ret;
  110. dir->bigtail = t = (struct adfs_bigdirtail *)
  111. (dir->bhs[dir->nr_buffers - 1]->b_data + (sb->s_blocksize - 8));
  112. ret = adfs_fplus_validate_tail(h, t);
  113. if (ret) {
  114. adfs_error(sb, "dir %06x has malformed tail", indaddr);
  115. goto out;
  116. }
  117. if (adfs_fplus_checkbyte(dir) != t->bigdircheckbyte) {
  118. adfs_error(sb, "dir %06x checkbyte mismatch\n", indaddr);
  119. goto out;
  120. }
  121. dir->parent_id = le32_to_cpu(h->bigdirparent);
  122. return 0;
  123. out:
  124. adfs_dir_relse(dir);
  125. return ret;
  126. }
  127. static int
  128. adfs_fplus_setpos(struct adfs_dir *dir, unsigned int fpos)
  129. {
  130. int ret = -ENOENT;
  131. if (fpos <= le32_to_cpu(dir->bighead->bigdirentries)) {
  132. dir->pos = fpos;
  133. ret = 0;
  134. }
  135. return ret;
  136. }
  137. static int
  138. adfs_fplus_getnext(struct adfs_dir *dir, struct object_info *obj)
  139. {
  140. struct adfs_bigdirheader *h = dir->bighead;
  141. struct adfs_bigdirentry bde;
  142. unsigned int offset;
  143. int ret;
  144. if (dir->pos >= le32_to_cpu(h->bigdirentries))
  145. return -ENOENT;
  146. offset = adfs_fplus_offset(h, dir->pos);
  147. ret = adfs_dir_copyfrom(&bde, dir, offset,
  148. sizeof(struct adfs_bigdirentry));
  149. if (ret)
  150. return ret;
  151. obj->loadaddr = le32_to_cpu(bde.bigdirload);
  152. obj->execaddr = le32_to_cpu(bde.bigdirexec);
  153. obj->size = le32_to_cpu(bde.bigdirlen);
  154. obj->indaddr = le32_to_cpu(bde.bigdirindaddr);
  155. obj->attr = le32_to_cpu(bde.bigdirattr);
  156. obj->name_len = le32_to_cpu(bde.bigdirobnamelen);
  157. offset = adfs_fplus_offset(h, le32_to_cpu(h->bigdirentries));
  158. offset += le32_to_cpu(bde.bigdirobnameptr);
  159. ret = adfs_dir_copyfrom(obj->name, dir, offset, obj->name_len);
  160. if (ret)
  161. return ret;
  162. adfs_object_fixup(dir, obj);
  163. dir->pos += 1;
  164. return 0;
  165. }
  166. static int adfs_fplus_iterate(struct adfs_dir *dir, struct dir_context *ctx)
  167. {
  168. struct object_info obj;
  169. if ((ctx->pos - 2) >> 32)
  170. return 0;
  171. if (adfs_fplus_setpos(dir, ctx->pos - 2))
  172. return 0;
  173. while (!adfs_fplus_getnext(dir, &obj)) {
  174. if (!dir_emit(ctx, obj.name, obj.name_len,
  175. obj.indaddr, DT_UNKNOWN))
  176. break;
  177. ctx->pos++;
  178. }
  179. return 0;
  180. }
  181. static int adfs_fplus_update(struct adfs_dir *dir, struct object_info *obj)
  182. {
  183. struct adfs_bigdirheader *h = dir->bighead;
  184. struct adfs_bigdirentry bde;
  185. int offset, end, ret;
  186. offset = adfs_fplus_offset(h, 0) - sizeof(bde);
  187. end = adfs_fplus_offset(h, le32_to_cpu(h->bigdirentries));
  188. do {
  189. offset += sizeof(bde);
  190. if (offset >= end) {
  191. adfs_error(dir->sb, "unable to locate entry to update");
  192. return -ENOENT;
  193. }
  194. ret = adfs_dir_copyfrom(&bde, dir, offset, sizeof(bde));
  195. if (ret) {
  196. adfs_error(dir->sb, "error reading directory entry");
  197. return -ENOENT;
  198. }
  199. } while (le32_to_cpu(bde.bigdirindaddr) != obj->indaddr);
  200. bde.bigdirload = cpu_to_le32(obj->loadaddr);
  201. bde.bigdirexec = cpu_to_le32(obj->execaddr);
  202. bde.bigdirlen = cpu_to_le32(obj->size);
  203. bde.bigdirindaddr = cpu_to_le32(obj->indaddr);
  204. bde.bigdirattr = cpu_to_le32(obj->attr);
  205. return adfs_dir_copyto(dir, offset, &bde, sizeof(bde));
  206. }
  207. static int adfs_fplus_commit(struct adfs_dir *dir)
  208. {
  209. int ret;
  210. /* Increment directory sequence number */
  211. dir->bighead->startmasseq += 1;
  212. dir->bigtail->bigdirendmasseq += 1;
  213. /* Update directory check byte */
  214. dir->bigtail->bigdircheckbyte = adfs_fplus_checkbyte(dir);
  215. /* Make sure the directory still validates correctly */
  216. ret = adfs_fplus_validate_header(dir->bighead);
  217. if (ret == 0)
  218. ret = adfs_fplus_validate_tail(dir->bighead, dir->bigtail);
  219. return ret;
  220. }
  221. const struct adfs_dir_ops adfs_fplus_dir_ops = {
  222. .read = adfs_fplus_read,
  223. .iterate = adfs_fplus_iterate,
  224. .setpos = adfs_fplus_setpos,
  225. .getnext = adfs_fplus_getnext,
  226. .update = adfs_fplus_update,
  227. .commit = adfs_fplus_commit,
  228. };