blob: cfea313e34b9356df6855101dbdbd3b1647799fd [file] [log] [blame]
Joao Marcos Costa29da3742020-07-30 15:33:47 +02001// SPDX-License-Identifier: GPL-2.0
2/*
3 * Copyright (C) 2020 Bootlin
4 *
5 * Author: Joao Marcos Costa <joaomarcos.costa@bootlin.com>
6 *
7 * sqfs.c: SquashFS filesystem implementation
8 */
9
10#include <asm/unaligned.h>
11#include <errno.h>
12#include <fs.h>
13#include <linux/types.h>
14#include <linux/byteorder/little_endian.h>
15#include <linux/byteorder/generic.h>
16#include <memalign.h>
17#include <stdlib.h>
18#include <string.h>
19#include <squashfs.h>
20#include <part.h>
21
22#include "sqfs_decompressor.h"
23#include "sqfs_filesystem.h"
24#include "sqfs_utils.h"
25
Joao Marcos Costa29da3742020-07-30 15:33:47 +020026static struct squashfs_ctxt ctxt;
27
28static int sqfs_disk_read(__u32 block, __u32 nr_blocks, void *buf)
29{
30 ulong ret;
31
32 if (!ctxt.cur_dev)
33 return -1;
34
35 ret = blk_dread(ctxt.cur_dev, ctxt.cur_part_info.start + block,
36 nr_blocks, buf);
37
38 if (ret != nr_blocks)
39 return -1;
40
41 return ret;
42}
43
44static int sqfs_read_sblk(struct squashfs_super_block **sblk)
45{
46 *sblk = malloc_cache_aligned(ctxt.cur_dev->blksz);
47 if (!*sblk)
48 return -ENOMEM;
49
50 if (sqfs_disk_read(0, 1, *sblk) != 1) {
51 free(*sblk);
52 return -EINVAL;
53 }
54
55 return 0;
56}
57
58static int sqfs_count_tokens(const char *filename)
59{
60 int token_count = 1, l;
61
62 for (l = 1; l < strlen(filename); l++) {
63 if (filename[l] == '/')
64 token_count++;
65 }
66
67 /* Ignore trailing '/' in path */
68 if (filename[strlen(filename) - 1] == '/')
69 token_count--;
70
71 if (!token_count)
72 token_count = 1;
73
74 return token_count;
75}
76
77/*
78 * Calculates how many blocks are needed for the buffer used in sqfs_disk_read.
79 * The memory section (e.g. inode table) start offset and its end (i.e. the next
80 * table start) must be specified. It also calculates the offset from which to
81 * start reading the buffer.
82 */
83static int sqfs_calc_n_blks(__le64 start, __le64 end, u64 *offset)
84{
85 u64 start_, table_size;
86
87 table_size = le64_to_cpu(end) - le64_to_cpu(start);
88 start_ = le64_to_cpu(start) / ctxt.cur_dev->blksz;
89 *offset = le64_to_cpu(start) - (start_ * ctxt.cur_dev->blksz);
90
91 return DIV_ROUND_UP(table_size + *offset, ctxt.cur_dev->blksz);
92}
93
94/*
95 * Retrieves fragment block entry and returns true if the fragment block is
96 * compressed
97 */
98static int sqfs_frag_lookup(u32 inode_fragment_index,
99 struct squashfs_fragment_block_entry *e)
100{
101 u64 start, n_blks, src_len, table_offset, start_block;
102 unsigned char *metadata_buffer, *metadata, *table;
103 struct squashfs_fragment_block_entry *entries;
104 struct squashfs_super_block *sblk = ctxt.sblk;
105 unsigned long dest_len;
106 int block, offset, ret;
Joao Marcos Costab87fd012020-08-18 17:17:22 +0200107 u16 header;
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200108
109 if (inode_fragment_index >= get_unaligned_le32(&sblk->fragments))
110 return -EINVAL;
111
112 start = get_unaligned_le64(&sblk->fragment_table_start) /
113 ctxt.cur_dev->blksz;
114 n_blks = sqfs_calc_n_blks(sblk->fragment_table_start,
115 sblk->export_table_start,
116 &table_offset);
117
118 /* Allocate a proper sized buffer to store the fragment index table */
119 table = malloc_cache_aligned(n_blks * ctxt.cur_dev->blksz);
120 if (!table)
121 return -ENOMEM;
122
123 if (sqfs_disk_read(start, n_blks, table) < 0) {
124 free(table);
125 return -EINVAL;
126 }
127
128 block = SQFS_FRAGMENT_INDEX(inode_fragment_index);
129 offset = SQFS_FRAGMENT_INDEX_OFFSET(inode_fragment_index);
130
131 /*
132 * Get the start offset of the metadata block that contains the right
133 * fragment block entry
134 */
135 start_block = get_unaligned_le64(table + table_offset + block *
136 sizeof(u64));
137
138 start = start_block / ctxt.cur_dev->blksz;
139 n_blks = sqfs_calc_n_blks(cpu_to_le64(start_block),
140 sblk->fragment_table_start, &table_offset);
141
142 metadata_buffer = malloc_cache_aligned(n_blks * ctxt.cur_dev->blksz);
143 if (!metadata_buffer) {
144 ret = -ENOMEM;
145 goto free_table;
146 }
147
148 if (sqfs_disk_read(start, n_blks, metadata_buffer) < 0) {
149 ret = -EINVAL;
150 goto free_buffer;
151 }
152
153 /* Every metadata block starts with a 16-bit header */
154 header = get_unaligned_le16(metadata_buffer + table_offset);
155 metadata = metadata_buffer + table_offset + SQFS_HEADER_SIZE;
156
Joao Marcos Costaf1dfe4a2020-09-11 12:21:06 +0200157 if (!metadata || !header) {
Joao Marcos Costab7fb3c32020-08-19 18:28:41 +0200158 ret = -ENOMEM;
159 goto free_buffer;
160 }
161
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200162 entries = malloc(SQFS_METADATA_BLOCK_SIZE);
163 if (!entries) {
164 ret = -ENOMEM;
165 goto free_buffer;
166 }
167
168 if (SQFS_COMPRESSED_METADATA(header)) {
169 src_len = SQFS_METADATA_SIZE(header);
170 dest_len = SQFS_METADATA_BLOCK_SIZE;
Joao Marcos Costab87fd012020-08-18 17:17:22 +0200171 ret = sqfs_decompress(&ctxt, entries, &dest_len, metadata,
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200172 src_len);
173 if (ret) {
174 ret = -EINVAL;
175 goto free_entries;
176 }
177 } else {
178 memcpy(entries, metadata, SQFS_METADATA_SIZE(header));
179 }
180
181 *e = entries[offset];
182 ret = SQFS_COMPRESSED_BLOCK(e->size);
183
184free_entries:
185 free(entries);
186free_buffer:
187 free(metadata_buffer);
188free_table:
189 free(table);
190
191 return ret;
192}
193
194/*
195 * The entry name is a flexible array member, and we don't know its size before
196 * actually reading the entry. So we need a first copy to retrieve this size so
197 * we can finally copy the whole struct.
198 */
199static int sqfs_read_entry(struct squashfs_directory_entry **dest, void *src)
200{
201 struct squashfs_directory_entry *tmp;
202 u16 sz;
203
204 tmp = src;
205 sz = get_unaligned_le16(src + sizeof(*tmp) - sizeof(u16));
206 /*
207 * 'src' points to the begin of a directory entry, and 'sz' gets its
208 * 'name_size' member's value. name_size is actually the string
209 * length - 1, so adding 2 compensates this difference and adds space
210 * for the trailling null byte.
211 */
212 *dest = malloc(sizeof(*tmp) + sz + 2);
213 if (!*dest)
214 return -ENOMEM;
215
216 memcpy(*dest, src, sizeof(*tmp) + sz + 1);
217 (*dest)->name[sz + 1] = '\0';
218
219 return 0;
220}
221
222static int sqfs_get_tokens_length(char **tokens, int count)
223{
224 int length = 0, i;
225
226 /*
227 * 1 is added to the result of strlen to consider the slash separator
228 * between the tokens.
229 */
230 for (i = 0; i < count; i++)
231 length += strlen(tokens[i]) + 1;
232
233 return length;
234}
235
236/* Takes a token list and returns a single string with '/' as separator. */
237static char *sqfs_concat_tokens(char **token_list, int token_count)
238{
239 char *result;
240 int i, length = 0, offset = 0;
241
242 length = sqfs_get_tokens_length(token_list, token_count);
243
244 result = malloc(length + 1);
Richard Genoud489e7ae2020-11-03 12:11:08 +0100245 if (!result)
246 return NULL;
247
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200248 result[length] = '\0';
249
250 for (i = 0; i < token_count; i++) {
251 strcpy(result + offset, token_list[i]);
252 offset += strlen(token_list[i]);
253 result[offset++] = '/';
254 }
255
256 return result;
257}
258
259/*
260 * Differently from sqfs_concat_tokens, sqfs_join writes the result into a
261 * previously allocated string, and returns the number of bytes written.
262 */
263static int sqfs_join(char **strings, char *dest, int start, int end,
264 char separator)
265{
266 int i, offset = 0;
267
268 for (i = start; i < end; i++) {
269 strcpy(dest + offset, strings[i]);
270 offset += strlen(strings[i]);
271 if (i < end - 1)
272 dest[offset++] = separator;
273 }
274
275 return offset;
276}
277
278/*
279 * Fills the given token list using its size (count) and a source string (str)
280 */
281static int sqfs_tokenize(char **tokens, int count, const char *str)
282{
Joao Marcos Costab7fb3c32020-08-19 18:28:41 +0200283 int i, j, ret = 0;
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200284 char *aux, *strc;
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200285
286 strc = strdup(str);
287 if (!strc)
288 return -ENOMEM;
289
290 if (!strcmp(strc, "/")) {
291 tokens[0] = strdup(strc);
292 if (!tokens[0]) {
Joao Marcos Costab7fb3c32020-08-19 18:28:41 +0200293 ret = -ENOMEM;
294 goto free_strc;
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200295 }
296 } else {
297 for (j = 0; j < count; j++) {
298 aux = strtok(!j ? strc : NULL, "/");
299 tokens[j] = strdup(aux);
300 if (!tokens[j]) {
301 for (i = 0; i < j; i++)
302 free(tokens[i]);
Joao Marcos Costab7fb3c32020-08-19 18:28:41 +0200303 ret = -ENOMEM;
304 goto free_strc;
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200305 }
306 }
307 }
308
Joao Marcos Costab7fb3c32020-08-19 18:28:41 +0200309free_strc:
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200310 free(strc);
311
Joao Marcos Costab7fb3c32020-08-19 18:28:41 +0200312 return ret;
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200313}
314
315/*
316 * Remove last 'updir + 1' tokens from the base path tokens list. This leaves us
317 * with a token list containing only the tokens needed to form the resolved
318 * path, and returns the decremented size of the token list.
319 */
320static int sqfs_clean_base_path(char **base, int count, int updir)
321{
322 int i;
323
324 for (i = count - updir - 1; i < count; i++)
325 free(base[i]);
326
327 return count - updir - 1;
328}
329
330/*
331 * Given the base ("current dir.") path and the relative one, generate the
332 * absolute path.
333 */
334static char *sqfs_get_abs_path(const char *base, const char *rel)
335{
336 char **base_tokens, **rel_tokens, *resolved = NULL;
337 int ret, bc, rc, i, updir = 0, resolved_size = 0, offset = 0;
338
339 /* Memory allocation for the token lists */
340 bc = sqfs_count_tokens(base);
341 rc = sqfs_count_tokens(rel);
342 if (bc < 1 || rc < 1)
343 return NULL;
344
345 base_tokens = malloc(bc * sizeof(char *));
346 if (!base_tokens)
347 return NULL;
348
349 rel_tokens = malloc(rc * sizeof(char *));
350 if (!rel_tokens)
351 goto free_b_tokens;
352
353 /* Fill token lists */
354 ret = sqfs_tokenize(base_tokens, bc, base);
355 if (ret)
356 goto free_r_tokens;
357
358 sqfs_tokenize(rel_tokens, rc, rel);
359 if (ret)
360 goto free_r_tokens;
361
362 /* count '..' occurrences in target path */
363 for (i = 0; i < rc; i++) {
364 if (!strcmp(rel_tokens[i], ".."))
365 updir++;
366 }
367
368 /* Remove the last token and the '..' occurrences */
369 bc = sqfs_clean_base_path(base_tokens, bc, updir);
370 if (bc < 0)
371 goto free_r_tokens;
372
373 /* Calculate resolved path size */
374 if (!bc)
375 resolved_size++;
376
377 resolved_size += sqfs_get_tokens_length(base_tokens, bc) +
378 sqfs_get_tokens_length(rel_tokens, rc);
379
380 resolved = malloc(resolved_size + 1);
381 if (!resolved)
382 goto free_r_tokens_loop;
383
384 /* Set resolved path */
385 memset(resolved, '\0', resolved_size + 1);
386 offset += sqfs_join(base_tokens, resolved + offset, 0, bc, '/');
387 resolved[offset++] = '/';
388 offset += sqfs_join(rel_tokens, resolved + offset, updir, rc, '/');
389
390free_r_tokens_loop:
391 for (i = 0; i < rc; i++)
392 free(rel_tokens[i]);
393 for (i = 0; i < bc; i++)
394 free(base_tokens[i]);
395free_r_tokens:
396 free(rel_tokens);
397free_b_tokens:
398 free(base_tokens);
399
400 return resolved;
401}
402
403static char *sqfs_resolve_symlink(struct squashfs_symlink_inode *sym,
404 const char *base_path)
405{
406 char *resolved, *target;
407 u32 sz;
408
409 sz = get_unaligned_le32(&sym->symlink_size);
410 target = malloc(sz + 1);
411 if (!target)
412 return NULL;
413
414 /*
415 * There is no trailling null byte in the symlink's target path, so a
416 * copy is made and a '\0' is added at its end.
417 */
418 target[sz] = '\0';
419 /* Get target name (relative path) */
420 strncpy(target, sym->symlink, sz);
421
422 /* Relative -> absolute path conversion */
423 resolved = sqfs_get_abs_path(base_path, target);
424
425 free(target);
426
427 return resolved;
428}
429
430/*
431 * m_list contains each metadata block's position, and m_count is the number of
432 * elements of m_list. Those metadata blocks come from the compressed directory
433 * table.
434 */
435static int sqfs_search_dir(struct squashfs_dir_stream *dirs, char **token_list,
436 int token_count, u32 *m_list, int m_count)
437{
438 struct squashfs_super_block *sblk = ctxt.sblk;
439 char *path, *target, **sym_tokens, *res, *rem;
Richard Genoud2762f652020-11-03 12:11:06 +0100440 int j, ret = 0, new_inode_number, offset;
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200441 struct squashfs_symlink_inode *sym;
Joao Marcos Costaf1dfe4a2020-09-11 12:21:06 +0200442 struct squashfs_ldir_inode *ldir;
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200443 struct squashfs_dir_inode *dir;
444 struct fs_dir_stream *dirsp;
445 struct fs_dirent *dent;
446 unsigned char *table;
447
Richard Genoud2762f652020-11-03 12:11:06 +0100448 res = NULL;
449 rem = NULL;
450 path = NULL;
451 target = NULL;
452 sym_tokens = NULL;
453
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200454 dirsp = (struct fs_dir_stream *)dirs;
455
456 /* Start by root inode */
457 table = sqfs_find_inode(dirs->inode_table, le32_to_cpu(sblk->inodes),
458 sblk->inodes, sblk->block_size);
459
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200460 dir = (struct squashfs_dir_inode *)table;
Joao Marcos Costaf1dfe4a2020-09-11 12:21:06 +0200461 ldir = (struct squashfs_ldir_inode *)table;
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200462
463 /* get directory offset in directory table */
464 offset = sqfs_dir_offset(table, m_list, m_count);
465 dirs->table = &dirs->dir_table[offset];
466
467 /* Setup directory header */
468 dirs->dir_header = malloc(SQFS_DIR_HEADER_SIZE);
469 if (!dirs->dir_header)
470 return -ENOMEM;
471
472 memcpy(dirs->dir_header, dirs->table, SQFS_DIR_HEADER_SIZE);
473
474 /* Initialize squashfs_dir_stream members */
475 dirs->table += SQFS_DIR_HEADER_SIZE;
476 dirs->size = get_unaligned_le16(&dir->file_size) - SQFS_DIR_HEADER_SIZE;
477 dirs->entry_count = dirs->dir_header->count + 1;
478
479 /* No path given -> root directory */
480 if (!strcmp(token_list[0], "/")) {
481 dirs->table = &dirs->dir_table[offset];
482 memcpy(&dirs->i_dir, dir, sizeof(*dir));
483 return 0;
484 }
485
486 for (j = 0; j < token_count; j++) {
487 if (!sqfs_is_dir(get_unaligned_le16(&dir->inode_type))) {
488 printf("** Cannot find directory. **\n");
Richard Genoud2762f652020-11-03 12:11:06 +0100489 ret = -EINVAL;
490 goto out;
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200491 }
492
493 while (!sqfs_readdir(dirsp, &dent)) {
494 ret = strcmp(dent->name, token_list[j]);
495 if (!ret)
496 break;
497 free(dirs->entry);
Richard Genoude405fc42020-11-03 12:11:05 +0100498 dirs->entry = NULL;
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200499 }
500
501 if (ret) {
502 printf("** Cannot find directory. **\n");
Richard Genoud2762f652020-11-03 12:11:06 +0100503 ret = -EINVAL;
504 goto out;
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200505 }
506
507 /* Redefine inode as the found token */
508 new_inode_number = dirs->entry->inode_offset +
509 dirs->dir_header->inode_number;
510
511 /* Get reference to inode in the inode table */
512 table = sqfs_find_inode(dirs->inode_table, new_inode_number,
513 sblk->inodes, sblk->block_size);
514 dir = (struct squashfs_dir_inode *)table;
515
516 /* Check for symbolic link and inode type sanity */
517 if (get_unaligned_le16(&dir->inode_type) == SQFS_SYMLINK_TYPE) {
518 sym = (struct squashfs_symlink_inode *)table;
519 /* Get first j + 1 tokens */
520 path = sqfs_concat_tokens(token_list, j + 1);
Richard Genoud2762f652020-11-03 12:11:06 +0100521 if (!path) {
522 ret = -ENOMEM;
523 goto out;
524 }
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200525 /* Resolve for these tokens */
526 target = sqfs_resolve_symlink(sym, path);
Richard Genoud2762f652020-11-03 12:11:06 +0100527 if (!target) {
528 ret = -ENOMEM;
529 goto out;
530 }
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200531 /* Join remaining tokens */
532 rem = sqfs_concat_tokens(token_list + j + 1, token_count -
533 j - 1);
Richard Genoud2762f652020-11-03 12:11:06 +0100534 if (!rem) {
535 ret = -ENOMEM;
536 goto out;
537 }
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200538 /* Concatenate remaining tokens and symlink's target */
539 res = malloc(strlen(rem) + strlen(target) + 1);
Richard Genoud2762f652020-11-03 12:11:06 +0100540 if (!res) {
541 ret = -ENOMEM;
542 goto out;
543 }
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200544 strcpy(res, target);
545 res[strlen(target)] = '/';
546 strcpy(res + strlen(target) + 1, rem);
547 token_count = sqfs_count_tokens(res);
548
Richard Genoud2762f652020-11-03 12:11:06 +0100549 if (token_count < 0) {
550 ret = -EINVAL;
551 goto out;
552 }
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200553
554 sym_tokens = malloc(token_count * sizeof(char *));
Richard Genoud2762f652020-11-03 12:11:06 +0100555 if (!sym_tokens) {
556 ret = -EINVAL;
557 goto out;
558 }
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200559
560 /* Fill tokens list */
561 ret = sqfs_tokenize(sym_tokens, token_count, res);
Richard Genoud2762f652020-11-03 12:11:06 +0100562 if (ret) {
563 ret = -EINVAL;
564 goto out;
565 }
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200566 free(dirs->entry);
Richard Genoude405fc42020-11-03 12:11:05 +0100567 dirs->entry = NULL;
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200568
569 ret = sqfs_search_dir(dirs, sym_tokens, token_count,
570 m_list, m_count);
Richard Genoud2762f652020-11-03 12:11:06 +0100571 goto out;
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200572 } else if (!sqfs_is_dir(get_unaligned_le16(&dir->inode_type))) {
573 printf("** Cannot find directory. **\n");
574 free(dirs->entry);
Richard Genoude405fc42020-11-03 12:11:05 +0100575 dirs->entry = NULL;
Richard Genoud2762f652020-11-03 12:11:06 +0100576 ret = -EINVAL;
577 goto out;
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200578 }
579
580 /* Check if it is an extended dir. */
581 if (get_unaligned_le16(&dir->inode_type) == SQFS_LDIR_TYPE)
582 ldir = (struct squashfs_ldir_inode *)table;
583
584 /* Get dir. offset into the directory table */
585 offset = sqfs_dir_offset(table, m_list, m_count);
586 dirs->table = &dirs->dir_table[offset];
587
588 /* Copy directory header */
589 memcpy(dirs->dir_header, &dirs->dir_table[offset],
590 SQFS_DIR_HEADER_SIZE);
591
592 /* Check for empty directory */
593 if (sqfs_is_empty_dir(table)) {
594 printf("Empty directory.\n");
595 free(dirs->entry);
Richard Genoude405fc42020-11-03 12:11:05 +0100596 dirs->entry = NULL;
Richard Genoud2762f652020-11-03 12:11:06 +0100597 ret = SQFS_EMPTY_DIR;
598 goto out;
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200599 }
600
601 dirs->table += SQFS_DIR_HEADER_SIZE;
602 dirs->size = get_unaligned_le16(&dir->file_size);
603 dirs->entry_count = dirs->dir_header->count + 1;
604 dirs->size -= SQFS_DIR_HEADER_SIZE;
605 free(dirs->entry);
Richard Genoude405fc42020-11-03 12:11:05 +0100606 dirs->entry = NULL;
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200607 }
608
609 offset = sqfs_dir_offset(table, m_list, m_count);
610 dirs->table = &dirs->dir_table[offset];
611
612 if (get_unaligned_le16(&dir->inode_type) == SQFS_DIR_TYPE)
613 memcpy(&dirs->i_dir, dir, sizeof(*dir));
614 else
615 memcpy(&dirs->i_ldir, ldir, sizeof(*ldir));
616
Richard Genoud2762f652020-11-03 12:11:06 +0100617out:
618 free(res);
619 free(rem);
620 free(path);
621 free(target);
622 free(sym_tokens);
623 return ret;
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200624}
625
626/*
627 * Inode and directory tables are stored as a series of metadata blocks, and
628 * given the compressed size of this table, we can calculate how much metadata
629 * blocks are needed to store the result of the decompression, since a
630 * decompressed metadata block should have a size of 8KiB.
631 */
632static int sqfs_count_metablks(void *table, u32 offset, int table_size)
633{
634 int count = 0, cur_size = 0, ret;
635 u32 data_size;
636 bool comp;
637
638 do {
639 ret = sqfs_read_metablock(table, offset + cur_size, &comp,
640 &data_size);
641 if (ret)
642 return -EINVAL;
643 cur_size += data_size + SQFS_HEADER_SIZE;
644 count++;
645 } while (cur_size < table_size);
646
647 return count;
648}
649
650/*
651 * Storing the metadata blocks header's positions will be useful while looking
652 * for an entry in the directory table, using the reference (index and offset)
653 * given by its inode.
654 */
655static int sqfs_get_metablk_pos(u32 *pos_list, void *table, u32 offset,
656 int metablks_count)
657{
658 u32 data_size, cur_size = 0;
659 int j, ret = 0;
660 bool comp;
661
662 if (!metablks_count)
663 return -EINVAL;
664
665 for (j = 0; j < metablks_count; j++) {
666 ret = sqfs_read_metablock(table, offset + cur_size, &comp,
667 &data_size);
668 if (ret)
669 return -EINVAL;
670
671 cur_size += data_size + SQFS_HEADER_SIZE;
672 pos_list[j] = cur_size;
673 }
674
675 return ret;
676}
677
678static int sqfs_read_inode_table(unsigned char **inode_table)
679{
680 struct squashfs_super_block *sblk = ctxt.sblk;
681 u64 start, n_blks, table_offset, table_size;
Joao Marcos Costab87fd012020-08-18 17:17:22 +0200682 int j, ret = 0, metablks_count;
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200683 unsigned char *src_table, *itb;
684 u32 src_len, dest_offset = 0;
Joao Marcos Costab7fb3c32020-08-19 18:28:41 +0200685 unsigned long dest_len = 0;
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200686 bool compressed;
687
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200688 table_size = get_unaligned_le64(&sblk->directory_table_start) -
689 get_unaligned_le64(&sblk->inode_table_start);
690 start = get_unaligned_le64(&sblk->inode_table_start) /
691 ctxt.cur_dev->blksz;
692 n_blks = sqfs_calc_n_blks(sblk->inode_table_start,
693 sblk->directory_table_start, &table_offset);
694
695 /* Allocate a proper sized buffer (itb) to store the inode table */
696 itb = malloc_cache_aligned(n_blks * ctxt.cur_dev->blksz);
697 if (!itb)
698 return -ENOMEM;
699
700 if (sqfs_disk_read(start, n_blks, itb) < 0) {
701 ret = -EINVAL;
702 goto free_itb;
703 }
704
705 /* Parse inode table (metadata block) header */
706 ret = sqfs_read_metablock(itb, table_offset, &compressed, &src_len);
707 if (ret) {
708 ret = -EINVAL;
709 goto free_itb;
710 }
711
712 /* Calculate size to store the whole decompressed table */
713 metablks_count = sqfs_count_metablks(itb, table_offset, table_size);
714 if (metablks_count < 1) {
715 ret = -EINVAL;
716 goto free_itb;
717 }
718
719 *inode_table = malloc(metablks_count * SQFS_METADATA_BLOCK_SIZE);
720 if (!*inode_table) {
721 ret = -ENOMEM;
722 goto free_itb;
723 }
724
725 src_table = itb + table_offset + SQFS_HEADER_SIZE;
726
727 /* Extract compressed Inode table */
728 for (j = 0; j < metablks_count; j++) {
729 sqfs_read_metablock(itb, table_offset, &compressed, &src_len);
730 if (compressed) {
731 dest_len = SQFS_METADATA_BLOCK_SIZE;
Joao Marcos Costab87fd012020-08-18 17:17:22 +0200732 ret = sqfs_decompress(&ctxt, *inode_table +
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200733 dest_offset, &dest_len,
734 src_table, src_len);
735 if (ret) {
736 free(*inode_table);
Richard Genouda62528d2020-11-03 12:11:07 +0100737 *inode_table = NULL;
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200738 goto free_itb;
739 }
740
Joao Marcos Costab7fb3c32020-08-19 18:28:41 +0200741 dest_offset += dest_len;
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200742 } else {
743 memcpy(*inode_table + (j * SQFS_METADATA_BLOCK_SIZE),
744 src_table, src_len);
745 }
746
747 /*
748 * Offsets to the decompression destination, to the metadata
749 * buffer 'itb' and to the decompression source, respectively.
750 */
Joao Marcos Costab7fb3c32020-08-19 18:28:41 +0200751
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200752 table_offset += src_len + SQFS_HEADER_SIZE;
753 src_table += src_len + SQFS_HEADER_SIZE;
754 }
755
756free_itb:
757 free(itb);
758
759 return ret;
760}
761
762static int sqfs_read_directory_table(unsigned char **dir_table, u32 **pos_list)
763{
764 u64 start, n_blks, table_offset, table_size;
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200765 struct squashfs_super_block *sblk = ctxt.sblk;
Joao Marcos Costab87fd012020-08-18 17:17:22 +0200766 int j, ret = 0, metablks_count = -1;
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200767 unsigned char *src_table, *dtb;
768 u32 src_len, dest_offset = 0;
Joao Marcos Costab7fb3c32020-08-19 18:28:41 +0200769 unsigned long dest_len = 0;
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200770 bool compressed;
771
Richard Genoud2f9c9852020-11-03 12:11:04 +0100772 *dir_table = NULL;
773 *pos_list = NULL;
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200774 /* DIRECTORY TABLE */
775 table_size = get_unaligned_le64(&sblk->fragment_table_start) -
776 get_unaligned_le64(&sblk->directory_table_start);
777 start = get_unaligned_le64(&sblk->directory_table_start) /
778 ctxt.cur_dev->blksz;
779 n_blks = sqfs_calc_n_blks(sblk->directory_table_start,
780 sblk->fragment_table_start, &table_offset);
781
782 /* Allocate a proper sized buffer (dtb) to store the directory table */
783 dtb = malloc_cache_aligned(n_blks * ctxt.cur_dev->blksz);
784 if (!dtb)
785 return -ENOMEM;
786
787 if (sqfs_disk_read(start, n_blks, dtb) < 0)
Richard Genoud2f9c9852020-11-03 12:11:04 +0100788 goto out;
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200789
790 /* Parse directory table (metadata block) header */
791 ret = sqfs_read_metablock(dtb, table_offset, &compressed, &src_len);
792 if (ret)
Richard Genoud2f9c9852020-11-03 12:11:04 +0100793 goto out;
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200794
795 /* Calculate total size to store the whole decompressed table */
796 metablks_count = sqfs_count_metablks(dtb, table_offset, table_size);
797 if (metablks_count < 1)
Richard Genoud2f9c9852020-11-03 12:11:04 +0100798 goto out;
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200799
800 *dir_table = malloc(metablks_count * SQFS_METADATA_BLOCK_SIZE);
801 if (!*dir_table)
Richard Genoud2f9c9852020-11-03 12:11:04 +0100802 goto out;
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200803
804 *pos_list = malloc(metablks_count * sizeof(u32));
Richard Genoud2f9c9852020-11-03 12:11:04 +0100805 if (!*pos_list)
806 goto out;
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200807
808 ret = sqfs_get_metablk_pos(*pos_list, dtb, table_offset,
809 metablks_count);
810 if (ret) {
811 metablks_count = -1;
Richard Genoud2f9c9852020-11-03 12:11:04 +0100812 goto out;
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200813 }
814
815 src_table = dtb + table_offset + SQFS_HEADER_SIZE;
816
817 /* Extract compressed Directory table */
818 dest_offset = 0;
819 for (j = 0; j < metablks_count; j++) {
820 sqfs_read_metablock(dtb, table_offset, &compressed, &src_len);
821 if (compressed) {
822 dest_len = SQFS_METADATA_BLOCK_SIZE;
Joao Marcos Costab87fd012020-08-18 17:17:22 +0200823 ret = sqfs_decompress(&ctxt, *dir_table +
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200824 (j * SQFS_METADATA_BLOCK_SIZE),
825 &dest_len, src_table, src_len);
826 if (ret) {
827 metablks_count = -1;
Richard Genoud2f9c9852020-11-03 12:11:04 +0100828 goto out;
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200829 }
830
831 if (dest_len < SQFS_METADATA_BLOCK_SIZE) {
832 dest_offset += dest_len;
833 break;
834 }
Joao Marcos Costab7fb3c32020-08-19 18:28:41 +0200835
836 dest_offset += dest_len;
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200837 } else {
838 memcpy(*dir_table + (j * SQFS_METADATA_BLOCK_SIZE),
839 src_table, src_len);
840 }
841
842 /*
843 * Offsets to the decompression destination, to the metadata
844 * buffer 'dtb' and to the decompression source, respectively.
845 */
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200846 table_offset += src_len + SQFS_HEADER_SIZE;
847 src_table += src_len + SQFS_HEADER_SIZE;
848 }
849
Richard Genoud2f9c9852020-11-03 12:11:04 +0100850out:
851 if (metablks_count < 1) {
852 free(*dir_table);
853 free(*pos_list);
854 *dir_table = NULL;
855 *pos_list = NULL;
856 }
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200857 free(dtb);
858
859 return metablks_count;
860}
861
862int sqfs_opendir(const char *filename, struct fs_dir_stream **dirsp)
863{
864 unsigned char *inode_table = NULL, *dir_table = NULL;
Richard Genoud32bea5b2020-11-03 12:11:01 +0100865 int j, token_count = 0, ret = 0, metablks_count;
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200866 struct squashfs_dir_stream *dirs;
Richard Genoud32bea5b2020-11-03 12:11:01 +0100867 char **token_list = NULL, *path = NULL;
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200868 u32 *pos_list = NULL;
869
870 dirs = malloc(sizeof(*dirs));
871 if (!dirs)
872 return -EINVAL;
873
Richard Genoud557f08f2020-11-03 12:11:00 +0100874 /* these should be set to NULL to prevent dangling pointers */
875 dirs->dir_header = NULL;
876 dirs->entry = NULL;
877 dirs->table = NULL;
878 dirs->inode_table = NULL;
879 dirs->dir_table = NULL;
880
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200881 ret = sqfs_read_inode_table(&inode_table);
Richard Genoud557f08f2020-11-03 12:11:00 +0100882 if (ret) {
883 ret = -EINVAL;
Richard Genoud32bea5b2020-11-03 12:11:01 +0100884 goto out;
Richard Genoud557f08f2020-11-03 12:11:00 +0100885 }
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200886
887 metablks_count = sqfs_read_directory_table(&dir_table, &pos_list);
Richard Genoud557f08f2020-11-03 12:11:00 +0100888 if (metablks_count < 1) {
889 ret = -EINVAL;
Richard Genoud32bea5b2020-11-03 12:11:01 +0100890 goto out;
Richard Genoud557f08f2020-11-03 12:11:00 +0100891 }
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200892
893 /* Tokenize filename */
894 token_count = sqfs_count_tokens(filename);
Richard Genoud557f08f2020-11-03 12:11:00 +0100895 if (token_count < 0) {
896 ret = -EINVAL;
Richard Genoud32bea5b2020-11-03 12:11:01 +0100897 goto out;
Richard Genoud557f08f2020-11-03 12:11:00 +0100898 }
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200899
900 path = strdup(filename);
Richard Genoud557f08f2020-11-03 12:11:00 +0100901 if (!path) {
902 ret = -EINVAL;
Richard Genoud32bea5b2020-11-03 12:11:01 +0100903 goto out;
Richard Genoud557f08f2020-11-03 12:11:00 +0100904 }
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200905
906 token_list = malloc(token_count * sizeof(char *));
907 if (!token_list) {
908 ret = -EINVAL;
Richard Genoud32bea5b2020-11-03 12:11:01 +0100909 goto out;
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200910 }
911
912 /* Fill tokens list */
913 ret = sqfs_tokenize(token_list, token_count, path);
914 if (ret)
Richard Genoud32bea5b2020-11-03 12:11:01 +0100915 goto out;
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200916 /*
917 * ldir's (extended directory) size is greater than dir, so it works as
918 * a general solution for the malloc size, since 'i' is a union.
919 */
920 dirs->inode_table = inode_table;
921 dirs->dir_table = dir_table;
922 ret = sqfs_search_dir(dirs, token_list, token_count, pos_list,
923 metablks_count);
924 if (ret)
Richard Genoud32bea5b2020-11-03 12:11:01 +0100925 goto out;
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200926
927 if (le16_to_cpu(dirs->i_dir.inode_type) == SQFS_DIR_TYPE)
928 dirs->size = le16_to_cpu(dirs->i_dir.file_size);
929 else
930 dirs->size = le32_to_cpu(dirs->i_ldir.file_size);
931
932 /* Setup directory header */
933 memcpy(dirs->dir_header, dirs->table, SQFS_DIR_HEADER_SIZE);
934 dirs->entry_count = dirs->dir_header->count + 1;
935 dirs->size -= SQFS_DIR_HEADER_SIZE;
936
937 /* Setup entry */
938 dirs->entry = NULL;
939 dirs->table += SQFS_DIR_HEADER_SIZE;
940
941 *dirsp = (struct fs_dir_stream *)dirs;
942
Richard Genoud32bea5b2020-11-03 12:11:01 +0100943out:
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200944 for (j = 0; j < token_count; j++)
945 free(token_list[j]);
946 free(token_list);
947 free(pos_list);
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200948 free(path);
Richard Genoud32bea5b2020-11-03 12:11:01 +0100949 if (ret) {
Richard Genoud557f08f2020-11-03 12:11:00 +0100950 free(inode_table);
Richard Genoud557f08f2020-11-03 12:11:00 +0100951 free(dirs);
Richard Genoud32bea5b2020-11-03 12:11:01 +0100952 }
Joao Marcos Costa29da3742020-07-30 15:33:47 +0200953
954 return ret;
955}
956
957int sqfs_readdir(struct fs_dir_stream *fs_dirs, struct fs_dirent **dentp)
958{
959 struct squashfs_super_block *sblk = ctxt.sblk;
960 struct squashfs_dir_stream *dirs;
961 struct squashfs_lreg_inode *lreg;
962 struct squashfs_base_inode *base;
963 struct squashfs_reg_inode *reg;
964 int i_number, offset = 0, ret;
965 struct fs_dirent *dent;
966 unsigned char *ipos;
967
968 dirs = (struct squashfs_dir_stream *)fs_dirs;
969 if (!dirs->size) {
970 *dentp = NULL;
971 return -SQFS_STOP_READDIR;
972 }
973
974 dent = &dirs->dentp;
975
976 if (!dirs->entry_count) {
977 if (dirs->size > SQFS_DIR_HEADER_SIZE) {
978 dirs->size -= SQFS_DIR_HEADER_SIZE;
979 } else {
980 *dentp = NULL;
981 dirs->size = 0;
982 return -SQFS_STOP_READDIR;
983 }
984
985 if (dirs->size > SQFS_EMPTY_FILE_SIZE) {
986 /* Read follow-up (emitted) dir. header */
987 memcpy(dirs->dir_header, dirs->table,
988 SQFS_DIR_HEADER_SIZE);
989 dirs->entry_count = dirs->dir_header->count + 1;
990 ret = sqfs_read_entry(&dirs->entry, dirs->table +
991 SQFS_DIR_HEADER_SIZE);
992 if (ret)
993 return -SQFS_STOP_READDIR;
994
995 dirs->table += SQFS_DIR_HEADER_SIZE;
996 }
997 } else {
998 ret = sqfs_read_entry(&dirs->entry, dirs->table);
999 if (ret)
1000 return -SQFS_STOP_READDIR;
1001 }
1002
1003 i_number = dirs->dir_header->inode_number + dirs->entry->inode_offset;
1004 ipos = sqfs_find_inode(dirs->inode_table, i_number, sblk->inodes,
1005 sblk->block_size);
1006
1007 base = (struct squashfs_base_inode *)ipos;
1008
1009 /* Set entry type and size */
1010 switch (dirs->entry->type) {
1011 case SQFS_DIR_TYPE:
1012 case SQFS_LDIR_TYPE:
1013 dent->type = FS_DT_DIR;
1014 break;
1015 case SQFS_REG_TYPE:
1016 case SQFS_LREG_TYPE:
1017 /*
1018 * Entries do not differentiate extended from regular types, so
1019 * it needs to be verified manually.
1020 */
1021 if (get_unaligned_le16(&base->inode_type) == SQFS_LREG_TYPE) {
1022 lreg = (struct squashfs_lreg_inode *)ipos;
1023 dent->size = get_unaligned_le64(&lreg->file_size);
1024 } else {
1025 reg = (struct squashfs_reg_inode *)ipos;
1026 dent->size = get_unaligned_le32(&reg->file_size);
1027 }
1028
1029 dent->type = FS_DT_REG;
1030 break;
1031 case SQFS_BLKDEV_TYPE:
1032 case SQFS_CHRDEV_TYPE:
1033 case SQFS_LBLKDEV_TYPE:
1034 case SQFS_LCHRDEV_TYPE:
1035 case SQFS_FIFO_TYPE:
1036 case SQFS_SOCKET_TYPE:
1037 case SQFS_LFIFO_TYPE:
1038 case SQFS_LSOCKET_TYPE:
1039 dent->type = SQFS_MISC_ENTRY_TYPE;
1040 break;
1041 case SQFS_SYMLINK_TYPE:
1042 case SQFS_LSYMLINK_TYPE:
1043 dent->type = FS_DT_LNK;
1044 break;
1045 default:
1046 return -SQFS_STOP_READDIR;
1047 }
1048
1049 /* Set entry name */
1050 strncpy(dent->name, dirs->entry->name, dirs->entry->name_size + 1);
1051 dent->name[dirs->entry->name_size + 1] = '\0';
1052
1053 offset = dirs->entry->name_size + 1 + SQFS_ENTRY_BASE_LENGTH;
1054 dirs->entry_count--;
1055
1056 /* Decrement size to be read */
1057 if (dirs->size > offset)
1058 dirs->size -= offset;
1059 else
1060 dirs->size = 0;
1061
1062 /* Keep a reference to the current entry before incrementing it */
1063 dirs->table += offset;
1064
1065 *dentp = dent;
1066
1067 return 0;
1068}
1069
1070int sqfs_probe(struct blk_desc *fs_dev_desc, struct disk_partition *fs_partition)
1071{
1072 struct squashfs_super_block *sblk;
1073 int ret;
1074
1075 ctxt.cur_dev = fs_dev_desc;
1076 ctxt.cur_part_info = *fs_partition;
1077
1078 ret = sqfs_read_sblk(&sblk);
1079 if (ret)
1080 return ret;
1081
1082 /* Make sure it has a valid SquashFS magic number*/
1083 if (get_unaligned_le32(&sblk->s_magic) != SQFS_MAGIC_NUMBER) {
1084 printf("Bad magic number for SquashFS image.\n");
1085 ctxt.cur_dev = NULL;
1086 return -EINVAL;
1087 }
1088
1089 ctxt.sblk = sblk;
1090
Joao Marcos Costa877576c2020-08-18 17:17:21 +02001091 ret = sqfs_decompressor_init(&ctxt);
1092
1093 if (ret) {
1094 ctxt.cur_dev = NULL;
1095 free(ctxt.sblk);
1096 return -EINVAL;
1097 }
1098
Joao Marcos Costa29da3742020-07-30 15:33:47 +02001099 return 0;
1100}
1101
1102static char *sqfs_basename(char *path)
1103{
1104 char *fname;
1105
1106 fname = path + strlen(path) - 1;
1107 while (fname >= path) {
1108 if (*fname == '/') {
1109 fname++;
1110 break;
1111 }
1112
1113 fname--;
1114 }
1115
1116 return fname;
1117}
1118
1119static char *sqfs_dirname(char *path)
1120{
1121 char *fname;
1122
1123 fname = sqfs_basename(path);
1124 --fname;
1125 *fname = '\0';
1126
1127 return path;
1128}
1129
1130/*
1131 * Takes a path to file and splits it in two parts: the filename itself and the
1132 * directory's path, e.g.:
1133 * path: /path/to/file.txt
1134 * file: file.txt
1135 * dir: /path/to
1136 */
1137static int sqfs_split_path(char **file, char **dir, const char *path)
1138{
1139 char *dirc, *basec, *bname, *dname, *tmp_path;
1140 int ret = 0;
1141
Richard Genoud8cf2f022020-11-03 12:11:03 +01001142 *file = NULL;
1143 *dir = NULL;
1144 dirc = NULL;
1145 basec = NULL;
1146 bname = NULL;
1147 dname = NULL;
1148 tmp_path = NULL;
1149
Joao Marcos Costa29da3742020-07-30 15:33:47 +02001150 /* check for first slash in path*/
1151 if (path[0] == '/') {
1152 tmp_path = strdup(path);
Richard Genoud8cf2f022020-11-03 12:11:03 +01001153 if (!tmp_path) {
1154 ret = -ENOMEM;
1155 goto out;
1156 }
Joao Marcos Costa29da3742020-07-30 15:33:47 +02001157 } else {
1158 tmp_path = malloc(strlen(path) + 2);
Richard Genoud8cf2f022020-11-03 12:11:03 +01001159 if (!tmp_path) {
1160 ret = -ENOMEM;
1161 goto out;
1162 }
Joao Marcos Costa29da3742020-07-30 15:33:47 +02001163 tmp_path[0] = '/';
1164 strcpy(tmp_path + 1, path);
1165 }
1166
1167 /* String duplicates */
1168 dirc = strdup(tmp_path);
1169 if (!dirc) {
1170 ret = -ENOMEM;
Richard Genoud8cf2f022020-11-03 12:11:03 +01001171 goto out;
Joao Marcos Costa29da3742020-07-30 15:33:47 +02001172 }
1173
1174 basec = strdup(tmp_path);
1175 if (!basec) {
1176 ret = -ENOMEM;
Richard Genoud8cf2f022020-11-03 12:11:03 +01001177 goto out;
Joao Marcos Costa29da3742020-07-30 15:33:47 +02001178 }
1179
1180 dname = sqfs_dirname(dirc);
1181 bname = sqfs_basename(basec);
1182
1183 *file = strdup(bname);
1184
1185 if (!*file) {
1186 ret = -ENOMEM;
Richard Genoud8cf2f022020-11-03 12:11:03 +01001187 goto out;
Joao Marcos Costa29da3742020-07-30 15:33:47 +02001188 }
1189
1190 if (*dname == '\0') {
1191 *dir = malloc(2);
1192 if (!*dir) {
1193 ret = -ENOMEM;
Richard Genoud8cf2f022020-11-03 12:11:03 +01001194 goto out;
Joao Marcos Costa29da3742020-07-30 15:33:47 +02001195 }
1196
1197 (*dir)[0] = '/';
1198 (*dir)[1] = '\0';
1199 } else {
1200 *dir = strdup(dname);
1201 if (!*dir) {
1202 ret = -ENOMEM;
Richard Genoud8cf2f022020-11-03 12:11:03 +01001203 goto out;
Joao Marcos Costa29da3742020-07-30 15:33:47 +02001204 }
1205 }
1206
Richard Genoud8cf2f022020-11-03 12:11:03 +01001207out:
1208 if (ret) {
1209 free(*file);
1210 free(*dir);
1211 *dir = NULL;
1212 *file = NULL;
1213 }
Joao Marcos Costa29da3742020-07-30 15:33:47 +02001214 free(basec);
Joao Marcos Costa29da3742020-07-30 15:33:47 +02001215 free(dirc);
Joao Marcos Costa29da3742020-07-30 15:33:47 +02001216 free(tmp_path);
1217
1218 return ret;
1219}
1220
1221static int sqfs_get_regfile_info(struct squashfs_reg_inode *reg,
1222 struct squashfs_file_info *finfo,
1223 struct squashfs_fragment_block_entry *fentry,
1224 __le32 blksz)
1225{
1226 int datablk_count = 0, ret;
1227
1228 finfo->size = get_unaligned_le32(&reg->file_size);
1229 finfo->offset = get_unaligned_le32(&reg->offset);
1230 finfo->start = get_unaligned_le32(&reg->start_block);
1231 finfo->frag = SQFS_IS_FRAGMENTED(get_unaligned_le32(&reg->fragment));
1232
Joao Marcos Costaf1dfe4a2020-09-11 12:21:06 +02001233 if (finfo->frag && finfo->offset == 0xFFFFFFFF)
Joao Marcos Costab7fb3c32020-08-19 18:28:41 +02001234 return -EINVAL;
1235
Joao Marcos Costaf1dfe4a2020-09-11 12:21:06 +02001236 if (finfo->size < 1 || finfo->start == 0xFFFFFFFF)
1237 return -EINVAL;
1238
Joao Marcos Costa29da3742020-07-30 15:33:47 +02001239 if (finfo->frag) {
1240 datablk_count = finfo->size / le32_to_cpu(blksz);
1241 ret = sqfs_frag_lookup(get_unaligned_le32(&reg->fragment),
1242 fentry);
1243 if (ret < 0)
1244 return -EINVAL;
1245 finfo->comp = true;
Joao Marcos Costaf1dfe4a2020-09-11 12:21:06 +02001246 if (fentry->size < 1 || fentry->start == 0x7FFFFFFF)
Joao Marcos Costab7fb3c32020-08-19 18:28:41 +02001247 return -EINVAL;
Joao Marcos Costa29da3742020-07-30 15:33:47 +02001248 } else {
1249 datablk_count = DIV_ROUND_UP(finfo->size, le32_to_cpu(blksz));
1250 }
1251
1252 finfo->blk_sizes = malloc(datablk_count * sizeof(u32));
1253 if (!finfo->blk_sizes)
1254 return -ENOMEM;
1255
1256 return datablk_count;
1257}
1258
1259static int sqfs_get_lregfile_info(struct squashfs_lreg_inode *lreg,
1260 struct squashfs_file_info *finfo,
1261 struct squashfs_fragment_block_entry *fentry,
1262 __le32 blksz)
1263{
1264 int datablk_count = 0, ret;
1265
1266 finfo->size = get_unaligned_le64(&lreg->file_size);
1267 finfo->offset = get_unaligned_le32(&lreg->offset);
1268 finfo->start = get_unaligned_le64(&lreg->start_block);
1269 finfo->frag = SQFS_IS_FRAGMENTED(get_unaligned_le32(&lreg->fragment));
1270
Joao Marcos Costaf1dfe4a2020-09-11 12:21:06 +02001271 if (finfo->frag && finfo->offset == 0xFFFFFFFF)
Joao Marcos Costab7fb3c32020-08-19 18:28:41 +02001272 return -EINVAL;
1273
Joao Marcos Costaf1dfe4a2020-09-11 12:21:06 +02001274 if (finfo->size < 1 || finfo->start == 0x7FFFFFFF)
1275 return -EINVAL;
1276
Joao Marcos Costa29da3742020-07-30 15:33:47 +02001277 if (finfo->frag) {
1278 datablk_count = finfo->size / le32_to_cpu(blksz);
1279 ret = sqfs_frag_lookup(get_unaligned_le32(&lreg->fragment),
1280 fentry);
1281 if (ret < 0)
1282 return -EINVAL;
1283 finfo->comp = true;
Joao Marcos Costaf1dfe4a2020-09-11 12:21:06 +02001284 if (fentry->size < 1 || fentry->start == 0x7FFFFFFF)
Joao Marcos Costab7fb3c32020-08-19 18:28:41 +02001285 return -EINVAL;
Joao Marcos Costa29da3742020-07-30 15:33:47 +02001286 } else {
1287 datablk_count = DIV_ROUND_UP(finfo->size, le32_to_cpu(blksz));
1288 }
1289
1290 finfo->blk_sizes = malloc(datablk_count * sizeof(u32));
1291 if (!finfo->blk_sizes)
1292 return -ENOMEM;
1293
1294 return datablk_count;
1295}
1296
1297int sqfs_read(const char *filename, void *buf, loff_t offset, loff_t len,
1298 loff_t *actread)
1299{
1300 char *dir, *fragment_block, *datablock = NULL, *data_buffer = NULL;
1301 char *fragment, *file, *resolved, *data;
1302 u64 start, n_blks, table_size, data_offset, table_offset;
Joao Marcos Costab87fd012020-08-18 17:17:22 +02001303 int ret, j, i_number, datablk_count = 0;
Joao Marcos Costa29da3742020-07-30 15:33:47 +02001304 struct squashfs_super_block *sblk = ctxt.sblk;
1305 struct squashfs_fragment_block_entry frag_entry;
1306 struct squashfs_file_info finfo = {0};
1307 struct squashfs_symlink_inode *symlink;
1308 struct fs_dir_stream *dirsp = NULL;
1309 struct squashfs_dir_stream *dirs;
1310 struct squashfs_lreg_inode *lreg;
1311 struct squashfs_base_inode *base;
1312 struct squashfs_reg_inode *reg;
1313 unsigned long dest_len;
1314 struct fs_dirent *dent;
1315 unsigned char *ipos;
1316
1317 *actread = 0;
1318
Joao Marcos Costa29da3742020-07-30 15:33:47 +02001319 /*
1320 * sqfs_opendir will uncompress inode and directory tables, and will
1321 * return a pointer to the directory that contains the requested file.
1322 */
1323 sqfs_split_path(&file, &dir, filename);
1324 ret = sqfs_opendir(dir, &dirsp);
1325 if (ret) {
Joao Marcos Costa29da3742020-07-30 15:33:47 +02001326 goto free_paths;
1327 }
1328
1329 dirs = (struct squashfs_dir_stream *)dirsp;
1330
1331 /* For now, only regular files are able to be loaded */
1332 while (!sqfs_readdir(dirsp, &dent)) {
1333 ret = strcmp(dent->name, file);
1334 if (!ret)
1335 break;
1336
1337 free(dirs->entry);
Richard Genoud0b8aa992020-11-03 12:11:11 +01001338 dirs->entry = NULL;
Joao Marcos Costa29da3742020-07-30 15:33:47 +02001339 }
1340
1341 if (ret) {
1342 printf("File not found.\n");
1343 *actread = 0;
Joao Marcos Costa29da3742020-07-30 15:33:47 +02001344 ret = -ENOENT;
1345 goto free_paths;
1346 }
1347
1348 i_number = dirs->dir_header->inode_number + dirs->entry->inode_offset;
1349 ipos = sqfs_find_inode(dirs->inode_table, i_number, sblk->inodes,
1350 sblk->block_size);
1351
1352 base = (struct squashfs_base_inode *)ipos;
1353 switch (get_unaligned_le16(&base->inode_type)) {
1354 case SQFS_REG_TYPE:
1355 reg = (struct squashfs_reg_inode *)ipos;
1356 datablk_count = sqfs_get_regfile_info(reg, &finfo, &frag_entry,
1357 sblk->block_size);
1358 if (datablk_count < 0) {
1359 ret = -EINVAL;
1360 goto free_paths;
1361 }
1362
1363 memcpy(finfo.blk_sizes, ipos + sizeof(*reg),
1364 datablk_count * sizeof(u32));
1365 break;
1366 case SQFS_LREG_TYPE:
1367 lreg = (struct squashfs_lreg_inode *)ipos;
1368 datablk_count = sqfs_get_lregfile_info(lreg, &finfo,
1369 &frag_entry,
1370 sblk->block_size);
1371 if (datablk_count < 0) {
1372 ret = -EINVAL;
1373 goto free_paths;
1374 }
1375
1376 memcpy(finfo.blk_sizes, ipos + sizeof(*lreg),
1377 datablk_count * sizeof(u32));
1378 break;
1379 case SQFS_SYMLINK_TYPE:
1380 case SQFS_LSYMLINK_TYPE:
1381 symlink = (struct squashfs_symlink_inode *)ipos;
1382 resolved = sqfs_resolve_symlink(symlink, filename);
1383 ret = sqfs_read(resolved, buf, offset, len, actread);
1384 free(resolved);
1385 goto free_paths;
1386 case SQFS_BLKDEV_TYPE:
1387 case SQFS_CHRDEV_TYPE:
1388 case SQFS_LBLKDEV_TYPE:
1389 case SQFS_LCHRDEV_TYPE:
1390 case SQFS_FIFO_TYPE:
1391 case SQFS_SOCKET_TYPE:
1392 case SQFS_LFIFO_TYPE:
1393 case SQFS_LSOCKET_TYPE:
1394 default:
1395 printf("Unsupported entry type\n");
1396 ret = -EINVAL;
1397 goto free_paths;
1398 }
1399
1400 /* If the user specifies a length, check its sanity */
1401 if (len) {
1402 if (len > finfo.size) {
1403 ret = -EINVAL;
1404 goto free_paths;
1405 }
1406
1407 finfo.size = len;
1408 }
1409
1410 if (datablk_count) {
1411 data_offset = finfo.start;
1412 datablock = malloc(get_unaligned_le32(&sblk->block_size));
1413 if (!datablock) {
1414 ret = -ENOMEM;
1415 goto free_paths;
1416 }
1417 }
1418
1419 for (j = 0; j < datablk_count; j++) {
1420 start = data_offset / ctxt.cur_dev->blksz;
1421 table_size = SQFS_BLOCK_SIZE(finfo.blk_sizes[j]);
1422 table_offset = data_offset - (start * ctxt.cur_dev->blksz);
1423 n_blks = DIV_ROUND_UP(table_size + table_offset,
1424 ctxt.cur_dev->blksz);
1425
1426 data_buffer = malloc_cache_aligned(n_blks * ctxt.cur_dev->blksz);
1427
1428 if (!data_buffer) {
1429 ret = -ENOMEM;
1430 goto free_datablk;
1431 }
1432
1433 ret = sqfs_disk_read(start, n_blks, data_buffer);
1434 if (ret < 0) {
1435 /*
1436 * Possible causes: too many data blocks or too large
1437 * SquashFS block size. Tip: re-compile the SquashFS
1438 * image with mksquashfs's -b <block_size> option.
1439 */
1440 printf("Error: too many data blocks to be read.\n");
1441 goto free_buffer;
1442 }
1443
1444 data = data_buffer + table_offset;
1445
1446 /* Load the data */
1447 if (SQFS_COMPRESSED_BLOCK(finfo.blk_sizes[j])) {
1448 dest_len = get_unaligned_le32(&sblk->block_size);
Joao Marcos Costab87fd012020-08-18 17:17:22 +02001449 ret = sqfs_decompress(&ctxt, datablock, &dest_len,
Joao Marcos Costa29da3742020-07-30 15:33:47 +02001450 data, table_size);
1451 if (ret)
1452 goto free_buffer;
1453
1454 memcpy(buf + offset + *actread, datablock, dest_len);
1455 *actread += dest_len;
1456 } else {
1457 memcpy(buf + offset + *actread, data, table_size);
1458 *actread += table_size;
1459 }
1460
1461 data_offset += table_size;
Richard Genoud75844652020-11-03 12:11:14 +01001462 free(data_buffer);
1463 data_buffer = NULL;
Joao Marcos Costa29da3742020-07-30 15:33:47 +02001464 }
1465
1466 free(finfo.blk_sizes);
1467
1468 /*
1469 * There is no need to continue if the file is not fragmented.
1470 */
1471 if (!finfo.frag) {
1472 ret = 0;
1473 goto free_buffer;
1474 }
1475
1476 start = frag_entry.start / ctxt.cur_dev->blksz;
1477 table_size = SQFS_BLOCK_SIZE(frag_entry.size);
1478 table_offset = frag_entry.start - (start * ctxt.cur_dev->blksz);
1479 n_blks = DIV_ROUND_UP(table_size + table_offset, ctxt.cur_dev->blksz);
1480
1481 fragment = malloc_cache_aligned(n_blks * ctxt.cur_dev->blksz);
1482
1483 if (!fragment) {
1484 ret = -ENOMEM;
1485 goto free_buffer;
1486 }
1487
1488 ret = sqfs_disk_read(start, n_blks, fragment);
1489 if (ret < 0)
1490 goto free_fragment;
1491
1492 /* File compressed and fragmented */
1493 if (finfo.frag && finfo.comp) {
1494 dest_len = get_unaligned_le32(&sblk->block_size);
1495 fragment_block = malloc(dest_len);
1496 if (!fragment_block) {
1497 ret = -ENOMEM;
1498 goto free_fragment;
1499 }
1500
Joao Marcos Costab87fd012020-08-18 17:17:22 +02001501 ret = sqfs_decompress(&ctxt, fragment_block, &dest_len,
Joao Marcos Costa29da3742020-07-30 15:33:47 +02001502 (void *)fragment + table_offset,
1503 frag_entry.size);
1504 if (ret) {
1505 free(fragment_block);
1506 goto free_fragment;
1507 }
1508
1509 for (j = offset + *actread; j < finfo.size; j++) {
1510 memcpy(buf + j, &fragment_block[finfo.offset + j], 1);
1511 (*actread)++;
1512 }
1513
1514 free(fragment_block);
1515
1516 } else if (finfo.frag && !finfo.comp) {
1517 fragment_block = (void *)fragment + table_offset;
1518
1519 for (j = offset + *actread; j < finfo.size; j++) {
1520 memcpy(buf + j, &fragment_block[finfo.offset + j], 1);
1521 (*actread)++;
1522 }
1523 }
1524
1525free_fragment:
1526 free(fragment);
1527free_buffer:
1528 if (datablk_count)
1529 free(data_buffer);
1530free_datablk:
1531 if (datablk_count)
1532 free(datablock);
1533free_paths:
1534 free(file);
1535 free(dir);
Richard Genoud84a5f572020-11-03 12:11:13 +01001536 sqfs_closedir(dirsp);
Joao Marcos Costa29da3742020-07-30 15:33:47 +02001537
1538 return ret;
1539}
1540
1541int sqfs_size(const char *filename, loff_t *size)
1542{
1543 struct squashfs_super_block *sblk = ctxt.sblk;
1544 struct squashfs_symlink_inode *symlink;
1545 struct fs_dir_stream *dirsp = NULL;
1546 struct squashfs_base_inode *base;
1547 struct squashfs_dir_stream *dirs;
1548 struct squashfs_lreg_inode *lreg;
1549 struct squashfs_reg_inode *reg;
1550 char *dir, *file, *resolved;
1551 struct fs_dirent *dent;
1552 unsigned char *ipos;
1553 int ret, i_number;
1554
1555 sqfs_split_path(&file, &dir, filename);
1556 /*
1557 * sqfs_opendir will uncompress inode and directory tables, and will
1558 * return a pointer to the directory that contains the requested file.
1559 */
1560 ret = sqfs_opendir(dir, &dirsp);
1561 if (ret) {
Joao Marcos Costa29da3742020-07-30 15:33:47 +02001562 ret = -EINVAL;
1563 goto free_strings;
1564 }
1565
1566 dirs = (struct squashfs_dir_stream *)dirsp;
1567
1568 while (!sqfs_readdir(dirsp, &dent)) {
1569 ret = strcmp(dent->name, file);
1570 if (!ret)
1571 break;
1572 free(dirs->entry);
Richard Genoudc3dadcb2020-11-03 12:11:09 +01001573 dirs->entry = NULL;
Joao Marcos Costa29da3742020-07-30 15:33:47 +02001574 }
1575
1576 if (ret) {
1577 printf("File not found.\n");
1578 *size = 0;
1579 ret = -EINVAL;
1580 goto free_strings;
1581 }
1582
1583 i_number = dirs->dir_header->inode_number + dirs->entry->inode_offset;
1584 ipos = sqfs_find_inode(dirs->inode_table, i_number, sblk->inodes,
1585 sblk->block_size);
1586 free(dirs->entry);
Richard Genoudc3dadcb2020-11-03 12:11:09 +01001587 dirs->entry = NULL;
Joao Marcos Costa29da3742020-07-30 15:33:47 +02001588
1589 base = (struct squashfs_base_inode *)ipos;
1590 switch (get_unaligned_le16(&base->inode_type)) {
1591 case SQFS_REG_TYPE:
1592 reg = (struct squashfs_reg_inode *)ipos;
1593 *size = get_unaligned_le32(&reg->file_size);
1594 break;
1595 case SQFS_LREG_TYPE:
1596 lreg = (struct squashfs_lreg_inode *)ipos;
1597 *size = get_unaligned_le64(&lreg->file_size);
1598 break;
1599 case SQFS_SYMLINK_TYPE:
1600 case SQFS_LSYMLINK_TYPE:
1601 symlink = (struct squashfs_symlink_inode *)ipos;
1602 resolved = sqfs_resolve_symlink(symlink, filename);
1603 ret = sqfs_size(resolved, size);
1604 free(resolved);
1605 break;
1606 case SQFS_BLKDEV_TYPE:
1607 case SQFS_CHRDEV_TYPE:
1608 case SQFS_LBLKDEV_TYPE:
1609 case SQFS_LCHRDEV_TYPE:
1610 case SQFS_FIFO_TYPE:
1611 case SQFS_SOCKET_TYPE:
1612 case SQFS_LFIFO_TYPE:
1613 case SQFS_LSOCKET_TYPE:
1614 default:
1615 printf("Unable to recover entry's size.\n");
1616 *size = 0;
1617 ret = -EINVAL;
1618 break;
1619 }
1620
1621free_strings:
1622 free(dir);
1623 free(file);
1624
1625 sqfs_closedir(dirsp);
1626
1627 return ret;
1628}
1629
1630void sqfs_close(void)
1631{
1632 free(ctxt.sblk);
1633 ctxt.cur_dev = NULL;
Joao Marcos Costa877576c2020-08-18 17:17:21 +02001634 sqfs_decompressor_cleanup(&ctxt);
Joao Marcos Costa29da3742020-07-30 15:33:47 +02001635}
1636
1637void sqfs_closedir(struct fs_dir_stream *dirs)
1638{
1639 struct squashfs_dir_stream *sqfs_dirs;
1640
1641 sqfs_dirs = (struct squashfs_dir_stream *)dirs;
1642 free(sqfs_dirs->inode_table);
1643 free(sqfs_dirs->dir_table);
1644 free(sqfs_dirs->dir_header);
Richard Genoud2575eb22020-11-03 12:11:02 +01001645 free(sqfs_dirs);
Joao Marcos Costa29da3742020-07-30 15:33:47 +02001646}