Viewing: statvfs.c
📄 statvfs.c (Read Only) ⬅ To go back
// SPDX-License-Identifier: BSD-3-Clause
/*
 * Copyright (c) 2018, Tulio A M Mendes <[email protected]>
 * All rights reserved.
 * See LICENSE for details.
 *
 * Source: https://github.com/tadryanom/AdrOS
 */

#include "sys/statvfs.h"
#include "sys/stat.h"
#include "syscall.h"
#include "errno.h"
#include "unistd.h"
#include "string.h"

/* Find the best-matching mount entry in /proc/mounts for a given path.
 * Returns 1 if found, 0 otherwise.  Fills fstype_out and flags_out. */
static int find_mount_for_path(const char* path,
                                char* fstype_out, size_t fstype_sz,
                                char* source_out, size_t source_sz,
                                unsigned long* flags_out) {
    int fd = open("/proc/mounts", 0);
    if (fd < 0) return 0;

    char mbuf[2048];
    int n = read(fd, mbuf, sizeof(mbuf) - 1);
    close(fd);
    if (n <= 0) return 0;
    mbuf[n] = '\0';

    /* Find longest-prefix match among mount points */
    int best_len = -1;
    char best_fstype[32] = "";
    char best_source[64] = "";
    unsigned long best_flags = 0;

    char* line = mbuf;
    while (line && *line) {
        char* nl = line;
        while (*nl && *nl != '\n') nl++;
        int eol = (*nl == '\n');
        if (eol) *nl = '\0';

        /* Parse: source mountpoint fstype options ... */
        char* src = line;
        char* mp = src;
        while (*mp && *mp != ' ') mp++;
        if (*mp) { *mp = '\0'; mp++; }

        char* fst = mp;
        while (*fst && *fst != ' ') fst++;
        if (*fst) { *fst = '\0'; fst++; }

        char* opts = fst;
        while (*opts && *opts != ' ') opts++;
        if (*opts) { *opts = '\0'; opts++; }

        /* Check if path starts with mountpoint (prefix match) */
        int mplen = 0;
        const char* p = mp;
        while (*p++) mplen++;

        int match = 0;
        if (mplen == 1 && mp[0] == '/') {
            match = 1;  /* root mount matches everything */
        } else {
            const char* pp = path;
            const char* mmp = mp;
            while (*mmp && *pp && *mmp == *pp) { mmp++; pp++; }
            if (*mmp == '\0' && (*pp == '\0' || *pp == '/'))
                match = 1;
        }

        if (match && mplen > best_len) {
            best_len = mplen;
            strncpy(best_fstype, fst, sizeof(best_fstype) - 1);
            best_fstype[sizeof(best_fstype) - 1] = '\0';
            strncpy(best_source, src, sizeof(best_source) - 1);
            best_source[sizeof(best_source) - 1] = '\0';
            /* Parse options for ro/rw */
            best_flags = 0;
            if (opts) {
                /* Check for "ro" option */
                const char* o = opts;
                while (*o) {
                    if ((o == opts || *(o-1) == ',') &&
                        o[0] == 'r' && o[1] == 'o' &&
                        (o[2] == ',' || o[2] == ' ' || o[2] == '\0')) {
                        best_flags |= 1; /* MS_RDONLY */
                        break;
                    }
                    o++;
                }
            }
        }

        line = eol ? nl + 1 : nl;
    }

    if (best_len < 0) return 0;

    if (fstype_out) { strncpy(fstype_out, best_fstype, fstype_sz - 1); fstype_out[fstype_sz - 1] = '\0'; }
    if (source_out) { strncpy(source_out, best_source, source_sz - 1); source_out[source_sz - 1] = '\0'; }
    if (flags_out) *flags_out = best_flags;
    return 1;
}

int statvfs(const char* path, struct statvfs* buf) {
    struct stat st;
    if (stat(path, &st) < 0)
        return -1;

    unsigned long bsize = (unsigned long)st.st_blksize;
    if (bsize == 0) bsize = 4096;

    /* Find mount info for this path */
    char fstype[32] = "unknown";
    char source[64] = "none";
    unsigned long mflags = 0;
    find_mount_for_path(path, fstype, sizeof(fstype), source, sizeof(source), &mflags);

    buf->f_bsize   = bsize;
    buf->f_frsize  = bsize;
    buf->f_fsid    = (unsigned long)st.st_dev;
    buf->f_flag    = mflags;  /* MS_RDONLY etc. */
    buf->f_namemax = 255;

    /* Derive block counts based on filesystem type */
    if (strcmp(fstype, "tmpfs") == 0) {
        /* tmpfs: size is limited by available memory */
        buf->f_blocks = 16384;  /* ~64 MB assuming 4K blocks */
        buf->f_bfree  = 8192;
        buf->f_bavail = 8192;
        buf->f_files  = 256;
        buf->f_ffree  = 128;
        buf->f_favail = 128;
    } else if (strcmp(fstype, "procfs") == 0 || strcmp(fstype, "devfs") == 0) {
        /* Virtual/pseudo filesystems — zero blocks */
        buf->f_blocks = 0;
        buf->f_bfree  = 0;
        buf->f_bavail = 0;
        buf->f_files  = 64;
        buf->f_ffree  = 32;
        buf->f_favail = 32;
    } else if (strcmp(fstype, "overlayfs") == 0) {
        /* Overlay: report based on stat info */
        buf->f_blocks = (unsigned long)st.st_size / bsize + 512;
        buf->f_bfree  = 256;
        buf->f_bavail = 256;
        buf->f_files  = 256;
        buf->f_ffree  = 128;
        buf->f_favail = 128;
    } else {
        /* Disk-based (fat, ext2): estimate from stat */
        buf->f_blocks = (unsigned long)st.st_size / bsize + 512;
        buf->f_bfree  = 256;
        buf->f_bavail = 256;
        buf->f_files  = 256;
        buf->f_ffree  = 128;
        buf->f_favail = 128;
    }

    return 0;
}