xref: /linux/fs/adfs/dir_fplus.c (revision 44f57d78)
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 <linux/buffer_head.h>
8 #include <linux/slab.h>
9 #include "adfs.h"
10 #include "dir_fplus.h"
11 
12 static int
13 adfs_fplus_read(struct super_block *sb, unsigned int id, unsigned int sz, struct adfs_dir *dir)
14 {
15 	struct adfs_bigdirheader *h;
16 	struct adfs_bigdirtail *t;
17 	unsigned long block;
18 	unsigned int blk, size;
19 	int i, ret = -EIO;
20 
21 	dir->nr_buffers = 0;
22 
23 	/* start off using fixed bh set - only alloc for big dirs */
24 	dir->bh_fplus = &dir->bh[0];
25 
26 	block = __adfs_block_map(sb, id, 0);
27 	if (!block) {
28 		adfs_error(sb, "dir object %X has a hole at offset 0", id);
29 		goto out;
30 	}
31 
32 	dir->bh_fplus[0] = sb_bread(sb, block);
33 	if (!dir->bh_fplus[0])
34 		goto out;
35 	dir->nr_buffers += 1;
36 
37 	h = (struct adfs_bigdirheader *)dir->bh_fplus[0]->b_data;
38 	size = le32_to_cpu(h->bigdirsize);
39 	if (size != sz) {
40 		printk(KERN_WARNING "adfs: adfs_fplus_read:"
41 					" directory header size %X\n"
42 					" does not match directory size %X\n",
43 					size, sz);
44 	}
45 
46 	if (h->bigdirversion[0] != 0 || h->bigdirversion[1] != 0 ||
47 	    h->bigdirversion[2] != 0 || size & 2047 ||
48 	    h->bigdirstartname != cpu_to_le32(BIGDIRSTARTNAME)) {
49 		printk(KERN_WARNING "adfs: dir object %X has"
50 					" malformed dir header\n", id);
51 		goto out;
52 	}
53 
54 	size >>= sb->s_blocksize_bits;
55 	if (size > ARRAY_SIZE(dir->bh)) {
56 		/* this directory is too big for fixed bh set, must allocate */
57 		struct buffer_head **bh_fplus =
58 			kcalloc(size, sizeof(struct buffer_head *),
59 				GFP_KERNEL);
60 		if (!bh_fplus) {
61 			ret = -ENOMEM;
62 			adfs_error(sb, "not enough memory for"
63 					" dir object %X (%d blocks)", id, size);
64 			goto out;
65 		}
66 		dir->bh_fplus = bh_fplus;
67 		/* copy over the pointer to the block that we've already read */
68 		dir->bh_fplus[0] = dir->bh[0];
69 	}
70 
71 	for (blk = 1; blk < size; blk++) {
72 		block = __adfs_block_map(sb, id, blk);
73 		if (!block) {
74 			adfs_error(sb, "dir object %X has a hole at offset %d", id, blk);
75 			goto out;
76 		}
77 
78 		dir->bh_fplus[blk] = sb_bread(sb, block);
79 		if (!dir->bh_fplus[blk]) {
80 			adfs_error(sb,	"dir object %x failed read for offset %d, mapped block %lX",
81 				   id, blk, block);
82 			goto out;
83 		}
84 
85 		dir->nr_buffers += 1;
86 	}
87 
88 	t = (struct adfs_bigdirtail *)
89 		(dir->bh_fplus[size - 1]->b_data + (sb->s_blocksize - 8));
90 
91 	if (t->bigdirendname != cpu_to_le32(BIGDIRENDNAME) ||
92 	    t->bigdirendmasseq != h->startmasseq ||
93 	    t->reserved[0] != 0 || t->reserved[1] != 0) {
94 		printk(KERN_WARNING "adfs: dir object %X has "
95 					"malformed dir end\n", id);
96 		goto out;
97 	}
98 
99 	dir->parent_id = le32_to_cpu(h->bigdirparent);
100 	dir->sb = sb;
101 	return 0;
102 
103 out:
104 	if (dir->bh_fplus) {
105 		for (i = 0; i < dir->nr_buffers; i++)
106 			brelse(dir->bh_fplus[i]);
107 
108 		if (&dir->bh[0] != dir->bh_fplus)
109 			kfree(dir->bh_fplus);
110 
111 		dir->bh_fplus = NULL;
112 	}
113 
114 	dir->nr_buffers = 0;
115 	dir->sb = NULL;
116 	return ret;
117 }
118 
119 static int
120 adfs_fplus_setpos(struct adfs_dir *dir, unsigned int fpos)
121 {
122 	struct adfs_bigdirheader *h =
123 		(struct adfs_bigdirheader *) dir->bh_fplus[0]->b_data;
124 	int ret = -ENOENT;
125 
126 	if (fpos <= le32_to_cpu(h->bigdirentries)) {
127 		dir->pos = fpos;
128 		ret = 0;
129 	}
130 
131 	return ret;
132 }
133 
134 static void
135 dir_memcpy(struct adfs_dir *dir, unsigned int offset, void *to, int len)
136 {
137 	struct super_block *sb = dir->sb;
138 	unsigned int buffer, partial, remainder;
139 
140 	buffer = offset >> sb->s_blocksize_bits;
141 	offset &= sb->s_blocksize - 1;
142 
143 	partial = sb->s_blocksize - offset;
144 
145 	if (partial >= len)
146 		memcpy(to, dir->bh_fplus[buffer]->b_data + offset, len);
147 	else {
148 		char *c = (char *)to;
149 
150 		remainder = len - partial;
151 
152 		memcpy(c,
153 			dir->bh_fplus[buffer]->b_data + offset,
154 			partial);
155 
156 		memcpy(c + partial,
157 			dir->bh_fplus[buffer + 1]->b_data,
158 			remainder);
159 	}
160 }
161 
162 static int
163 adfs_fplus_getnext(struct adfs_dir *dir, struct object_info *obj)
164 {
165 	struct adfs_bigdirheader *h =
166 		(struct adfs_bigdirheader *) dir->bh_fplus[0]->b_data;
167 	struct adfs_bigdirentry bde;
168 	unsigned int offset;
169 	int ret = -ENOENT;
170 
171 	if (dir->pos >= le32_to_cpu(h->bigdirentries))
172 		goto out;
173 
174 	offset = offsetof(struct adfs_bigdirheader, bigdirname);
175 	offset += ((le32_to_cpu(h->bigdirnamelen) + 4) & ~3);
176 	offset += dir->pos * sizeof(struct adfs_bigdirentry);
177 
178 	dir_memcpy(dir, offset, &bde, sizeof(struct adfs_bigdirentry));
179 
180 	obj->loadaddr = le32_to_cpu(bde.bigdirload);
181 	obj->execaddr = le32_to_cpu(bde.bigdirexec);
182 	obj->size     = le32_to_cpu(bde.bigdirlen);
183 	obj->file_id  = le32_to_cpu(bde.bigdirindaddr);
184 	obj->attr     = le32_to_cpu(bde.bigdirattr);
185 	obj->name_len = le32_to_cpu(bde.bigdirobnamelen);
186 
187 	offset = offsetof(struct adfs_bigdirheader, bigdirname);
188 	offset += ((le32_to_cpu(h->bigdirnamelen) + 4) & ~3);
189 	offset += le32_to_cpu(h->bigdirentries) * sizeof(struct adfs_bigdirentry);
190 	offset += le32_to_cpu(bde.bigdirobnameptr);
191 
192 	dir_memcpy(dir, offset, obj->name, obj->name_len);
193 	adfs_object_fixup(dir, obj);
194 
195 	dir->pos += 1;
196 	ret = 0;
197 out:
198 	return ret;
199 }
200 
201 static int
202 adfs_fplus_sync(struct adfs_dir *dir)
203 {
204 	int err = 0;
205 	int i;
206 
207 	for (i = dir->nr_buffers - 1; i >= 0; i--) {
208 		struct buffer_head *bh = dir->bh_fplus[i];
209 		sync_dirty_buffer(bh);
210 		if (buffer_req(bh) && !buffer_uptodate(bh))
211 			err = -EIO;
212 	}
213 
214 	return err;
215 }
216 
217 static void
218 adfs_fplus_free(struct adfs_dir *dir)
219 {
220 	int i;
221 
222 	if (dir->bh_fplus) {
223 		for (i = 0; i < dir->nr_buffers; i++)
224 			brelse(dir->bh_fplus[i]);
225 
226 		if (&dir->bh[0] != dir->bh_fplus)
227 			kfree(dir->bh_fplus);
228 
229 		dir->bh_fplus = NULL;
230 	}
231 
232 	dir->nr_buffers = 0;
233 	dir->sb = NULL;
234 }
235 
236 const struct adfs_dir_ops adfs_fplus_dir_ops = {
237 	.read		= adfs_fplus_read,
238 	.setpos		= adfs_fplus_setpos,
239 	.getnext	= adfs_fplus_getnext,
240 	.sync		= adfs_fplus_sync,
241 	.free		= adfs_fplus_free
242 };
243