// SPDX-License-Identifier: GPL-2.0 /* * This file is part of UBIFS. * * Copyright (C) 2006-2008 Nokia Corporation. * * (C) Copyright 2008-2010 * Stefan Roese, DENX Software Engineering, sr@denx.de. * * Authors: Artem Bityutskiy (Битюцкий Артём) * Adrian Hunter */ #include #include #include #include #include #include #include #include "ubifs.h" #include #include #include #include #include #include DECLARE_GLOBAL_DATA_PTR; /* compress.c */ /* * We need a wrapper for zunzip() because the parameters are * incompatible with the lzo decompressor. */ static int gzip_decompress(const unsigned char *in, size_t in_len, unsigned char *out, size_t *out_len) { return zunzip(out, *out_len, (unsigned char *)in, (unsigned long *)out_len, 0, 0); } /* Fake description object for the "none" compressor */ static struct ubifs_compressor none_compr = { .compr_type = UBIFS_COMPR_NONE, .name = "none", .capi_name = "", .decompress = NULL, }; static struct ubifs_compressor lzo_compr = { .compr_type = UBIFS_COMPR_LZO, #ifndef __UBOOT__ .comp_mutex = &lzo_mutex, #endif .name = "lzo", .capi_name = "lzo", .decompress = lzo1x_decompress_safe, }; static struct ubifs_compressor zlib_compr = { .compr_type = UBIFS_COMPR_ZLIB, #ifndef __UBOOT__ .comp_mutex = &deflate_mutex, .decomp_mutex = &inflate_mutex, #endif .name = "zlib", .capi_name = "deflate", .decompress = gzip_decompress, }; /* All UBIFS compressors */ struct ubifs_compressor *ubifs_compressors[UBIFS_COMPR_TYPES_CNT]; #ifdef __UBOOT__ struct crypto_comp { int compressor; }; static inline struct crypto_comp *crypto_alloc_comp(const char *alg_name, u32 type, u32 mask) { struct ubifs_compressor *comp; struct crypto_comp *ptr; int i = 0; ptr = malloc_cache_aligned(sizeof(struct crypto_comp)); while (i < UBIFS_COMPR_TYPES_CNT) { comp = ubifs_compressors[i]; if (!comp) { i++; continue; } if (strncmp(alg_name, comp->capi_name, strlen(alg_name)) == 0) { ptr->compressor = i; return ptr; } i++; } if (i >= UBIFS_COMPR_TYPES_CNT) { dbg_gen("invalid compression type %s", alg_name); free (ptr); return NULL; } return ptr; } static inline int crypto_comp_decompress(const struct ubifs_info *c, struct crypto_comp *tfm, const u8 *src, unsigned int slen, u8 *dst, unsigned int *dlen) { struct ubifs_compressor *compr = ubifs_compressors[tfm->compressor]; int err; size_t tmp_len = *dlen; if (compr->compr_type == UBIFS_COMPR_NONE) { memcpy(dst, src, slen); *dlen = slen; return 0; } err = compr->decompress(src, slen, dst, &tmp_len); if (err) ubifs_err(c, "cannot decompress %d bytes, compressor %s, " "error %d", slen, compr->name, err); *dlen = tmp_len; return err; return 0; } /* from shrinker.c */ /* Global clean znode counter (for all mounted UBIFS instances) */ atomic_long_t ubifs_clean_zn_cnt; #endif /** * ubifs_decompress - decompress data. * @in_buf: data to decompress * @in_len: length of the data to decompress * @out_buf: output buffer where decompressed data should * @out_len: output length is returned here * @compr_type: type of compression * * This function decompresses data from buffer @in_buf into buffer @out_buf. * The length of the uncompressed data is returned in @out_len. This functions * returns %0 on success or a negative error code on failure. */ int ubifs_decompress(const struct ubifs_info *c, const void *in_buf, int in_len, void *out_buf, int *out_len, int compr_type) { int err; struct ubifs_compressor *compr; if (unlikely(compr_type < 0 || compr_type >= UBIFS_COMPR_TYPES_CNT)) { ubifs_err(c, "invalid compression type %d", compr_type); return -EINVAL; } compr = ubifs_compressors[compr_type]; if (unlikely(!compr->capi_name)) { ubifs_err(c, "%s compression is not compiled in", compr->name); return -EINVAL; } if (compr_type == UBIFS_COMPR_NONE) { memcpy(out_buf, in_buf, in_len); *out_len = in_len; return 0; } if (compr->decomp_mutex) mutex_lock(compr->decomp_mutex); err = crypto_comp_decompress(c, compr->cc, in_buf, in_len, out_buf, (unsigned int *)out_len); if (compr->decomp_mutex) mutex_unlock(compr->decomp_mutex); if (err) ubifs_err(c, "cannot decompress %d bytes, compressor %s," " error %d", in_len, compr->name, err); return err; } /** * compr_init - initialize a compressor. * @compr: compressor description object * * This function initializes the requested compressor and returns zero in case * of success or a negative error code in case of failure. */ static int __init compr_init(struct ubifs_compressor *compr) { ubifs_compressors[compr->compr_type] = compr; if (compr->capi_name) { compr->cc = crypto_alloc_comp(compr->capi_name, 0, 0); if (IS_ERR(compr->cc)) { dbg_gen("cannot initialize compressor %s," " error %ld", compr->name, PTR_ERR(compr->cc)); return PTR_ERR(compr->cc); } } return 0; } /** * ubifs_compressors_init - initialize UBIFS compressors. * * This function initializes the compressor which were compiled in. Returns * zero in case of success and a negative error code in case of failure. */ int __init ubifs_compressors_init(void) { int err; err = compr_init(&lzo_compr); if (err) return err; err = compr_init(&zlib_compr); if (err) return err; err = compr_init(&none_compr); if (err) return err; return 0; } /* * ubifsls... */ static int filldir(struct ubifs_info *c, const char *name, int namlen, u64 ino, unsigned int d_type) { struct inode *inode; char filetime[32]; switch (d_type) { case UBIFS_ITYPE_REG: printf("\t"); break; case UBIFS_ITYPE_DIR: printf("\t"); break; case UBIFS_ITYPE_LNK: printf("\t"); break; default: printf("other\t"); break; } inode = ubifs_iget(c->vfs_sb, ino); if (IS_ERR(inode)) { printf("%s: Error in ubifs_iget(), ino=%lld ret=%p!\n", __func__, ino, inode); return -1; } ctime_r((time_t *)&inode->i_mtime, filetime); printf("%9lld %24.24s ", inode->i_size, filetime); #ifndef __UBOOT__ ubifs_iput(inode); #endif printf("%s\n", name); return 0; } static int ubifs_printdir(struct file *file, void *dirent) { int err, over = 0; struct qstr nm; union ubifs_key key; struct ubifs_dent_node *dent; struct inode *dir = file->f_path.dentry->d_inode; struct ubifs_info *c = dir->i_sb->s_fs_info; dbg_gen("dir ino %lu, f_pos %#llx", dir->i_ino, file->f_pos); if (file->f_pos > UBIFS_S_KEY_HASH_MASK || file->f_pos == 2) /* * The directory was seek'ed to a senseless position or there * are no more entries. */ return 0; if (file->f_pos == 1) { /* Find the first entry in TNC and save it */ lowest_dent_key(c, &key, dir->i_ino); nm.name = NULL; dent = ubifs_tnc_next_ent(c, &key, &nm); if (IS_ERR(dent)) { err = PTR_ERR(dent); goto out; } file->f_pos = key_hash_flash(c, &dent->key); file->private_data = dent; } dent = file->private_data; if (!dent) { /* * The directory was seek'ed to and is now readdir'ed. * Find the entry corresponding to @file->f_pos or the * closest one. */ dent_key_init_hash(c, &key, dir->i_ino, file->f_pos); nm.name = NULL; dent = ubifs_tnc_next_ent(c, &key, &nm); if (IS_ERR(dent)) { err = PTR_ERR(dent); goto out; } file->f_pos = key_hash_flash(c, &dent->key); file->private_data = dent; } while (1) { dbg_gen("feed '%s', ino %llu, new f_pos %#x", dent->name, (unsigned long long)le64_to_cpu(dent->inum), key_hash_flash(c, &dent->key)); #ifndef __UBOOT__ ubifs_assert(le64_to_cpu(dent->ch.sqnum) > ubifs_inode(dir)->creat_sqnum); #endif nm.len = le16_to_cpu(dent->nlen); over = filldir(c, (char *)dent->name, nm.len, le64_to_cpu(dent->inum), dent->type); if (over) return 0; /* Switch to the next entry */ key_read(c, &dent->key, &key); nm.name = (char *)dent->name; dent = ubifs_tnc_next_ent(c, &key, &nm); if (IS_ERR(dent)) { err = PTR_ERR(dent); goto out; } kfree(file->private_data); file->f_pos = key_hash_flash(c, &dent->key); file->private_data = dent; cond_resched(); } out: if (err != -ENOENT) { ubifs_err(c, "cannot find next direntry, error %d", err); return err; } kfree(file->private_data); file->private_data = NULL; file->f_pos = 2; return 0; } static int ubifs_finddir(struct super_block *sb, char *dirname, unsigned long root_inum, unsigned long *inum) { int err; struct qstr nm; union ubifs_key key; struct ubifs_dent_node *dent; struct ubifs_info *c; struct file *file; struct dentry *dentry; struct inode *dir; int ret = 0; file = kzalloc(sizeof(struct file), 0); dentry = kzalloc(sizeof(struct dentry), 0); dir = kzalloc(sizeof(struct inode), 0); if (!file || !dentry || !dir) { printf("%s: Error, no memory for malloc!\n", __func__); err = -ENOMEM; goto out; } dir->i_sb = sb; file->f_path.dentry = dentry; file->f_path.dentry->d_parent = dentry; file->f_path.dentry->d_inode = dir; file->f_path.dentry->d_inode->i_ino = root_inum; c = sb->s_fs_info; dbg_gen("dir ino %lu, f_pos %#llx", dir->i_ino, file->f_pos); /* Find the first entry in TNC and save it */ lowest_dent_key(c, &key, dir->i_ino); nm.name = NULL; dent = ubifs_tnc_next_ent(c, &key, &nm); if (IS_ERR(dent)) { err = PTR_ERR(dent); goto out; } file->f_pos = key_hash_flash(c, &dent->key); file->private_data = dent; while (1) { dbg_gen("feed '%s', ino %llu, new f_pos %#x", dent->name, (unsigned long long)le64_to_cpu(dent->inum), key_hash_flash(c, &dent->key)); #ifndef __UBOOT__ ubifs_assert(le64_to_cpu(dent->ch.sqnum) > ubifs_inode(dir)->creat_sqnum); #endif nm.len = le16_to_cpu(dent->nlen); if ((strncmp(dirname, (char *)dent->name, nm.len) == 0) && (strlen(dirname) == nm.len)) { *inum = le64_to_cpu(dent->inum); ret = 1; goto out_free; } /* Switch to the next entry */ key_read(c, &dent->key, &key); nm.name = (char *)dent->name; dent = ubifs_tnc_next_ent(c, &key, &nm); if (IS_ERR(dent)) { err = PTR_ERR(dent); goto out; } kfree(file->private_data); file->f_pos = key_hash_flash(c, &dent->key); file->private_data = dent; cond_resched(); } out: if (err != -ENOENT) dbg_gen("cannot find next direntry, error %d", err); out_free: kfree(file->private_data); free(file); free(dentry); free(dir); return ret; } static unsigned long ubifs_findfile(struct super_block *sb, char *filename) { int ret; char *next; char fpath[128]; char symlinkpath[128]; char *name = fpath; unsigned long root_inum = 1; unsigned long inum; int symlink_count = 0; /* Don't allow symlink recursion */ char link_name[64]; strcpy(fpath, filename); /* Remove all leading slashes */ while (*name == '/') name++; /* * Handle root-direcoty ('/') */ inum = root_inum; if (!name || *name == '\0') return inum; for (;;) { struct inode *inode; struct ubifs_inode *ui; /* Extract the actual part from the pathname. */ next = strchr(name, '/'); if (next) { /* Remove all leading slashes. */ while (*next == '/') *(next++) = '\0'; } ret = ubifs_finddir(sb, name, root_inum, &inum); if (!ret) return 0; inode = ubifs_iget(sb, inum); if (!inode) return 0; ui = ubifs_inode(inode); if ((inode->i_mode & S_IFMT) == S_IFLNK) { char buf[128]; /* We have some sort of symlink recursion, bail out */ if (symlink_count++ > 8) { printf("Symlink recursion, aborting\n"); return 0; } memcpy(link_name, ui->data, ui->data_len); link_name[ui->data_len] = '\0'; if (link_name[0] == '/') { /* Absolute path, redo everything without * the leading slash */ next = name = link_name + 1; root_inum = 1; continue; } /* Relative to cur dir */ sprintf(buf, "%s/%s", link_name, next == NULL ? "" : next); memcpy(symlinkpath, buf, sizeof(buf)); next = name = symlinkpath; continue; } /* * Check if directory with this name exists */ /* Found the node! */ if (!next || *next == '\0') return inum; root_inum = inum; name = next; } return 0; } int ubifs_set_blk_dev(struct blk_desc *rbdd, struct disk_partition *info) { if (rbdd) { debug("UBIFS cannot be used with normal block devices\n"); return -1; } /* * Should never happen since blk_get_device_part_str() already checks * this, but better safe then sorry. */ if (!ubifs_is_mounted()) { debug("UBIFS not mounted, use ubifsmount to mount volume first!\n"); return -1; } return 0; } int ubifs_ls(const char *filename) { struct ubifs_info *c = ubifs_sb->s_fs_info; struct file *file; struct dentry *dentry; struct inode *dir; void *dirent = NULL; unsigned long inum; int ret = 0; c->ubi = ubi_open_volume(c->vi.ubi_num, c->vi.vol_id, UBI_READONLY); inum = ubifs_findfile(ubifs_sb, (char *)filename); if (!inum) { ret = -1; goto out; } file = kzalloc(sizeof(struct file), 0); dentry = kzalloc(sizeof(struct dentry), 0); dir = kzalloc(sizeof(struct inode), 0); if (!file || !dentry || !dir) { printf("%s: Error, no memory for malloc!\n", __func__); ret = -ENOMEM; goto out_mem; } dir->i_sb = ubifs_sb; file->f_path.dentry = dentry; file->f_path.dentry->d_parent = dentry; file->f_path.dentry->d_inode = dir; file->f_path.dentry->d_inode->i_ino = inum; file->f_pos = 1; file->private_data = NULL; ubifs_printdir(file, dirent); out_mem: if (file) free(file); if (dentry) free(dentry); if (dir) free(dir); out: ubi_close_volume(c->ubi); return ret; } int ubifs_exists(const char *filename) { struct ubifs_info *c = ubifs_sb->s_fs_info; unsigned long inum; c->ubi = ubi_open_volume(c->vi.ubi_num, c->vi.vol_id, UBI_READONLY); inum = ubifs_findfile(ubifs_sb, (char *)filename); ubi_close_volume(c->ubi); return inum != 0; } int ubifs_size(const char *filename, loff_t *size) { struct ubifs_info *c = ubifs_sb->s_fs_info; unsigned long inum; struct inode *inode; int err = 0; c->ubi = ubi_open_volume(c->vi.ubi_num, c->vi.vol_id, UBI_READONLY); inum = ubifs_findfile(ubifs_sb, (char *)filename); if (!inum) { err = -1; goto out; } inode = ubifs_iget(ubifs_sb, inum); if (IS_ERR(inode)) { printf("%s: Error reading inode %ld!\n", __func__, inum); err = PTR_ERR(inode); goto out; } *size = inode->i_size; ubifs_iput(inode); out: ubi_close_volume(c->ubi); return err; } /* * ubifsload... */ /* file.c */ static inline void *kmap(struct page *page) { return page->addr; } static int read_block(struct inode *inode, void *addr, unsigned int block, struct ubifs_data_node *dn) { struct ubifs_info *c = inode->i_sb->s_fs_info; int err, len, out_len; union ubifs_key key; unsigned int dlen; data_key_init(c, &key, inode->i_ino, block); err = ubifs_tnc_lookup(c, &key, dn); if (err) { if (err == -ENOENT) /* Not found, so it must be a hole */ memset(addr, 0, UBIFS_BLOCK_SIZE); return err; } ubifs_assert(le64_to_cpu(dn->ch.sqnum) > ubifs_inode(inode)->creat_sqnum); len = le32_to_cpu(dn->size); if (len <= 0 || len > UBIFS_BLOCK_SIZE) goto dump; dlen = le32_to_cpu(dn->ch.len) - UBIFS_DATA_NODE_SZ; out_len = UBIFS_BLOCK_SIZE; err = ubifs_decompress(c, &dn->data, dlen, addr, &out_len, le16_to_cpu(dn->compr_type)); if (err || len != out_len) goto dump; /* * Data length can be less than a full block, even for blocks that are * not the last in the file (e.g., as a result of making a hole and * appending data). Ensure that the remainder is zeroed out. */ if (len < UBIFS_BLOCK_SIZE) memset(addr + len, 0, UBIFS_BLOCK_SIZE - len); return 0; dump: ubifs_err(c, "bad data node (block %u, inode %lu)", block, inode->i_ino); ubifs_dump_node(c, dn); return -EINVAL; } static int do_readpage(struct ubifs_info *c, struct inode *inode, struct page *page, int last_block_size) { void *addr; int err = 0, i; unsigned int block, beyond; struct ubifs_data_node *dn; loff_t i_size = inode->i_size; dbg_gen("ino %lu, pg %lu, i_size %lld", inode->i_ino, page->index, i_size); addr = kmap(page); block = page->index << UBIFS_BLOCKS_PER_PAGE_SHIFT; beyond = (i_size + UBIFS_BLOCK_SIZE - 1) >> UBIFS_BLOCK_SHIFT; if (block >= beyond) { /* Reading beyond inode */ memset(addr, 0, PAGE_CACHE_SIZE); goto out; } dn = kmalloc(UBIFS_MAX_DATA_NODE_SZ, GFP_NOFS); if (!dn) return -ENOMEM; i = 0; while (1) { int ret; if (block >= beyond) { /* Reading beyond inode */ err = -ENOENT; memset(addr, 0, UBIFS_BLOCK_SIZE); } else { /* * Reading last block? Make sure to not write beyond * the requested size in the destination buffer. */ if (((block + 1) == beyond) || last_block_size) { void *buff; int dlen; /* * We need to buffer the data locally for the * last block. This is to not pad the * destination area to a multiple of * UBIFS_BLOCK_SIZE. */ buff = malloc_cache_aligned(UBIFS_BLOCK_SIZE); if (!buff) { printf("%s: Error, malloc fails!\n", __func__); err = -ENOMEM; break; } /* Read block-size into temp buffer */ ret = read_block(inode, buff, block, dn); if (ret) { err = ret; if (err != -ENOENT) { free(buff); break; } } if (last_block_size) dlen = last_block_size; else if (ret) dlen = UBIFS_BLOCK_SIZE; else dlen = le32_to_cpu(dn->size); /* Now copy required size back to dest */ memcpy(addr, buff, dlen); free(buff); } else { ret = read_block(inode, addr, block, dn); if (ret) { err = ret; if (err != -ENOENT) break; } } } if (++i >= UBIFS_BLOCKS_PER_PAGE) break; block += 1; addr += UBIFS_BLOCK_SIZE; } if (err) { if (err == -ENOENT) { /* Not found, so it must be a hole */ dbg_gen("hole"); goto out_free; } ubifs_err(c, "cannot read page %lu of inode %lu, error %d", page->index, inode->i_ino, err); goto error; } out_free: kfree(dn); out: return 0; error: kfree(dn); return err; } int ubifs_read(const char *filename, void *buf, loff_t offset, loff_t size, loff_t *actread) { struct ubifs_info *c = ubifs_sb->s_fs_info; unsigned long inum; struct inode *inode; struct page page; int err = 0; int i; int count; int last_block_size = 0; *actread = 0; if (offset & (PAGE_SIZE - 1)) { printf("ubifs: Error offset must be a multiple of %d\n", PAGE_SIZE); return -1; } c->ubi = ubi_open_volume(c->vi.ubi_num, c->vi.vol_id, UBI_READONLY); /* ubifs_findfile will resolve symlinks, so we know that we get * the real file here */ inum = ubifs_findfile(ubifs_sb, (char *)filename); if (!inum) { err = -1; goto out; } /* * Read file inode */ inode = ubifs_iget(ubifs_sb, inum); if (IS_ERR(inode)) { printf("%s: Error reading inode %ld!\n", __func__, inum); err = PTR_ERR(inode); goto out; } if (offset > inode->i_size) { printf("ubifs: Error offset (%lld) > file-size (%lld)\n", offset, size); err = -1; goto put_inode; } /* * If no size was specified or if size bigger than filesize * set size to filesize */ if ((size == 0) || (size > (inode->i_size - offset))) size = inode->i_size - offset; count = (size + UBIFS_BLOCK_SIZE - 1) >> UBIFS_BLOCK_SHIFT; page.addr = buf; page.index = offset / PAGE_SIZE; page.inode = inode; for (i = 0; i < count; i++) { /* * Make sure to not read beyond the requested size */ if (((i + 1) == count) && (size < inode->i_size)) last_block_size = size - (i * PAGE_SIZE); err = do_readpage(c, inode, &page, last_block_size); if (err) break; page.addr += PAGE_SIZE; page.index++; } if (err) { printf("Error reading file '%s'\n", filename); *actread = i * PAGE_SIZE; } else { *actread = size; } put_inode: ubifs_iput(inode); out: ubi_close_volume(c->ubi); return err; } void ubifs_close(void) { } /* Compat wrappers for common/cmd_ubifs.c */ int ubifs_load(char *filename, unsigned long addr, u32 size) { loff_t actread; int err; printf("Loading file '%s' to addr 0x%08lx...\n", filename, addr); err = ubifs_read(filename, (void *)(uintptr_t)addr, 0, size, &actread); if (err == 0) { env_set_hex("filesize", actread); printf("Done\n"); } return err; } void uboot_ubifs_umount(void) { if (ubifs_sb) { printf("Unmounting UBIFS volume %s!\n", ((struct ubifs_info *)(ubifs_sb->s_fs_info))->vi.name); ubifs_umount(ubifs_sb->s_fs_info); ubifs_sb = NULL; } }