
U-Boot supports a fairly wide variety of filesystems, including ext4, ubifs, fat, exfat, zfs, btrfs. These are an important part of bootloader functionality, since reading files from bare partitions or disk offsets is neither scalable nor convenient.
The filesystem API is functional but could use an overhaul. The main interface is in fs/fs.c
, which looks like this:
struct fstype_info {
int fstype;
char *name;
/*
* Is it legal to pass NULL as .probe()'s fs_dev_desc parameter? This
* should be false in most cases. For "virtual" filesystems which
* aren't based on a U-Boot block device (e.g. sandbox), this can be
* set to true. This should also be true for the dummy entry at the end
* of fstypes[], since that is essentially a "virtual" (non-existent)
* filesystem.
*/
bool null_dev_desc_ok;
int (*probe)(struct blk_desc *fs_dev_desc,
struct disk_partition *fs_partition);
int (*ls)(const char *dirname);
int (*exists)(const char *filename);
int (*size)(const char *filename, loff_t *size);
int (*read)(const char *filename, void *buf, loff_t offset,
loff_t len, loff_t *actread);
int (*write)(const char *filename, void *buf, loff_t offset,
loff_t len, loff_t *actwrite);
void (*close)(void);
int (*uuid)(char *uuid_str);
/*
* Open a directory stream. On success return 0 and directory
* stream pointer via 'dirsp'. On error, return -errno. See
* fs_opendir().
*/
int (*opendir)(const char *filename, struct fs_dir_stream **dirsp);
/*
* Read next entry from directory stream. On success return 0
* and directory entry pointer via 'dentp'. On error return
* -errno. See fs_readdir().
*/
int (*readdir)(struct fs_dir_stream *dirs, struct fs_dirent **dentp);
/* see fs_closedir() */
void (*closedir)(struct fs_dir_stream *dirs);
int (*unlink)(const char *filename);
int (*mkdir)(const char *dirname);
int (*ln)(const char *filename, const char *target);
int (*rename)(const char *old_path, const char *new_path);
};
At first glance this seems like a reasonable API. But where is the filesystem specified? The API seems to assume that this is already present somehow.
In fact there is a pair of separate functions responsible for selecting which filesystem the API acts on:
int fs_set_blk_dev(const char *ifname, const char *dev_part_str, int fstype)
int fs_set_blk_dev_with_part(struct blk_desc *desc, int part)
When you want to access a file, call either of these functions. It sets three ‘global’ variables, fs_dev_desc
, fs_dev_part
and fs_type
. After each operation, a call to fs_close()
resets things. This means you must select the block device before each operation. For example, see this code in bootmeth-uclass.c
:
if (IS_ENABLED(CONFIG_BOOTSTD_FULL) && bflow->fs_type)
fs_set_type(bflow->fs_type);
ret = fs_size(path, &size);
log_debug(" %s - err=%d\n", path, ret);
/* Sadly FS closes the file after fs_size() so we must redo this */
ret2 = bootmeth_setup_fs(bflow, desc);
if (ret2)
return log_msg_ret("fs", ret2);
It is a bit clumsy. Obviously this interface is not set up to support caching. In fact the filesystem is mounted afresh each time it is accessed. In a bootloader this is normally not too much of a problem. Since the OS and associated files are normally packaged in a FIT, a single read is enough to obtain everything that is needed. But if multiple directories need to be searched to find that FIT, or if there are multiple files to read, the repeated mounting does slow things down.
If you have sharp eyes you might have seen another problem. The two functions above assume that they are dealing with a block device. In fact, struct blk_desc
is the uclass-private data for a block device. What about when the filesystem is on the network? Also, with sandbox it is possible to access host files:
=> ls hostfs 0 /tmp/gimp
DIR 1044480 ..
DIR 4096 .
DIR 4096 2.10
=>
Clearly, the files on the hostsystem are not accessed at the block level. How does that work?
The key to this is null_dev_desc_ok
, which is true for the hostfs filesystem. There is a special case in the code to handle this.
int blk_get_device_part_str(const char *ifname, const char *dev_part_str,
struct blk_desc **desc,
struct disk_partition *info, int allow_whole_dev)
{
...
#if IS_ENABLED(CONFIG_SANDBOX) || IS_ENABLED(CONFIG_SEMIHOSTING)
/*
* Special-case a pseudo block device "hostfs", to allow access to the
* host's own filesystem.
*/
if (!strcmp(ifname, "hostfs")) {
strcpy((char *)info->type, BOOT_PART_TYPE);
strcpy((char *)info->name, "Host filesystem");
return 0;
}
#endif
It isn’t great. I’ve been looking at virtio-fs lately, which also doesn’t use a block device.
There are other things that could be improved, too:
- Filesystems must be specified explicitly by their device and partition number. It would be nice to have a unified ‘VFS’ like Linux (and Barebox) so filesystems could be mounted within a unified space.
- Files cannot be accessed from a device, nor is there any way to maintain a reference to a file you are working with
- Reading a file must done all at once, in most cases. It would be nice to have an interface to open, read and close the file.
Instead of adding yet more special cases, it may be time to overhaul the code a little.
An initial series has been sent to the concept@u-boot.org mailing list.