diff --git a/kernel/inc/fs/vfs.h b/kernel/inc/fs/vfs.h index e294f50a..0f1739ea 100644 --- a/kernel/inc/fs/vfs.h +++ b/kernel/inc/fs/vfs.h @@ -53,11 +53,12 @@ int vfs_unregister_filesystem(file_system_type_t *fs); /// @brief Register a superblock for the filesystem. /// @param name The name of the superblock. -/// @param path The path associated with the superblock. +/// @param path The mount point path associated with the superblock. +/// @param source The mount source (device/file) used for this filesystem. /// @param type A pointer to the filesystem type. /// @param root A pointer to the root file of the filesystem. /// @return 1 on success, 0 on failure. -int vfs_register_superblock(const char *name, const char *path, file_system_type_t *type, vfs_file_t *root); +int vfs_register_superblock(const char *name, const char *path, const char *source, file_system_type_t *type, vfs_file_t *root); /// @brief Unregister a superblock. /// @param sb A pointer to the superblock to unregister. @@ -73,6 +74,18 @@ super_block_t *vfs_get_superblock(const char *absolute_path); /// @param log_level Logging level to use for the output. void vfs_dump_superblocks(int log_level); +/// @brief Callback used for iterating all mounted superblocks. +/// @param sb Pointer to the current superblock. +/// @param ctx User-provided context pointer. +/// @return 0 to continue iteration, non-zero to stop early. +typedef int (*vfs_superblock_iter_fn)(super_block_t *sb, void *ctx); + +/// @brief Iterate over all mounted superblocks. +/// @param fn Callback to execute for each superblock. +/// @param ctx User-provided context passed to the callback. +/// @return 0 if iteration completed, otherwise the non-zero return value from the callback. +int vfs_superblock_for_each(vfs_superblock_iter_fn fn, void *ctx); + /// @brief Open a file given its absolute path. /// @param absolute_path An absolute path to the file. /// @param flags Used to set the file status flags and access modes. @@ -187,12 +200,24 @@ int vfs_symlink(const char *linkname, const char *path); /// @return 0 on success, -errno on failure. int vfs_stat(const char *path, stat_t *buf); +/// @brief Retrieve filesystem statistics for the mounted filesystem containing path. +/// @param path Path used to resolve the target mounted filesystem. +/// @param buf Buffer where filesystem statistics are stored. +/// @return 0 on success, -errno on failure. +int vfs_statfs(const char *path, statfs_t *buf); + /// @brief Stat the given file. /// @param file Pointer to the file for which we are retrieving the statistics. /// @param buf Buffer where we are storing the statistics. /// @return 0 on success, -errno on failure. int vfs_fstat(vfs_file_t *file, stat_t *buf); +/// @brief Retrieve filesystem statistics for the mounted filesystem containing file. +/// @param file Pointer to an open file on the target mounted filesystem. +/// @param buf Buffer where filesystem statistics are stored. +/// @return 0 on success, -errno on failure. +int vfs_fstatfs(vfs_file_t *file, statfs_t *buf); + /// @brief Mount the path as a filesystem of the given type. /// @param type The type of filesystem /// @param path The path to where it should be mounter. diff --git a/kernel/inc/fs/vfs_types.h b/kernel/inc/fs/vfs_types.h index c8551728..fa20ac7b 100644 --- a/kernel/inc/fs/vfs_types.h +++ b/kernel/inc/fs/vfs_types.h @@ -6,7 +6,9 @@ #pragma once #include "bits/stat.h" +#include "bits/statfs.h" #include "dirent.h" +#include "limits.h" #include "list_head.h" #include "stdint.h" @@ -53,6 +55,8 @@ typedef struct vfs_sys_operations { int (*rmdir_f)(const char *); /// Retrieves file status information. int (*stat_f)(const char *, stat_t *); + /// Retrieves filesystem status information. + int (*statfs_f)(const char *, statfs_t *); /// Creates a new file or directory. struct vfs_file *(*creat_f)(const char *, mode_t); /// Creates a symbolic link. @@ -137,8 +141,10 @@ typedef struct vfs_file { typedef struct super_block { /// Name of the superblock. char name[NAME_MAX]; - /// Path of the superblock. + /// Mount point path of the superblock. char path[PATH_MAX]; + /// Source (device/file) used to mount the filesystem. + char source[PATH_MAX]; /// Pointer to the root file of the given filesystem. struct vfs_file *root; /// Pointer to the information regarding the filesystem. @@ -163,7 +169,13 @@ typedef struct vfs_file_descriptor { #define ATTR_CTIME (1 << 5) ///< Flag set to specify the validity of CTIME. /// Used to initialize an iattr inside the chown function. -#define IATTR_CHOWN(user, group) {.ia_valid = ATTR_UID | ATTR_GID, .ia_uid = (user), .ia_gid = (group)} +#define IATTR_CHOWN(user, group) \ + { \ + .ia_valid = ATTR_UID | ATTR_GID, .ia_uid = (user), .ia_gid = (group) \ + } /// Used to initialize an iattr inside the chmod function. -#define IATTR_CHMOD(mode) {.ia_valid = ATTR_MODE, .ia_mode = (mode)} +#define IATTR_CHMOD(mode) \ + { \ + .ia_valid = ATTR_MODE, .ia_mode = (mode) \ + } diff --git a/kernel/inc/system/syscall.h b/kernel/inc/system/syscall.h index ffef298a..5151ddd7 100644 --- a/kernel/inc/system/syscall.h +++ b/kernel/inc/system/syscall.h @@ -220,6 +220,18 @@ int sys_stat(const char *path, stat_t *buf); /// @return Returns a negative value on failure. int sys_fstat(int fd, stat_t *buf); +/// @brief Retrieve filesystem statistics for the mounted filesystem containing path. +/// @param path A path on the target mounted filesystem. +/// @param buf A structure where filesystem statistics will be stored. +/// @return Returns a negative value on failure. +int sys_statfs(const char *path, statfs_t *buf); + +/// @brief Retrieve filesystem statistics for an open file descriptor. +/// @param fd The file descriptor of a file on the target mounted filesystem. +/// @param buf A structure where filesystem statistics will be stored. +/// @return Returns a negative value on failure. +int sys_fstatfs(int fd, statfs_t *buf); + /// @brief Creates a new directory at the given path. /// @param path The path of the new directory. /// @param mode The permission of the new directory. diff --git a/kernel/src/drivers/ata.c b/kernel/src/drivers/ata.c index a728af6f..9ad0b723 100644 --- a/kernel/src/drivers/ata.c +++ b/kernel/src/drivers/ata.c @@ -1694,7 +1694,7 @@ static ata_device_type_t ata_device_detect(ata_device_t *dev) // Update the filesystem entry with the length of the device. dev->fs_root->length = ata_max_offset(dev); // Try to mount the drive. - if (!vfs_register_superblock(dev->fs_root->name, dev->path, &ata_file_system_type, dev->fs_root)) { + if (!vfs_register_superblock(dev->fs_root->name, dev->path, dev->path, &ata_file_system_type, dev->fs_root)) { pr_alert("Failed to mount ata device!\n"); // Free the memory. vfs_dealloc_file(dev->fs_root); diff --git a/kernel/src/drivers/mem.c b/kernel/src/drivers/mem.c index 1b316f65..cdb98a94 100644 --- a/kernel/src/drivers/mem.c +++ b/kernel/src/drivers/mem.c @@ -83,12 +83,14 @@ static vfs_file_t *find_device_file(const char *path) } static int mem_stat(const char *path, stat_t *stat); +static int mem_statfs(const char *path, statfs_t *statfs); /// @brief System operations for the memory device. static vfs_sys_operations_t mem_sys_operations = { .mkdir_f = NULL, .rmdir_f = NULL, .stat_f = mem_stat, + .statfs_f = mem_statfs, }; static vfs_file_t *null_mount_callback(const char *path, const char *device); @@ -143,6 +145,21 @@ static int mem_stat(const char *path, stat_t *stat) return -ENOENT; } +static int mem_statfs(const char *path, statfs_t *statfs) +{ + (void)path; + if (statfs == NULL) { + return -EINVAL; + } + + memset(statfs, 0, sizeof(statfs_t)); + statfs->f_type = 0; + statfs->f_bsize = 1; + statfs->f_namelen = NAME_MAX; + statfs->f_frsize = 1; + return 0; +} + /// @brief The mount callback, which prepares everything and calls the actual /// NULL mount function. /// @@ -371,7 +388,7 @@ int mem_devs_initialize(void) } // Mount the /dev/null device. - if (!vfs_register_superblock("null", "/dev/null", &null_file_system_type, devnull->file)) { + if (!vfs_register_superblock("null", "/dev/null", "/dev/null", &null_file_system_type, devnull->file)) { pr_err("mem_devs_initialize: Failed to mount /dev/null\n"); return 1; // Return error if mounting fails. } diff --git a/kernel/src/fs/ext2.c b/kernel/src/fs/ext2.c index f21020bd..bc3b815d 100644 --- a/kernel/src/fs/ext2.c +++ b/kernel/src/fs/ext2.c @@ -382,6 +382,7 @@ static int ext2_fsetattr(vfs_file_t *file, struct iattr *attr); static int ext2_mkdir(const char *path, mode_t mode); static int ext2_rmdir(const char *path); static int ext2_stat(const char *path, stat_t *stat); +static int ext2_statfs(const char *path, statfs_t *statfs); static int ext2_setattr(const char *path, struct iattr *attr); static vfs_file_t *ext2_creat(const char *path, mode_t mode); static vfs_file_t *ext2_mount(vfs_file_t *block_device, const char *path); @@ -397,6 +398,7 @@ static vfs_sys_operations_t ext2_sys_operations = { .mkdir_f = ext2_mkdir, .rmdir_f = ext2_rmdir, .stat_f = ext2_stat, + .statfs_f = ext2_statfs, .creat_f = ext2_creat, .symlink_f = NULL, .setattr_f = ext2_setattr, @@ -3522,6 +3524,33 @@ static int ext2_stat(const char *path, stat_t *stat) return __ext2_stat(fs, &inode, search.direntry.inode, stat); } +static int ext2_statfs(const char *path, statfs_t *statfs) +{ + ext2_filesystem_t *fs = get_ext2_filesystem(path); + if (fs == NULL) { + pr_err("ext2_statfs(path: %s): Failed to get the EXT2 filesystem.\n", path); + return -ENOENT; + } + + if (statfs == NULL) { + return -EINVAL; + } + + memset(statfs, 0, sizeof(statfs_t)); + statfs->f_type = EXT2_SUPERBLOCK_MAGIC; + statfs->f_bsize = fs->block_size; + statfs->f_blocks = fs->superblock.blocks_count; + statfs->f_bfree = fs->superblock.free_blocks_count; + statfs->f_bavail = (fs->superblock.free_blocks_count > fs->superblock.r_blocks_count) + ? (fs->superblock.free_blocks_count - fs->superblock.r_blocks_count) + : 0; + statfs->f_files = fs->superblock.inodes_count; + statfs->f_ffree = fs->superblock.free_inodes_count; + statfs->f_namelen = EXT2_NAME_LEN; + statfs->f_frsize = fs->block_size; + return 0; +} + /// @brief Perform the I/O control operation specified by REQUEST on FD. One /// argument may follow; its presence and type depend on REQUEST. /// @param file the file on which we perform the operations. diff --git a/kernel/src/fs/procfs.c b/kernel/src/fs/procfs.c index 3e208501..5f796da4 100644 --- a/kernel/src/fs/procfs.c +++ b/kernel/src/fs/procfs.c @@ -82,6 +82,7 @@ procfs_t fs; static int procfs_mkdir(const char *path, mode_t mode); static int procfs_rmdir(const char *path); static int procfs_stat(const char *path, stat_t *stat); +static int procfs_statfs(const char *path, statfs_t *statfs); static vfs_file_t *procfs_open(const char *path, int flags, mode_t mode); static int procfs_unlink(const char *path); @@ -102,6 +103,7 @@ static vfs_sys_operations_t procfs_sys_operations = { .mkdir_f = procfs_mkdir, .rmdir_f = procfs_rmdir, .stat_f = procfs_stat, + .statfs_f = procfs_statfs, .creat_f = NULL, .symlink_f = NULL, }; @@ -843,6 +845,21 @@ static inline ssize_t procfs_getdents(vfs_file_t *file, dirent_t *dirp, off_t do return written_size; } +static int procfs_statfs(const char *path, statfs_t *statfs) +{ + (void)path; + if (!statfs) { + return -EINVAL; + } + + memset(statfs, 0, sizeof(statfs_t)); + statfs->f_type = 0x9fa0; + statfs->f_bsize = 1024; + statfs->f_namelen = PROCFS_NAME_MAX; + statfs->f_frsize = 1024; + return 0; +} + /// @brief Mounts the block device as a procfs filesystem. /// @param block_device the block device formatted as procfs. /// @param path location where we mount the filesystem. diff --git a/kernel/src/fs/stat.c b/kernel/src/fs/stat.c index 5d7cb236..9a534fc9 100644 --- a/kernel/src/fs/stat.c +++ b/kernel/src/fs/stat.c @@ -40,3 +40,26 @@ int sys_fstat(int fd, stat_t *buf) return vfs_fstat(vfd->file_struct, buf); } + +int sys_statfs(const char *path, statfs_t *buf) { return vfs_statfs(path, buf); } + +int sys_fstatfs(int fd, statfs_t *buf) +{ + // Get the current task. + task_struct *task = scheduler_get_current_process(); + + // Check the current FD. + if (fd < 0 || fd >= task->max_fd) { + return -EMFILE; + } + + // Get the file descriptor. + vfs_file_descriptor_t *vfd = &task->fd_list[fd]; + + // Check the file. + if (vfd->file_struct == NULL) { + return -ENOSYS; + } + + return vfs_fstatfs(vfd->file_struct, buf); +} diff --git a/kernel/src/fs/vfs.c b/kernel/src/fs/vfs.c index a5e07746..a0037e87 100644 --- a/kernel/src/fs/vfs.c +++ b/kernel/src/fs/vfs.c @@ -168,7 +168,7 @@ int vfs_unregister_filesystem(file_system_type_t *fs) static inline void __vfs_dump_superblock(int log_level, super_block_t *sb) { assert(sb && "Received NULL suberblock."); - pr_log(log_level, "\tname=%s, path=%s, root=%p, type=%p\n", sb->name, sb->path, (void *)sb->root, (void *)sb->type); + pr_log(log_level, "\tname=%s, source=%s, path=%s, root=%p, type=%p\n", sb->name, sb->source, sb->path, (void *)sb->root, (void *)sb->type); } void vfs_dump_superblocks(int log_level) @@ -178,9 +178,27 @@ void vfs_dump_superblocks(int log_level) } } -int vfs_register_superblock(const char *name, const char *path, file_system_type_t *type, vfs_file_t *root) +int vfs_superblock_for_each(vfs_superblock_iter_fn fn, void *ctx) { - pr_debug("vfs_register_superblock(name: %s, path: %s, type: %s, root: %p)\n", name, path, type->name, root); + if (!fn) { + return 1; + } + int result = 0; + spinlock_lock(&vfs_spinlock); + list_for_each_decl (it, &vfs_super_blocks) { + super_block_t *sb = list_entry(it, super_block_t, mounts); + result = fn(sb, ctx); + if (result != 0) { + break; + } + } + spinlock_unlock(&vfs_spinlock); + return result; +} + +int vfs_register_superblock(const char *name, const char *path, const char *source, file_system_type_t *type, vfs_file_t *root) +{ + pr_debug("vfs_register_superblock(name: %s, path: %s, source: %s, type: %s, root: %p)\n", name, path, source ? source : "(null)", type->name, root); // Validate input parameters. if (!name) { pr_err("vfs_register_superblock: NULL name provided\n"); @@ -190,6 +208,10 @@ int vfs_register_superblock(const char *name, const char *path, file_system_type pr_err("vfs_register_superblock: NULL path provided\n"); return 0; } + if (!source) { + pr_err("vfs_register_superblock: NULL source provided\n"); + return 0; + } if (!type) { pr_err("vfs_register_superblock: NULL file system type provided\n"); return 0; @@ -216,6 +238,10 @@ int vfs_register_superblock(const char *name, const char *path, file_system_type strncpy(sb->path, path, sizeof(sb->path) - 1); sb->path[sizeof(sb->path) - 1] = '\0'; + // Copy the source of the superblock. + strncpy(sb->source, source, sizeof(sb->source) - 1); + sb->source[sizeof(sb->source) - 1] = '\0'; + // Initialize the root file and file system type. sb->root = root; sb->type = type; @@ -849,6 +875,67 @@ int vfs_fstat(vfs_file_t *file, stat_t *buf) return file->fs_operations->stat_f(file, buf); } +int vfs_statfs(const char *path, statfs_t *buf) +{ + // Allocate a variable for the path. + char absolute_path[PATH_MAX]; + // If the first character is not the '/' then get the absolute path. + int resolve_flags = REMOVE_TRAILING_SLASH | FOLLOW_LINKS; + int ret = resolve_path(path, absolute_path, PATH_MAX, resolve_flags); + if (ret < 0) { + pr_err("vfs_statfs(%s): Cannot get the absolute path.\n", path); + return ret; + } + + super_block_t *sb = vfs_get_superblock(absolute_path); + if (sb == NULL) { + pr_err("vfs_statfs(%s): Cannot find the superblock!\n", path); + return -ENODEV; + } + vfs_file_t *sb_root = sb->root; + if (sb_root == NULL) { + pr_err("vfs_statfs(%s): Cannot find the superblock root.\n", path); + return -ENOENT; + } + if ((sb_root->sys_operations == NULL) || (sb_root->sys_operations->statfs_f == NULL)) { + pr_err("vfs_statfs(%s): Function not supported in current filesystem.\n", path); + return -ENOSYS; + } + + memset(buf, 0, sizeof(statfs_t)); + return sb_root->sys_operations->statfs_f(absolute_path, buf); +} + +int vfs_fstatfs(vfs_file_t *file, statfs_t *buf) +{ + if (!file) { + return -EINVAL; + } + + super_block_t *target_sb = NULL; + + spinlock_lock(&vfs_spinlock); + list_for_each_decl (it, &vfs_super_blocks) { + super_block_t *sb = list_entry(it, super_block_t, mounts); + if (sb->root == file || sb->root->device == file->device) { + target_sb = sb; + break; + } + } + spinlock_unlock(&vfs_spinlock); + + if (target_sb == NULL) { + return -ENODEV; + } + if ((target_sb->root == NULL) || (target_sb->root->sys_operations == NULL) || + (target_sb->root->sys_operations->statfs_f == NULL)) { + return -ENOSYS; + } + + memset(buf, 0, sizeof(statfs_t)); + return target_sb->root->sys_operations->statfs_f(target_sb->path, buf); +} + int vfs_mount(const char *type, const char *path, const char *args) { file_system_type_t *fst = __vfs_find_filesystem(type); @@ -879,7 +966,7 @@ int vfs_mount(const char *type, const char *path, const char *args) return -ENODEV; } // Register the proc superblock. - if (!vfs_register_superblock(file->name, path, fst, file)) { + if (!vfs_register_superblock(file->name, path, args ? args : "", fst, file)) { pr_alert("Failed to register %s superblock!\n", file->name); return -ENODEV; } diff --git a/kernel/src/io/proc_system.c b/kernel/src/io/proc_system.c index 843e6c9b..58c86590 100644 --- a/kernel/src/io/proc_system.c +++ b/kernel/src/io/proc_system.c @@ -5,6 +5,7 @@ #include "errno.h" #include "fs/procfs.h" +#include "fs/vfs.h" #include "hardware/timer.h" #include "io/debug.h" #include "process/process.h" @@ -62,7 +63,7 @@ static ssize_t __procs_read(vfs_file_t *file, char *buf, off_t offset, size_t nb // Perform read. ssize_t it = 0; if (ret >= 0) { - size_t name_len = strlen(buffer); + size_t name_len = strnlen(buffer, BUFSIZ); size_t read_pos = offset; if (read_pos < name_len) { while ((it < nbyte) && (read_pos < name_len)) { @@ -136,11 +137,73 @@ static ssize_t procs_do_version(char *buffer, size_t bufsize) return sprintf(buffer, "%s version %s (site: %s) (email: %s)", OS_NAME, OS_VERSION, OS_SITEURL, OS_REF_EMAIL); } +/// @brief Context used when generating `/proc/mounts`. +typedef struct { + char *buf; + size_t bufsize; + size_t pos; +} procs_mounts_ctx_t; + +/// @brief Callback used to build a /proc/mounts line for each mount. +/// @param sb The superblock representing a mounted filesystem. +/// @param ctx A `procs_mounts_ctx_t` pointer. +/// @return 0 to continue, non-zero to stop. +static int procs_do_mounts_cb(super_block_t *sb, void *ctx) +{ + if (!sb || !ctx) { + return 1; + } + + procs_mounts_ctx_t *mounts_ctx = (procs_mounts_ctx_t *)ctx; + + if (mounts_ctx->pos >= (mounts_ctx->bufsize - 1)) { + return 1; + } + + const char *source = (sb->source[0] != '\0') ? sb->source : sb->name; + const char *mountpoint = sb->path; + const char *fstype = (sb->type && sb->type->name) ? sb->type->name : "unknown"; + const char *options = "rw"; + + int written = snprintf( + mounts_ctx->buf + mounts_ctx->pos, + mounts_ctx->bufsize - mounts_ctx->pos, + "%s %s %s %s 0 0\n", + source, + mountpoint, + fstype, + options); + if (written < 0) { + return 1; + } + + if ((size_t)written >= mounts_ctx->bufsize - mounts_ctx->pos) { + // Buffer filled, stop writing. + mounts_ctx->pos = mounts_ctx->bufsize - 1; + mounts_ctx->buf[mounts_ctx->pos] = '\0'; + return 1; + } + + mounts_ctx->pos += (size_t)written; + return 0; +} + /// @brief Write the list of mount points inside the buffer. /// @param buffer the buffer. /// @param bufsize the buffer size. /// @return the amount we wrote. -static ssize_t procs_do_mounts(char *buffer, size_t bufsize) { return 0; } +static ssize_t procs_do_mounts(char *buffer, size_t bufsize) +{ + if (!buffer || bufsize == 0) { + return 0; + } + + procs_mounts_ctx_t ctx = {.buf = buffer, .bufsize = bufsize, .pos = 0}; + + vfs_superblock_for_each(procs_do_mounts_cb, &ctx); + buffer[ctx.pos] = '\0'; + return ctx.pos; +} /// @brief Write the cpu information inside the buffer. /// @param buffer the buffer. diff --git a/kernel/src/mem/mm/vm_area.c b/kernel/src/mem/mm/vm_area.c index 32079455..b73f5cef 100644 --- a/kernel/src/mem/mm/vm_area.c +++ b/kernel/src/mem/mm/vm_area.c @@ -36,15 +36,15 @@ vm_area_create(struct mm_struct *mm, uint32_t vm_start, size_t size, uint32_t pg { // Validate inputs. if (!mm) { - pr_crit("Invalid arguments: mm is NULL."); + pr_crit("Invalid arguments: mm is NULL.\n"); return NULL; } if (!vm_start) { - pr_crit("Invalid arguments: vm_start is 0."); + pr_crit("Invalid arguments: vm_start is 0.\n"); return NULL; } if (!size) { - pr_crit("Invalid arguments: size is 0."); + pr_crit("Invalid arguments: size is 0.\n"); return NULL; } @@ -128,11 +128,11 @@ uint32_t vm_area_clone(mm_struct_t *mm, vm_area_struct_t *area, int cow, uint32_ { // Validate inputs. if (!mm) { - pr_crit("Invalid arguments: mm is NULL."); + pr_crit("Invalid arguments: mm is NULL.\n"); return -1; } if (!area) { - pr_crit("Invalid arguments: area is NULL."); + pr_crit("Invalid arguments: area is NULL.\n"); return -1; } @@ -295,28 +295,28 @@ int vm_area_is_valid(mm_struct_t *mm, uintptr_t vm_start, uintptr_t vm_end) // Check if the area is NULL. if (!area) { - pr_crit("Encountered a NULL area in the list."); + pr_crit("Encountered a NULL area in the list.\n"); return -1; } // Check if the new area overlaps with the current area. if ((vm_start > area->vm_start) && (vm_start < area->vm_end)) { pr_crit( - "Overlap detected at start: %p <= %p <= %p", (void *)area->vm_start, (void *)vm_start, + "Overlap detected at start: %p <= %p <= %p\n", (void *)area->vm_start, (void *)vm_start, (void *)area->vm_end); return 0; } if ((vm_end > area->vm_start) && (vm_end < area->vm_end)) { pr_crit( - "Overlap detected at end: %p <= %p <= %p", (void *)area->vm_start, (void *)vm_end, + "Overlap detected at end: %p <= %p <= %p\n", (void *)area->vm_start, (void *)vm_end, (void *)area->vm_end); return 0; } if ((vm_start < area->vm_start) && (vm_end > area->vm_end)) { pr_crit( - "Wrap-around detected: %p <= (%p, %p) <= %p", (void *)vm_start, (void *)area->vm_start, + "Wrap-around detected: %p <= (%p, %p) <= %p\n", (void *)vm_start, (void *)area->vm_start, (void *)area->vm_end, (void *)vm_end); return 0; } @@ -354,7 +354,7 @@ int vm_area_search_free_area(mm_struct_t *mm, size_t length, uintptr_t *vm_start { // Check for a valid memory descriptor. if (!mm || !length || !vm_start) { - pr_crit("Invalid arguments: mm or length or vm_start is NULL."); + pr_crit("Invalid arguments: mm or length or vm_start is NULL.\n"); return -1; } @@ -369,7 +369,7 @@ int vm_area_search_free_area(mm_struct_t *mm, size_t length, uintptr_t *vm_start // Check if the current area is NULL. if (!area) { - pr_crit("Encountered a NULL area in the list."); + pr_crit("Encountered a NULL area in the list.\n"); return -1; } @@ -379,7 +379,7 @@ int vm_area_search_free_area(mm_struct_t *mm, size_t length, uintptr_t *vm_start // Check if the previous area is NULL. if (!prev_area) { - pr_crit("Encountered a NULL previous area in the list."); + pr_crit("Encountered a NULL previous area in the list.\n"); return -1; } diff --git a/kernel/src/system/syscall.c b/kernel/src/system/syscall.c index ee9d2630..c3996c23 100644 --- a/kernel/src/system/syscall.c +++ b/kernel/src/system/syscall.c @@ -69,6 +69,8 @@ void syscall_init(void) sys_call_table[__NR_getuid] = (SystemCall)sys_getuid; sys_call_table[__NR_alarm] = (SystemCall)sys_alarm; sys_call_table[__NR_fstat] = (SystemCall)sys_fstat; + sys_call_table[__NR_statfs] = (SystemCall)sys_statfs; + sys_call_table[__NR_fstatfs] = (SystemCall)sys_fstatfs; sys_call_table[__NR_nice] = (SystemCall)sys_nice; sys_call_table[__NR_kill] = (SystemCall)sys_kill; sys_call_table[__NR_mkdir] = (SystemCall)sys_mkdir; diff --git a/kernel/src/tests/runner.c b/kernel/src/tests/runner.c index 1d6e94f2..ae0b152f 100644 --- a/kernel/src/tests/runner.c +++ b/kernel/src/tests/runner.c @@ -42,6 +42,7 @@ extern void test_page(void); extern void test_memory_adversarial(void); extern void test_dma(void); extern void test_fpu(void); +extern void test_vfs(void); /// @brief Test registry - one entry per subsystem. static const test_entry_t test_functions[] = { @@ -56,6 +57,7 @@ static const test_entry_t test_functions[] = { {test_mm, "MM/VMA Subsystem" }, {test_buddy, "Buddy System Subsystem" }, {test_page, "Page Structure Subsystem" }, + {test_vfs, "VFS Subsystem" }, {test_dma, "DMA Zone/Allocation Tests" }, {test_memory_adversarial, "Memory Adversarial/Error Tests"}, {test_fpu, "FPU Subsystem" }, diff --git a/kernel/src/tests/unit/test_vfs.c b/kernel/src/tests/unit/test_vfs.c new file mode 100644 index 00000000..2e398d6f --- /dev/null +++ b/kernel/src/tests/unit/test_vfs.c @@ -0,0 +1,87 @@ +/// @file test_vfs.c +/// @brief VFS and mount-table related tests. +/// @copyright (c) 2014-2024 This file is distributed under the MIT License. +/// See LICENSE.md for details. + +// Setup the logging for this file (do this before any other include). +#include "sys/kernel_levels.h" // Include kernel log levels. +#define __DEBUG_HEADER__ "[TUNIT ]" ///< Change header. +#define __DEBUG_LEVEL__ LOGLEVEL_NOTICE ///< Set log level. +#include "io/debug.h" // Include debugging functions. + +#include "fcntl.h" +#include "fs/vfs.h" +#include "string.h" +#include "tests/test.h" +#include "tests/test_utils.h" + +typedef struct { + int mount_count; + int has_root_mount; + int has_proc_mount; +} test_mounts_ctx_t; + +static int test_vfs_mounts_iter_cb(super_block_t *sb, void *ctx) +{ + if (!sb || !ctx) { + return 1; + } + + test_mounts_ctx_t *mounts_ctx = (test_mounts_ctx_t *)ctx; + mounts_ctx->mount_count++; + + if (strcmp(sb->path, "/") == 0) { + mounts_ctx->has_root_mount = 1; + } + if (strcmp(sb->path, "/proc") == 0) { + mounts_ctx->has_proc_mount = 1; + } + + return 0; +} + +/// @brief Validate mounted superblock enumeration through the VFS iterator. +TEST(memory_vfs_mount_iterator) +{ + TEST_SECTION_START("VFS mount iterator"); + + test_mounts_ctx_t ctx = {0}; + int ret = vfs_superblock_for_each(test_vfs_mounts_iter_cb, &ctx); + + ASSERT_MSG(ret == 0, "vfs_superblock_for_each must complete"); + ASSERT_MSG(ctx.mount_count > 0, "there must be at least one mounted filesystem"); + ASSERT_MSG(ctx.has_root_mount, "root mount '/' must exist"); + ASSERT_MSG(ctx.has_proc_mount, "proc mount '/proc' must exist"); + + TEST_SECTION_END(); +} + +/// @brief Validate /proc/mounts output is readable and contains key mount points. +TEST(memory_vfs_proc_mounts_content) +{ + TEST_SECTION_START("/proc/mounts content"); + + vfs_file_t *file = vfs_open("/proc/mounts", O_RDONLY, 0); + ASSERT_MSG(file != NULL, "vfs_open('/proc/mounts') must succeed"); + + char buffer[1024]; + memset(buffer, 0, sizeof(buffer)); + + ssize_t read_count = vfs_read(file, buffer, 0, sizeof(buffer) - 1); + ASSERT_MSG(read_count > 0, "vfs_read('/proc/mounts') must return data"); + buffer[read_count] = '\0'; + + ASSERT_MSG(strstr(buffer, " / ") != NULL, "'/proc/mounts' must include root mountpoint"); + ASSERT_MSG(strstr(buffer, " /proc ") != NULL, "'/proc/mounts' must include /proc mountpoint"); + + ASSERT_MSG(vfs_close(file) == 0, "vfs_close('/proc/mounts') must succeed"); + + TEST_SECTION_END(); +} + +/// @brief Main test function for VFS subsystem. +void test_vfs(void) +{ + test_memory_vfs_mount_iterator(); + test_memory_vfs_proc_mounts_content(); +} diff --git a/lib/inc/argparse.h b/lib/inc/argparse.h new file mode 100644 index 00000000..17750b21 --- /dev/null +++ b/lib/inc/argparse.h @@ -0,0 +1,72 @@ +/// @file argparse.h +/// @brief Allocation-free command line parser for userspace tools. +/// @copyright (c) 2014-2024 This file is distributed under the MIT License. +/// See LICENSE.md for details. + +#pragma once + +#include +#include + +typedef enum ap_value_mode { + AP_VALUE_NONE = 0, + AP_VALUE_REQUIRED, + AP_VALUE_OPTIONAL, +} ap_value_mode_t; + +typedef struct ap_option { + char short_name; + const char *long_name; + ap_value_mode_t value_mode; + const char *value_name; + const char *help; +} ap_option_t; + +typedef enum ap_error_code { + AP_ERR_NONE = 0, + AP_ERR_INVALID_ARGUMENT, + AP_ERR_UNKNOWN_OPTION, + AP_ERR_MISSING_VALUE, + AP_ERR_UNEXPECTED_VALUE, + AP_ERR_EMPTY_OPTION, +} ap_error_code_t; + +typedef struct ap_error { + ap_error_code_t code; + const char *token; + const char *option_name; +} ap_error_t; + +typedef enum ap_event_kind { + AP_EVENT_OPTION = 0, + AP_EVENT_POSITIONAL, +} ap_event_kind_t; + +typedef struct ap_event { + ap_event_kind_t kind; + const ap_option_t *option; + const char *name; + const char *value; + const char *positional; +} ap_event_t; + +typedef enum ap_status { + AP_STATUS_OK = 0, + AP_STATUS_END, + AP_STATUS_ERROR, +} ap_status_t; + +typedef struct ap_parser { + int argc; + char **argv; + int index; + int short_pos; + bool_t stop_options; +} ap_parser_t; + +void ap_parser_init(ap_parser_t *parser, int argc, char **argv); + +ap_status_t ap_parser_next(ap_parser_t *parser, const ap_option_t *options, size_t option_count, ap_event_t *event, + ap_error_t *error); + +const char *ap_error_code_to_string(ap_error_code_t code); diff --git a/lib/inc/bits/statfs.h b/lib/inc/bits/statfs.h new file mode 100644 index 00000000..b5a32c27 --- /dev/null +++ b/lib/inc/bits/statfs.h @@ -0,0 +1,39 @@ +/// @file statfs.h +/// @brief Defines the structure used by statfs() and fstatfs(). +/// @copyright (c) 2014-2024 This file is distributed under the MIT License. +/// See LICENSE.md for details. + +#pragma once + +#if !defined(__SYS_STATFS_H) && !defined(__KERNEL__) +#error "Never include directly; use instead." +#endif + +#include "stdint.h" + +typedef struct statfs { + /// Type of filesystem. + uint32_t f_type; + /// Optimal transfer block size. + uint32_t f_bsize; + /// Total data blocks in filesystem. + uint32_t f_blocks; + /// Free blocks in filesystem. + uint32_t f_bfree; + /// Free blocks available to unprivileged users. + uint32_t f_bavail; + /// Total file nodes in filesystem. + uint32_t f_files; + /// Free file nodes in filesystem. + uint32_t f_ffree; + /// Filesystem ID. + uint32_t f_fsid; + /// Maximum length of filenames. + uint32_t f_namelen; + /// Fragment size. + uint32_t f_frsize; + /// Mount flags. + uint32_t f_flags; + /// Spare values for future extensions. + uint32_t f_spare[4]; +} statfs_t; diff --git a/lib/inc/sys/statfs.h b/lib/inc/sys/statfs.h new file mode 100644 index 00000000..125413be --- /dev/null +++ b/lib/inc/sys/statfs.h @@ -0,0 +1,23 @@ +/// @file statfs.h +/// @brief Filesystem statistics functions. +/// @copyright (c) 2014-2024 This file is distributed under the MIT License. +/// See LICENSE.md for details. + +#pragma once + +/// Prevents the error when including . +#define __SYS_STATFS_H + +#include "bits/statfs.h" + +/// @brief Retrieves filesystem statistics for the mounted filesystem containing path. +/// @param path Path to any file or directory on the target filesystem. +/// @param buf Buffer where filesystem statistics are written. +/// @return 0 on success, -1 on failure and errno is set. +int statfs(const char *path, statfs_t *buf); + +/// @brief Retrieves filesystem statistics for an open file descriptor. +/// @param fd File descriptor of an open file on the target filesystem. +/// @param buf Buffer where filesystem statistics are written. +/// @return 0 on success, -1 on failure and errno is set. +int fstatfs(int fd, statfs_t *buf); diff --git a/lib/src/argparse.c b/lib/src/argparse.c new file mode 100644 index 00000000..ccd80975 --- /dev/null +++ b/lib/src/argparse.c @@ -0,0 +1,247 @@ +/// @file argparse.c +/// @brief Allocation-free command line parser for userspace tools. +/// @copyright (c) 2014-2024 This file is distributed under the MIT License. +/// See LICENSE.md for details. + +#include +#include + +static void ap_reset_event(ap_event_t *event) +{ + if (!event) { + return; + } + event->kind = AP_EVENT_POSITIONAL; + event->option = NULL; + event->name = NULL; + event->value = NULL; + event->positional = NULL; +} + +static void ap_reset_error(ap_error_t *error) +{ + if (!error) { + return; + } + error->code = AP_ERR_NONE; + error->token = NULL; + error->option_name = NULL; +} + +static void ap_set_error(ap_error_t *error, ap_error_code_t code, const char *token, const char *option_name) +{ + if (!error) { + return; + } + error->code = code; + error->token = token; + error->option_name = option_name; +} + +static const ap_option_t *ap_find_long_option_n(const ap_option_t *options, size_t option_count, const char *name, + size_t name_len) +{ + for (size_t i = 0; i < option_count; ++i) { + const char *long_name = options[i].long_name; + if (!long_name) { + continue; + } + if ((strncmp(long_name, name, name_len) == 0) && (long_name[name_len] == '\0')) { + return &options[i]; + } + } + return NULL; +} + +static const ap_option_t *ap_find_short_option(const ap_option_t *options, size_t option_count, char short_name) +{ + for (size_t i = 0; i < option_count; ++i) { + if (options[i].short_name == short_name) { + return &options[i]; + } + } + return NULL; +} + +void ap_parser_init(ap_parser_t *parser, int argc, char **argv) +{ + parser->argc = argc; + parser->argv = argv; + parser->index = 1; + parser->short_pos = 0; + parser->stop_options = false; +} + +static ap_status_t ap_parse_long_option(ap_parser_t *parser, const ap_option_t *options, size_t option_count, + ap_event_t *event, ap_error_t *error) +{ + const char *token = parser->argv[parser->index]; + const char *name = token + 2; + const char *eq = strchr(name, '='); + size_t name_len = eq ? (size_t)(eq - name) : strlen(name); + const char *value = eq ? (eq + 1) : NULL; + const ap_option_t *opt; + + if (name_len == 0) { + ap_set_error(error, AP_ERR_EMPTY_OPTION, token, token); + parser->index++; + return AP_STATUS_ERROR; + } + + opt = ap_find_long_option_n(options, option_count, name, name_len); + if (!opt) { + ap_set_error(error, AP_ERR_UNKNOWN_OPTION, token, token); + parser->index++; + return AP_STATUS_ERROR; + } + + if (opt->value_mode == AP_VALUE_NONE) { + if (value != NULL) { + ap_set_error(error, AP_ERR_UNEXPECTED_VALUE, token, token); + parser->index++; + return AP_STATUS_ERROR; + } + } else if (opt->value_mode == AP_VALUE_REQUIRED) { + if (value == NULL) { + if ((parser->index + 1) >= parser->argc) { + ap_set_error(error, AP_ERR_MISSING_VALUE, token, token); + parser->index++; + return AP_STATUS_ERROR; + } + value = parser->argv[parser->index + 1]; + parser->index += 2; + } else { + parser->index++; + } + } else { + parser->index++; + } + + event->kind = AP_EVENT_OPTION; + event->option = opt; + event->name = token; + event->value = value; + + return AP_STATUS_OK; +} + +static ap_status_t ap_parse_short_option(ap_parser_t *parser, const ap_option_t *options, size_t option_count, + ap_event_t *event, ap_error_t *error) +{ + const char *token = parser->argv[parser->index]; + char short_name = token[parser->short_pos]; + + const ap_option_t *opt = ap_find_short_option(options, option_count, short_name); + if (!opt) { + ap_set_error(error, AP_ERR_UNKNOWN_OPTION, token, token); + parser->index++; + parser->short_pos = 0; + return AP_STATUS_ERROR; + } + + const char *value = NULL; + const char *remainder = &token[parser->short_pos + 1]; + + if (opt->value_mode == AP_VALUE_NONE) { + parser->short_pos++; + if (token[parser->short_pos] == '\0') { + parser->index++; + parser->short_pos = 0; + } + } else if (opt->value_mode == AP_VALUE_REQUIRED) { + if (*remainder != '\0') { + value = remainder; + parser->index++; + parser->short_pos = 0; + } else if ((parser->index + 1) < parser->argc) { + value = parser->argv[parser->index + 1]; + parser->index += 2; + parser->short_pos = 0; + } else { + ap_set_error(error, AP_ERR_MISSING_VALUE, token, token); + parser->index++; + parser->short_pos = 0; + return AP_STATUS_ERROR; + } + } else { + if (*remainder != '\0') { + value = remainder; + } + parser->index++; + parser->short_pos = 0; + } + + event->kind = AP_EVENT_OPTION; + event->option = opt; + event->name = token; + event->value = value; + return AP_STATUS_OK; +} + +ap_status_t ap_parser_next(ap_parser_t *parser, const ap_option_t *options, size_t option_count, ap_event_t *event, + ap_error_t *error) +{ + ap_reset_event(event); + ap_reset_error(error); + + if ((parser == NULL) || (options == NULL) || (event == NULL)) { + ap_set_error(error, AP_ERR_INVALID_ARGUMENT, NULL, NULL); + return AP_STATUS_ERROR; + } + + while (parser->index < parser->argc) { + const char *token = parser->argv[parser->index]; + + if (parser->stop_options) { + event->kind = AP_EVENT_POSITIONAL; + event->positional = token; + parser->index++; + return AP_STATUS_OK; + } + + if (parser->short_pos > 0) { + return ap_parse_short_option(parser, options, option_count, event, error); + } + + if ((strcmp(token, "--") == 0)) { + parser->stop_options = true; + parser->index++; + continue; + } + + if ((token[0] != '-') || (token[1] == '\0')) { + event->kind = AP_EVENT_POSITIONAL; + event->positional = token; + parser->index++; + return AP_STATUS_OK; + } + + if (token[1] == '-') { + return ap_parse_long_option(parser, options, option_count, event, error); + } + + parser->short_pos = 1; + return ap_parse_short_option(parser, options, option_count, event, error); + } + + return AP_STATUS_END; +} + +const char *ap_error_code_to_string(ap_error_code_t code) +{ + switch (code) { + case AP_ERR_INVALID_ARGUMENT: + return "invalid argument"; + case AP_ERR_UNKNOWN_OPTION: + return "unknown option"; + case AP_ERR_MISSING_VALUE: + return "missing option value"; + case AP_ERR_UNEXPECTED_VALUE: + return "unexpected option value"; + case AP_ERR_EMPTY_OPTION: + return "empty option"; + case AP_ERR_NONE: + default: + return "no error"; + } +} diff --git a/lib/src/unistd/statfs.c b/lib/src/unistd/statfs.c new file mode 100644 index 00000000..14e6a58b --- /dev/null +++ b/lib/src/unistd/statfs.c @@ -0,0 +1,23 @@ +/// @file statfs.c +/// @brief Filesystem statistics syscall wrappers. +/// @copyright (c) 2014-2024 This file is distributed under the MIT License. +/// See LICENSE.md for details. + +#include "errno.h" +#include "system/syscall_types.h" +#include "sys/statfs.h" +#include "unistd.h" + +int statfs(const char *path, statfs_t *buf) +{ + long __res; + __inline_syscall_2(__res, statfs, path, buf); + __syscall_return(int, __res); +} + +int fstatfs(int fd, statfs_t *buf) +{ + long __res; + __inline_syscall_2(__res, fstatfs, fd, buf); + __syscall_return(int, __res); +} diff --git a/lib/src/vscanf.c b/lib/src/vscanf.c index 19580232..77dbf5db 100644 --- a/lib/src/vscanf.c +++ b/lib/src/vscanf.c @@ -54,8 +54,13 @@ static int __vsscanf(const char *str, const char *s, va_list ap) if (*s == 's') { while (isspace(*str)) ++str; - if (!width) { - width = strcspn(str, " \t\n\r\f\v"); + // Clamp width to the actual token length so str is never + // advanced past the real end of the token. Without this, a + // user-supplied width like %4095s would advance str by 4095 + // bytes even for a short token, reading past the buffer. + int token_len = (int)strcspn(str, " \t\n\r\f\v"); + if (!width || width > token_len) { + width = token_len; } if (!noassign) { char *string = va_arg(ap, char *); @@ -108,6 +113,13 @@ static int __vsscanf(const char *str, const char *s, va_list ap) char *next_sep = strchr(str, next); width = next_sep ? (next_sep - str) : strcspn(str, " \t\n\r\f\v"); } + } else { + // Clamp user-supplied width to the actual token length to + // avoid advancing str past the end of the token. + int token_len = (int)strcspn(str, " \t\n\r\f\v"); + if (width > token_len) { + width = token_len; + } } strncpy(tmp, str, width); diff --git a/userspace/bin/CMakeLists.txt b/userspace/bin/CMakeLists.txt index 2a45ad49..32044dd5 100644 --- a/userspace/bin/CMakeLists.txt +++ b/userspace/bin/CMakeLists.txt @@ -7,6 +7,7 @@ set(PROGRAM_LIST cp.c cpuid.c date.c + df.c echo.c edit.c env.c diff --git a/userspace/bin/df.c b/userspace/bin/df.c new file mode 100644 index 00000000..434b93ce --- /dev/null +++ b/userspace/bin/df.c @@ -0,0 +1,175 @@ +/// @file df.c +/// @brief Report filesystem disk space usage. +/// @copyright (c) 2024 This file is distributed under the MIT License. +/// See LICENSE.md for details. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DF_READ_BUFFER_SIZE 4096 + +static void print_help(const char *prog) +{ + printf("Usage: %s [OPTION]\n", prog); + printf("Show filesystem disk usage for mounted filesystems.\n"); + printf(" -h, --human-readable print sizes in powers of 1024 (e.g., 1023M)\n"); + printf(" --help display this help and exit\n"); +} + +static void print_cli_error(const char *prog, const ap_error_t *error) +{ + const char *token = (error->token != NULL) ? error->token : ""; + printf("%s: %s: %s\n", prog, ap_error_code_to_string(error->code), token); +} + +static int parse_args(int argc, char **argv, bool_t *human) +{ + const ap_option_t options[] = { + {'h', "human-readable", AP_VALUE_NONE, NULL, "print sizes in powers of 1024"}, + {'\0', "help", AP_VALUE_NONE, NULL, "display this help and exit"}, + }; + + ap_parser_t parser; + ap_event_t event; + ap_error_t error; + + ap_parser_init(&parser, argc, argv); + + while (true) { + ap_status_t status = ap_parser_next(&parser, options, sizeof(options) / sizeof(options[0]), &event, &error); + if (status == AP_STATUS_END) { + return 0; + } + if (status == AP_STATUS_ERROR) { + print_cli_error(argv[0], &error); + print_help(argv[0]); + return 1; + } + + if (event.kind == AP_EVENT_POSITIONAL) { + printf("%s: unexpected positional argument: %s\n", argv[0], event.positional); + print_help(argv[0]); + return 1; + } + + if (strcmp(event.option->long_name, "help") == 0) { + print_help(argv[0]); + return 2; + } + if ((event.option->short_name == 'h') || (strcmp(event.option->long_name, "human-readable") == 0)) { + *human = true; + } + } +} + +static const char *to_human_size(unsigned long bytes) +{ + static char output[32]; + const char *suffix[] = {"B", "K", "M", "G", "T"}; + int len = sizeof(suffix) / sizeof(suffix[0]); + int i = 0; + double val = (double)bytes; + while (val >= 1024.0 && i < len - 1) { + val /= 1024.0; + i++; + } + sprintf(output, "%.1f%s", val, suffix[i]); + return output; +} + +static void print_header(bool_t human) +{ + const char *size_col = human ? "Size" : "1K-blocks"; + printf("%-18s %10s %10s %10s %5s %s\n", "Filesystem", size_col, "Used", "Available", "Use%", "Mounted on"); +} + +static void print_mount_usage(const char *source, const char *mountpoint, bool_t human) +{ + statfs_t fs; + if (statfs(mountpoint, &fs) < 0) { + printf("%-18s %10s %10s %10s %5s %s\n", source, "-", "-", "-", "-", mountpoint); + return; + } + + uint32_t blocks_per_k = (fs.f_bsize >= 1024U) ? (fs.f_bsize / 1024U) : 1U; + uint32_t total_k = fs.f_blocks * blocks_per_k; + uint32_t free_k = fs.f_bavail * blocks_per_k; + uint32_t used_k = (total_k > free_k) ? (total_k - free_k) : 0; + + uint32_t use_pct = 0; + if (total_k > 0) { + use_pct = (used_k * 100U) / total_k; + } + + if (human) { + printf("%-18s %10s %10s %10s %4u%% %s\n", source, to_human_size((unsigned long)total_k * 1024UL), to_human_size((unsigned long)used_k * 1024UL), to_human_size((unsigned long)free_k * 1024UL), use_pct, mountpoint); + } else { + printf("%-18s %10u %10u %10u %4u%% %s\n", source, total_k, used_k, free_k, use_pct, mountpoint); + } +} + +int main(int argc, char **argv) +{ + // Open syslog connection. + openlog("df", LOG_CONS | LOG_PID, LOG_USER); + // Set log mask. + setlogmask(LOG_UPTO(LOG_DEBUG)); + + bool_t human = false; + + int parse_rc = parse_args(argc, argv, &human); + if (parse_rc == 2) { + return 0; + } + if (parse_rc != 0) { + return 1; + } + + int fd = open("/proc/mounts", O_RDONLY, 0); + if (fd < 0) { + printf("%s: cannot open '/proc/mounts' (errno=%d)\n", argv[0], errno); + return 1; + } + + char buffer[DF_READ_BUFFER_SIZE + 1]; + ssize_t nread = read(fd, buffer, DF_READ_BUFFER_SIZE); + close(fd); + + if (nread <= 0) { + printf("%s: cannot read '/proc/mounts'\n", argv[0]); + return 1; + } + + buffer[nread] = '\0'; + + print_header(human); + + char *save_line = NULL; + char *line = strtok_r(buffer, "\n", &save_line); + while (line != NULL) { + char source[PATH_MAX]; + char mountpoint[PATH_MAX]; + char fstype[64]; + char options[64]; + + int parsed = sscanf(line, "%4095s %4095s %63s %63s %*s %*s", source, mountpoint, fstype, options); + if (parsed == 4) { + print_mount_usage(source, mountpoint, human); + } + + line = strtok_r(NULL, "\n", &save_line); + } + + return 0; +}