1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * (C) Copyright 2011 - 2012 Samsung Electronics
4  * EXT4 filesystem implementation in Uboot by
5  * Uma Shankar <uma.shankar@samsung.com>
6  * Manjunatha C Achar <a.manjunatha@samsung.com>
7  *
8  * ext4ls and ext4load : Based on ext2 ls and load support in Uboot.
9  *		       Ext4 read optimization taken from Open-Moko
10  *		       Qi bootloader
11  *
12  * (C) Copyright 2004
13  * esd gmbh <www.esd-electronics.com>
14  * Reinhard Arlt <reinhard.arlt@esd-electronics.com>
15  *
16  * based on code from grub2 fs/ext2.c and fs/fshelp.c by
17  * GRUB  --  GRand Unified Bootloader
18  * Copyright (C) 2003, 2004  Free Software Foundation, Inc.
19  *
20  * ext4write : Based on generic ext4 protocol.
21  */
22 
23 #include <blk.h>
24 #include <div64.h>
25 #include <errno.h>
26 #include <ext_common.h>
27 #include <ext4fs.h>
28 #include <malloc.h>
29 #include <part.h>
30 #include <rtc.h>
31 #include <u-boot/uuid.h>
32 #include "ext4_common.h"
33 
34 int ext4fs_symlinknest;
35 struct ext_filesystem ext_fs;
36 
37 /**
38  * struct ext4_dir_stream - ext4 directory stream
39  *
40  * @parent: partition data used by fs layer.
41  * This field must be at the beginning of the structure.
42  * All other fields are private to the ext4 driver.
43  * @root:	root directory node
44  * @dir:	directory node
45  * @dirent:	directory stream entry
46  * @fpos:	file position in directory
47  */
48 struct ext4_dir_stream {
49 	struct fs_dir_stream parent;
50 	char *dirname;
51 	struct fs_dirent dirent;
52 	unsigned int fpos;
53 };
54 
get_fs(void)55 struct ext_filesystem *get_fs(void)
56 {
57 	return &ext_fs;
58 }
59 
ext4fs_free_node(struct ext2fs_node * node,struct ext2fs_node * currroot)60 void ext4fs_free_node(struct ext2fs_node *node, struct ext2fs_node *currroot)
61 {
62 	if ((node != &ext4fs_root->diropen) && (node != currroot))
63 		free(node);
64 }
65 
66 /*
67  * Taken from openmoko-kernel mailing list: By Andy green
68  * Optimized read file API : collects and defers contiguous sector
69  * reads into one potentially more efficient larger sequential read action
70  */
ext4fs_read_file(struct ext2fs_node * node,loff_t pos,loff_t len,char * buf,loff_t * actread)71 int ext4fs_read_file(struct ext2fs_node *node, loff_t pos,
72 		loff_t len, char *buf, loff_t *actread)
73 {
74 	struct ext_filesystem *fs = get_fs();
75 	int i;
76 	lbaint_t blockcnt;
77 	int log2blksz = fs->dev_desc->log2blksz;
78 	int log2_fs_blocksize = LOG2_BLOCK_SIZE(node->data) - log2blksz;
79 	int blocksize = (1 << (log2_fs_blocksize + log2blksz));
80 	unsigned int filesize = le32_to_cpu(node->inode.size);
81 	lbaint_t previous_block_number = -1;
82 	lbaint_t delayed_start = 0;
83 	lbaint_t delayed_extent = 0;
84 	lbaint_t delayed_skipfirst = 0;
85 	lbaint_t delayed_next = 0;
86 	char *delayed_buf = NULL;
87 	char *start_buf = buf;
88 	short status;
89 	struct ext_block_cache cache;
90 
91 	ext_cache_init(&cache);
92 
93 	/* Adjust len so it we can't read past the end of the file. */
94 	if (len + pos > filesize)
95 		len = (filesize - pos);
96 
97 	if (blocksize <= 0 || len <= 0) {
98 		ext_cache_fini(&cache);
99 		return -1;
100 	}
101 
102 	blockcnt = lldiv(((len + pos) + blocksize - 1), blocksize);
103 
104 	for (i = lldiv(pos, blocksize); i < blockcnt; i++) {
105 		lbaint_t blknr;
106 		long blknr_and_status;
107 		int blockoff = pos - (blocksize * i);
108 		int blockend = blocksize;
109 		int skipfirst = 0;
110 		blknr_and_status = read_allocated_block(&node->inode, i, &cache);
111 		if (blknr_and_status < 0) {
112 			ext_cache_fini(&cache);
113 			return -1;
114 		}
115 
116 		/* Block number could becomes very large when CONFIG_SYS_64BIT_LBA is enabled
117 		 * and wrap around at max long int
118 		 */
119 		blknr = (lbaint_t)blknr_and_status << log2_fs_blocksize;
120 
121 		/* Last block.  */
122 		if (i == blockcnt - 1) {
123 			blockend = (len + pos) - (blocksize * i);
124 
125 			/* The last portion is exactly blocksize. */
126 			if (!blockend)
127 				blockend = blocksize;
128 		}
129 
130 		/* First block. */
131 		if (i == lldiv(pos, blocksize)) {
132 			skipfirst = blockoff;
133 			blockend -= skipfirst;
134 		}
135 		if (blknr) {
136 			int status;
137 
138 			if (previous_block_number != -1) {
139 				if (delayed_next == blknr) {
140 					delayed_extent += blockend;
141 					delayed_next += blockend >> log2blksz;
142 				} else {	/* spill */
143 					status = ext4fs_devread(delayed_start,
144 							delayed_skipfirst,
145 							delayed_extent,
146 							delayed_buf);
147 					if (status == 0) {
148 						ext_cache_fini(&cache);
149 						return -1;
150 					}
151 					previous_block_number = blknr;
152 					delayed_start = blknr;
153 					delayed_extent = blockend;
154 					delayed_skipfirst = skipfirst;
155 					delayed_buf = buf;
156 					delayed_next = blknr +
157 						(blockend >> log2blksz);
158 				}
159 			} else {
160 				previous_block_number = blknr;
161 				delayed_start = blknr;
162 				delayed_extent = blockend;
163 				delayed_skipfirst = skipfirst;
164 				delayed_buf = buf;
165 				delayed_next = blknr +
166 					(blockend >> log2blksz);
167 			}
168 		} else {
169 			int n;
170 			int n_left;
171 			if (previous_block_number != -1) {
172 				/* spill */
173 				status = ext4fs_devread(delayed_start,
174 							delayed_skipfirst,
175 							delayed_extent,
176 							delayed_buf);
177 				if (status == 0) {
178 					ext_cache_fini(&cache);
179 					return -1;
180 				}
181 				previous_block_number = -1;
182 			}
183 			/* Zero no more than `len' bytes. */
184 			n = blocksize - skipfirst;
185 			n_left = len - ( buf - start_buf );
186 			if (n > n_left)
187 				n = n_left;
188 			memset(buf, 0, n);
189 		}
190 		buf += blocksize - skipfirst;
191 	}
192 	if (previous_block_number != -1) {
193 		/* spill */
194 		status = ext4fs_devread(delayed_start,
195 					delayed_skipfirst, delayed_extent,
196 					delayed_buf);
197 		if (status == 0) {
198 			ext_cache_fini(&cache);
199 			return -1;
200 		}
201 		previous_block_number = -1;
202 	}
203 
204 	*actread  = len;
205 	ext_cache_fini(&cache);
206 	return 0;
207 }
208 
ext4fs_opendir(const char * dirname,struct fs_dir_stream ** dirsp)209 int ext4fs_opendir(const char *dirname, struct fs_dir_stream **dirsp)
210 {
211 	struct ext4_dir_stream *dirs;
212 	struct ext2fs_node *dir = NULL;
213 	int ret;
214 
215 	*dirsp = NULL;
216 
217 	dirs = calloc(1, sizeof(struct ext4_dir_stream));
218 	if (!dirs)
219 		return -ENOMEM;
220 	dirs->dirname = strdup(dirname);
221 	if (!dirs->dirname) {
222 		free(dirs);
223 		return -ENOMEM;
224 	}
225 
226 	ret = ext4fs_find_file(dirname, &ext4fs_root->diropen, &dir,
227 			       FILETYPE_DIRECTORY);
228 	if (ret == 1) {
229 		ret = 0;
230 		*dirsp = (struct fs_dir_stream *)dirs;
231 	} else {
232 		free(dirs->dirname);
233 		free(dirs);
234 		ret = -ENOENT;
235 	}
236 
237 	if (dir)
238 		ext4fs_free_node(dir, &ext4fs_root->diropen);
239 
240 	return ret;
241 }
242 
ext4fs_readdir(struct fs_dir_stream * fs_dirs,struct fs_dirent ** dentp)243 int ext4fs_readdir(struct fs_dir_stream *fs_dirs, struct fs_dirent **dentp)
244 {
245 	struct ext4_dir_stream *dirs = (struct ext4_dir_stream *)fs_dirs;
246 	struct fs_dirent *dent = &dirs->dirent;
247 	struct ext2fs_node *dir = NULL;
248 	int ret;
249 	loff_t actread;
250 	struct ext2fs_node fdiro;
251 	int len;
252 	struct ext2_dirent dirent;
253 
254 	*dentp = NULL;
255 
256 	ret = ext4fs_find_file(dirs->dirname, &ext4fs_root->diropen, &dir,
257 			       FILETYPE_DIRECTORY);
258 	if (ret != 1) {
259 		ret = -ENOENT;
260 		goto out;
261 	}
262 	if (!dir->inode_read) {
263 		ret = ext4fs_read_inode(dir->data, dir->ino, &dir->inode);
264 		if (!ret) {
265 			ret = -EIO;
266 			goto out;
267 		}
268 	}
269 
270 	if (dirs->fpos >= le32_to_cpu(dir->inode.size))
271 		return -ENOENT;
272 
273 	memset(dent, 0, sizeof(struct fs_dirent));
274 
275 	while (dirs->fpos < le32_to_cpu(dir->inode.size)) {
276 		ret = ext4fs_read_file(dir, dirs->fpos,
277 				       sizeof(struct ext2_dirent),
278 				       (char *)&dirent, &actread);
279 		if (ret < 0)
280 			return ret;
281 
282 		if (!dirent.direntlen)
283 			return -EIO;
284 
285 		if (dirent.namelen)
286 			break;
287 
288 		dirs->fpos += le16_to_cpu(dirent.direntlen);
289 	}
290 
291 	len = min(FS_DIRENT_NAME_LEN - 1, (int)dirent.namelen);
292 
293 	ret = ext4fs_read_file(dir, dirs->fpos + sizeof(struct ext2_dirent),
294 			       len, dent->name, &actread);
295 	if (ret < 0)
296 		goto out;
297 	dent->name[len] = '\0';
298 
299 	fdiro.data = dir->data;
300 	fdiro.ino = le32_to_cpu(dirent.inode);
301 
302 	ret = ext4fs_read_inode(dir->data, fdiro.ino, &fdiro.inode);
303 	if (!ret) {
304 		ret = -EIO;
305 		goto out;
306 	}
307 
308 	switch (le16_to_cpu(fdiro.inode.mode) & FILETYPE_INO_MASK) {
309 	case FILETYPE_INO_DIRECTORY:
310 		dent->type = FS_DT_DIR;
311 		break;
312 	case FILETYPE_INO_SYMLINK:
313 		dent->type = FS_DT_LNK;
314 		break;
315 	case FILETYPE_INO_REG:
316 		dent->type = FS_DT_REG;
317 		break;
318 	default:
319 		dent->type = FILETYPE_UNKNOWN;
320 	}
321 
322 	rtc_to_tm(fdiro.inode.atime, &dent->access_time);
323 	rtc_to_tm(fdiro.inode.ctime, &dent->create_time);
324 	rtc_to_tm(fdiro.inode.mtime, &dent->change_time);
325 
326 	dirs->fpos += le16_to_cpu(dirent.direntlen);
327 	dent->size = fdiro.inode.size;
328 	*dentp = dent;
329 	ret = 0;
330 
331 out:
332 	if (dir)
333 		ext4fs_free_node(dir, &ext4fs_root->diropen);
334 
335 	return ret;
336 }
337 
ext4fs_closedir(struct fs_dir_stream * fs_dirs)338 void ext4fs_closedir(struct fs_dir_stream *fs_dirs)
339 {
340 	struct ext4_dir_stream *dirs = (struct ext4_dir_stream *)fs_dirs;
341 
342 	if (!dirs)
343 		return;
344 
345 	free(dirs->dirname);
346 	free(dirs);
347 }
348 
ext4fs_exists(const char * filename)349 int ext4fs_exists(const char *filename)
350 {
351 	struct ext2fs_node *dirnode = NULL;
352 	int filetype;
353 	int ret;
354 
355 	if (!filename)
356 		return 0;
357 
358 	ret = ext4fs_find_file1(filename, &ext4fs_root->diropen, &dirnode,
359 				&filetype);
360 	if (dirnode)
361 		ext4fs_free_node(dirnode, &ext4fs_root->diropen);
362 
363 	return ret;
364 }
365 
ext4fs_size(const char * filename,loff_t * size)366 int ext4fs_size(const char *filename, loff_t *size)
367 {
368 	return ext4fs_open(filename, size);
369 }
370 
ext4fs_read(char * buf,loff_t offset,loff_t len,loff_t * actread)371 int ext4fs_read(char *buf, loff_t offset, loff_t len, loff_t *actread)
372 {
373 	if (ext4fs_root == NULL || ext4fs_file == NULL)
374 		return -1;
375 
376 	return ext4fs_read_file(ext4fs_file, offset, len, buf, actread);
377 }
378 
ext4fs_probe(struct blk_desc * fs_dev_desc,struct disk_partition * fs_partition)379 int ext4fs_probe(struct blk_desc *fs_dev_desc,
380 		 struct disk_partition *fs_partition)
381 {
382 	ext4fs_set_blk_dev(fs_dev_desc, fs_partition);
383 
384 	if (!ext4fs_mount()) {
385 		ext4fs_close();
386 		return -1;
387 	}
388 
389 	return 0;
390 }
391 
ext4_read_file(const char * filename,void * buf,loff_t offset,loff_t len,loff_t * len_read)392 int ext4_read_file(const char *filename, void *buf, loff_t offset, loff_t len,
393 		   loff_t *len_read)
394 {
395 	loff_t file_len;
396 	int ret;
397 
398 	ret = ext4fs_open(filename, &file_len);
399 	if (ret < 0) {
400 		printf("** File not found %s **\n", filename);
401 		return -1;
402 	}
403 
404 	if (len == 0)
405 		len = file_len;
406 
407 	return ext4fs_read(buf, offset, len, len_read);
408 }
409 
ext4fs_uuid(char * uuid_str)410 int ext4fs_uuid(char *uuid_str)
411 {
412 	if (ext4fs_root == NULL)
413 		return -1;
414 
415 #ifdef CONFIG_LIB_UUID
416 	uuid_bin_to_str((unsigned char *)ext4fs_root->sblock.unique_id,
417 			uuid_str, UUID_STR_FORMAT_STD);
418 
419 	return 0;
420 #else
421 	return -ENOSYS;
422 #endif
423 }
424 
ext_cache_init(struct ext_block_cache * cache)425 void ext_cache_init(struct ext_block_cache *cache)
426 {
427 	memset(cache, 0, sizeof(*cache));
428 }
429 
ext_cache_fini(struct ext_block_cache * cache)430 void ext_cache_fini(struct ext_block_cache *cache)
431 {
432 	free(cache->buf);
433 	ext_cache_init(cache);
434 }
435 
ext_cache_read(struct ext_block_cache * cache,lbaint_t block,int size)436 int ext_cache_read(struct ext_block_cache *cache, lbaint_t block, int size)
437 {
438 	/* This could be more lenient, but this is simple and enough for now */
439 	if (cache->buf && cache->block == block && cache->size == size)
440 		return 1;
441 	ext_cache_fini(cache);
442 	cache->buf = memalign(ARCH_DMA_MINALIGN, size);
443 	if (!cache->buf)
444 		return 0;
445 	if (!ext4fs_devread(block, 0, size, cache->buf)) {
446 		ext_cache_fini(cache);
447 		return 0;
448 	}
449 	cache->block = block;
450 	cache->size = size;
451 	return 1;
452 }
453