mohanen b mohanen b - 2 months ago 8
C Question

c code to get secondary storage size

I need simple a way to get the secondary storage details (like total size , used and free space ) in a (daemon) c code for linux;

This are the things i tried


  • statvfs - dont know how to get disk details instead of files

  • using system("df -h --total | grep total > disk.stat") in the c code and then read the file.



but the above involves file write and read which is not efficient cause this c code is a daemon which will be polling the system details continuously as input to a graph generation .

if there no is other way , tell me an simple and fast ipc mechanism with example for communication between this bash and c code.

/*
* @breif returns total percentage of secondary storage used
*
* - uses bash command to get storage data and store in a file
* - and use c code retrive the percent of usage from file and return it
*/
int calculate_storage_size( )
{
if ( system("df -h --total | grep total > disk.stat") >= 0 )
{
char *temp_char_ptr = (char *)NULL;
int storage_size_percent = -1;
FILE *fp ;
fp = fopen ("disk.stat" , "r");
if (fp != (FILE *)NULL)
{
temp_char_ptr = (char*) calloc ( 6 , 1 );
fscanf( fp,"%s %s %s %s %d", temp_char_ptr, temp_char_ptr, temp_char_ptr, temp_char_ptr, &storage_size_percent);
}
free (temp_char_ptr);
fclose(fp);
return storage_size_percent;
}
return -1;
}


Thanks and regards in advance

Answer

I would suggest it would be better to let the user specify which mounts should be considered in the total, or use a heuristic to omit system and temporary mounts.

Consider the following example, info.c:

#define  _POSIX_C_SOURCE 200809L
#define  _GNU_SOURCE
#include <stdlib.h>
#include <stdint.h>
#include <inttypes.h>
#include <sys/statvfs.h>
#include <mntent.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>

static void free_array(char **array)
{
    if (array) {
        size_t i;    
        for (i = 0; array[i] != NULL; i++) {
            free(array[i]);
            array[i] = NULL;
        }
        free(array);
    }
}

static char **normal_mounts(void)
{
    char         **list = NULL, **temp;
    size_t         size = 0;
    size_t         used = 0;
    char           buffer[4096];
    struct mntent  entry;
    FILE          *mounts;

    mounts = fopen("/proc/mounts", "r");
    if (!mounts)
        return NULL;

    while (getmntent_r(mounts, &entry, buffer, sizeof buffer) == &entry)
        if (strcmp(entry.mnt_fsname, "tmpfs") &&
            strcmp(entry.mnt_fsname, "swap")  &&
            strcmp(entry.mnt_dir, "/proc")    && strncmp(entry.mnt_dir, "/proc/", 6) &&
            strcmp(entry.mnt_dir, "/boot")    && strncmp(entry.mnt_dir, "/boot/", 6) &&
            strcmp(entry.mnt_dir, "/sys")     && strncmp(entry.mnt_dir, "/sys/", 5) &&
            strcmp(entry.mnt_dir, "/run")     && strncmp(entry.mnt_dir, "/run/", 5) &&
            strcmp(entry.mnt_dir, "/dev")     && strncmp(entry.mnt_dir, "/dev/", 5) &&
            strcmp(entry.mnt_dir, "/mnt")     && strncmp(entry.mnt_dir, "/mnt/", 5) &&
            strcmp(entry.mnt_dir, "/media")   && strncmp(entry.mnt_dir, "/media/", 7) &&
            strcmp(entry.mnt_dir, "/var/run") && strncmp(entry.mnt_dir, "/var/run/", 9)) {

            if (used >= size) {
                size = (used | 15) + 17;
                temp = realloc(list, size * sizeof list[0]);
                if (!temp) {
                    endmntent(mounts);
                    free_array(list);
                    errno = ENOMEM;
                    return NULL;
                }
                list = temp;
            }

            if (!(list[used++] = strdup(entry.mnt_dir))) {
                endmntent(mounts);
                free_array(list);
                errno = ENOMEM;
                return NULL;
            }
        }

    if (ferror(mounts) || !feof(mounts)) {
        endmntent(mounts);
        free_array(list);
        errno = EIO;
        return NULL;
    } else
        endmntent(mounts);

    if (!used) {
        free_array(list);
        errno = 0;
        return NULL;
    }

    if (size != used + 1) {
        size = used + 1;
        temp = realloc(list, size * sizeof list[0]);
        if (!temp) {
            free_array(list);
            errno = ENOMEM;
            return NULL;
        }
        list = temp;
    }

    list[used] = NULL;
    errno = 0;
    return list;
}

static int statistics(const char **mountpoint, uint64_t *bytes_total, uint64_t *bytes_free)
{
    struct statvfs  info;
    uint64_t        btotal = 0;
    uint64_t        bfree = 0;
    size_t          i;

    if (!mountpoint)
        return errno = EINVAL;

    for (i = 0; mountpoint[i] != NULL; i++)
        if (statvfs(mountpoint[i], &info) != -1) {
            btotal += (uint64_t)info.f_frsize * (uint64_t)info.f_blocks;
            bfree  += (uint64_t)info.f_bsize * (uint64_t)info.f_bavail;
        } else
            return errno;

    if (bytes_total)
        *bytes_total = btotal;
    if (bytes_free)
        *bytes_free = bfree;

    return 0;
}

int main(int argc, char *argv[])
{
    uint64_t total = 0;
    uint64_t nfree = 0;

    if (argc > 1) {
        if (statistics((const char **)argv + 1, &total, &nfree)) {
            fprintf(stderr, "%s.\n", strerror(errno));
            return EXIT_FAILURE;
        }
    } else {
        char **mounts = normal_mounts();
        size_t i;
        if (!mounts) {
            if (errno)
                fprintf(stderr, "Error determining file systems: %s.\n", strerror(errno));
            else
                fprintf(stderr, "No normal file systems found.\n");
            return EXIT_FAILURE;
        }
        fprintf(stderr, "Considering mount points");
        for (i = 0; mounts[i] != NULL; i++)
            fprintf(stderr, " %s", mounts[i]);
        fprintf(stderr, "\n");

        if (statistics((const char **)mounts, &total, &nfree)) {
            fprintf(stderr, "%s.\n", strerror(errno));
            return EXIT_FAILURE;
        }
        free_array(mounts);
    }

    printf("%20" PRIu64 " bytes total\n", total);
    printf("%20" PRIu64 " bytes free\n",  nfree); 
    return EXIT_SUCCESS;
}

The statistics() function takes a NULL-terminated array of mount points, and two pointers to unsigned 64-bit integers. The function returns 0 if successful, and a nonzero errno code otherwise. If successful, the function will set the total number of bytes in the filesystems to the first integer, and the number of free bytes in the second.

If you supply one or more mounts points as command line arguments, only those are considered. (POSIX says argv[argc] == NULL, so this usage is safe.)

Otherwise, the normal_mounts() function is used to parse /proc/mounts to obtain a list of "normal" mount points. The function uses getmntent() to read each entry (line) from the kernel-provided pseudo-file. All tmpfs (ramdisks) and swap filesystems are excluded, as are those mounted at or under /proc, /boot, /sys, /run, /dev, /mnt, /media, and /var/run. This is just a crude heuristic, not a known good approach.

In a daemon, or even in a graphical application, you call only (your equivalent of) the statistics() function, with the same array of mount points. You could even consider tracking each mount point separately, and let the user filter and combine the information they are interested in. In fact, I would recommend that: I personally might be interested in seeing the fluctuations in my temporary file usage (on machines where /tmp and /var/tmp are tmpfs mounts), as well as track my long-term usage of /home.

In a daemon, you can use HUP or USR1 or USR2 signals to indicate when the user wants you to reload the configuration -- the mount point list, here. I do not believe it would be that interesting to integrate it to DBUS for detecting removable media mounts/unmounts, but of course you can if you think it useful.

If you compile the above program using e.g.

gcc -Wall -O2 info.c -o info

and run

./info

it will output something like

Considering mount points / /home
        119989497856 bytes total
         26786156544 bytes free

where the first line is output to standard error, and the bytes lines to standard output. You can also specifically name the mount points -- make sure they are different, as the code does not check for duplicate mounts --:

./info /home /tmp

If you are wondering how you could determine whether two directories are on the same mount or not: call stat(path1, &info1) on one, and stat(path2, &info2) on the other. If and only if (info1.st_dev == info2.st_dev), the two paths are on the same mount. (One device may be mounted multiple times at different points, using e.g. bind mounts, but usually the above check suffices.)

If you find all the above code annoying, you can always rely on the df utility. To ensure the output is in the C/POSIX locale (and not, say, in French or Finnish), use

handle = popen("LANG=C LC_ALL=C df -Pl", "r");

or similar, and read the output using len = getline(&line, &size, handle).