1// SPDX-License-Identifier: GPL-2.0+
2#include "internal.h"
3#include <fs_internal.h>
4
5struct erofs_sb_info sbi;
6
7static struct erofs_ctxt {
8	struct disk_partition cur_part_info;
9	struct blk_desc *cur_dev;
10} ctxt;
11
12int erofs_dev_read(int device_id, void *buf, u64 offset, size_t len)
13{
14	lbaint_t sect = offset >> ctxt.cur_dev->log2blksz;
15	int off = offset & (ctxt.cur_dev->blksz - 1);
16
17	if (!ctxt.cur_dev)
18		return -EIO;
19
20	if (fs_devread(ctxt.cur_dev, &ctxt.cur_part_info, sect,
21		       off, len, buf))
22		return 0;
23	return -EIO;
24}
25
26int erofs_blk_read(void *buf, erofs_blk_t start, u32 nblocks)
27{
28	return erofs_dev_read(0, buf, erofs_pos(start),
29			 erofs_pos(nblocks));
30}
31
32int erofs_probe(struct blk_desc *fs_dev_desc,
33		struct disk_partition *fs_partition)
34{
35	int ret;
36
37	ctxt.cur_dev = fs_dev_desc;
38	ctxt.cur_part_info = *fs_partition;
39
40	ret = erofs_read_superblock();
41	if (ret)
42		goto error;
43
44	return 0;
45error:
46	ctxt.cur_dev = NULL;
47	return ret;
48}
49
50struct erofs_dir_stream {
51	struct fs_dir_stream fs_dirs;
52	struct fs_dirent dirent;
53
54	struct erofs_inode inode;
55	char dblk[EROFS_MAX_BLOCK_SIZE];
56	unsigned int maxsize, de_end;
57	erofs_off_t pos;
58};
59
60static int erofs_readlink(struct erofs_inode *vi)
61{
62	size_t len = vi->i_size;
63	char *target;
64	int err;
65
66	target = malloc(len + 1);
67	if (!target)
68		return -ENOMEM;
69	target[len] = '\0';
70
71	err = erofs_pread(vi, target, len, 0);
72	if (err)
73		goto err_out;
74
75	err = erofs_ilookup(target, vi);
76	if (err)
77		goto err_out;
78
79err_out:
80	free(target);
81	return err;
82}
83
84int erofs_opendir(const char *filename, struct fs_dir_stream **dirsp)
85{
86	struct erofs_dir_stream *dirs;
87	int err;
88
89	dirs = calloc(1, sizeof(*dirs));
90	if (!dirs)
91		return -ENOMEM;
92
93	err = erofs_ilookup(filename, &dirs->inode);
94	if (err)
95		goto err_out;
96
97	if (S_ISLNK(dirs->inode.i_mode)) {
98		err = erofs_readlink(&dirs->inode);
99		if (err)
100			goto err_out;
101	}
102
103	if (!S_ISDIR(dirs->inode.i_mode)) {
104		err = -ENOTDIR;
105		goto err_out;
106	}
107	*dirsp = (struct fs_dir_stream *)dirs;
108	return 0;
109err_out:
110	free(dirs);
111	return err;
112}
113
114int erofs_readdir(struct fs_dir_stream *fs_dirs, struct fs_dirent **dentp)
115{
116	struct erofs_dir_stream *dirs = (struct erofs_dir_stream *)fs_dirs;
117	struct fs_dirent *dent = &dirs->dirent;
118	erofs_off_t pos = dirs->pos;
119	unsigned int nameoff, de_namelen;
120	struct erofs_dirent *de;
121	char *de_name;
122	int err;
123
124	if (pos >= dirs->inode.i_size)
125		return 1;
126
127	if (!dirs->maxsize) {
128		dirs->maxsize = min_t(unsigned int, EROFS_MAX_BLOCK_SIZE,
129				      dirs->inode.i_size - pos);
130
131		err = erofs_pread(&dirs->inode, dirs->dblk,
132				  dirs->maxsize, pos);
133		if (err)
134			return err;
135
136		de = (struct erofs_dirent *)dirs->dblk;
137		dirs->de_end = le16_to_cpu(de->nameoff);
138		if (dirs->de_end < sizeof(struct erofs_dirent) ||
139		    dirs->de_end >= EROFS_MAX_BLOCK_SIZE) {
140			erofs_err("invalid de[0].nameoff %u @ nid %llu",
141				  dirs->de_end, de->nid | 0ULL);
142			return -EFSCORRUPTED;
143		}
144	}
145
146	de = (struct erofs_dirent *)(dirs->dblk + erofs_blkoff(pos));
147	nameoff = le16_to_cpu(de->nameoff);
148	de_name = (char *)dirs->dblk + nameoff;
149
150	/* the last dirent in the block? */
151	if (de + 1 >= (struct erofs_dirent *)(dirs->dblk + dirs->de_end))
152		de_namelen = strnlen(de_name, dirs->maxsize - nameoff);
153	else
154		de_namelen = le16_to_cpu(de[1].nameoff) - nameoff;
155
156	/* a corrupted entry is found */
157	if (nameoff + de_namelen > dirs->maxsize ||
158	    de_namelen > EROFS_NAME_LEN) {
159		erofs_err("bogus dirent @ nid %llu", de->nid | 0ULL);
160		DBG_BUGON(1);
161		return -EFSCORRUPTED;
162	}
163
164	memcpy(dent->name, de_name, de_namelen);
165	dent->name[de_namelen] = '\0';
166
167	if (de->file_type == EROFS_FT_DIR) {
168		dent->type = FS_DT_DIR;
169	} else if (de->file_type == EROFS_FT_SYMLINK) {
170		dent->type = FS_DT_LNK;
171	} else {
172		struct erofs_inode vi;
173
174		dent->type = FS_DT_REG;
175		vi.nid = de->nid;
176
177		err = erofs_read_inode_from_disk(&vi);
178		if (err)
179			return err;
180		dent->size = vi.i_size;
181	}
182	*dentp = dent;
183
184	pos += sizeof(*de);
185	if (erofs_blkoff(pos) >= dirs->de_end) {
186		pos = erofs_pos(erofs_blknr(pos) + 1);
187		dirs->maxsize = 0;
188	}
189	dirs->pos = pos;
190	return 0;
191}
192
193void erofs_closedir(struct fs_dir_stream *fs_dirs)
194{
195	free(fs_dirs);
196}
197
198int erofs_exists(const char *filename)
199{
200	struct erofs_inode vi;
201	int err;
202
203	err = erofs_ilookup(filename, &vi);
204	return err == 0;
205}
206
207int erofs_size(const char *filename, loff_t *size)
208{
209	struct erofs_inode vi;
210	int err;
211
212	err = erofs_ilookup(filename, &vi);
213	if (err)
214		return err;
215	*size = vi.i_size;
216	return 0;
217}
218
219int erofs_read(const char *filename, void *buf, loff_t offset, loff_t len,
220	       loff_t *actread)
221{
222	struct erofs_inode vi;
223	int err;
224
225	err = erofs_ilookup(filename, &vi);
226	if (err)
227		return err;
228
229	if (S_ISLNK(vi.i_mode)) {
230		err = erofs_readlink(&vi);
231		if (err)
232			return err;
233	}
234
235	if (!len)
236		len = vi.i_size;
237
238	err = erofs_pread(&vi, buf, len, offset);
239	if (err) {
240		*actread = 0;
241		return err;
242	}
243
244	if (offset >= vi.i_size)
245		*actread = 0;
246	else if (offset + len > vi.i_size)
247		*actread = vi.i_size - offset;
248	else
249		*actread = len;
250	return 0;
251}
252
253void erofs_close(void)
254{
255	ctxt.cur_dev = NULL;
256}
257
258int erofs_uuid(char *uuid_str)
259{
260	if (IS_ENABLED(CONFIG_LIB_UUID)) {
261		if (ctxt.cur_dev)
262			uuid_bin_to_str(sbi.uuid, uuid_str,
263					UUID_STR_FORMAT_STD);
264		return 0;
265	}
266	return -ENOSYS;
267}
268