3
\$\begingroup\$

This code takes a list of filenames as arguments or from stdin (or from a provided file) and dumps a list of disk blocks used by those files.

An attempt is made to use the linux ioctl function fiemap and if that fails (ex. on FAT filesystems) falls back to using the fibmap function.

This code compiles with VS Community 2017 on a Ubuntu Bionic system.

(Any suggestions as to better argument switches [such as using -o for output file and something else for offset will also be appreciated)

    /*
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/

/*
*  Copyright (C) 2018 
*  Author afuna
*  parts Copyright (C) 2010 Canonical: Colin Ian King,  [email protected]
*/



#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <fcntl.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>

#include <linux/fs.h>
#include <linux/fiemap.h>

void syntax(char **argv)
{
    printf("lsblocks - Dumps a list of blocks occupied by a file using FIEMAP or FIBMAP.\n");
    printf("\nUsage: %s [file1 file2...]\n", argv[0]);
    printf("\nYou can provide a list of newline delimited filenames from stdin.\n");
    printf("\nOptions:\n");
    printf(
        "  -h           show this help screen.\n"
        "  -v           print file information to stderr before dumping block numbers.\n"
        "               use -vv to print to the output file\n"
        "  -i <path>    read filenames from <path>\n"
        "  -s <path>    save output to <path>\n"
        "  -o <count>   offset values by <count>.\n"
        "  -d <dir>     change working directory to <dir>.\n"
        "  -f <format>  format block values using the given string;\n"
        "               you must provide the terminator (\\n or ' ' etc.)\n"
    );
}

static unsigned long long offset = 0;
static int verbosity = 0;
static char* format = NULL;
static char* infile = NULL;
static char* outfile = NULL;

static FILE* fd_in = NULL;
static FILE* fd_out = NULL;
static FILE* fd_info = NULL;

struct fiemap *read_fiemap(int fd)
{
    struct fiemap *fiemap;
    int extents_size;

    if ((fiemap = (struct fiemap*)malloc(sizeof(struct fiemap))) == NULL) {
        fprintf(stderr, "Out of memory allocating fiemap\n");
        return NULL;
    }
    memset(fiemap, 0, sizeof(struct fiemap));

    fiemap->fm_start = 0;
    fiemap->fm_length = ~0;     /* Lazy */
    fiemap->fm_flags = 0;
    fiemap->fm_extent_count = 0;
    fiemap->fm_mapped_extents = 0;

    /* Find out how many extents there are */
    if (ioctl(fd, FS_IOC_FIEMAP, fiemap) < 0) {
        perror("fiemap ioctl() failed: ");
        //fprintf(stderr, "fiemap ioctl() failed\n");
        return NULL;
    }

    /* Read in the extents */
    extents_size = sizeof(struct fiemap_extent) *
        (fiemap->fm_mapped_extents);

    /* Resize fiemap to allow us to read in the extents */
    if ((fiemap = (struct fiemap*)realloc(fiemap, sizeof(struct fiemap) +
        extents_size)) == NULL) {
        perror("Out of memory allocating fiemap: ");
        return NULL;
    }

    memset(fiemap->fm_extents, 0, extents_size);
    fiemap->fm_extent_count = fiemap->fm_mapped_extents;
    fiemap->fm_mapped_extents = 0;

    if (ioctl(fd, FS_IOC_FIEMAP, fiemap) < 0) {
        perror("fiemap ioctl() failed: ");
        return NULL;
    }

    return fiemap;
}

void dump_fiemap(struct fiemap *fiemap, char *filename) {
    if (verbosity > 0)
        fprintf(fd_info, "blocksize %d\n", 512);

    for (int i = 0; i < fiemap->fm_mapped_extents; i++) {
        unsigned long long base = offset + (fiemap->fm_extents[i].fe_physical / 512);
        unsigned long long count = fiemap->fm_extents[i].fe_length / 512;
        for (int j = 0; j < count; j++) {
            fprintf(fd_out, format, base++);
        }
    }
}

void dump_fibmap(int fd) {
    int block, blocksize, blkcnt;
    struct stat st;

    if (ioctl(fd, FIGETBSZ, &blocksize)) {
        perror("FIBMAP ioctl failed: ");
        return;
    }

    if (fstat(fd, &st)) {
        perror("fstat error");
        return;
    }

    blkcnt = (st.st_size + blocksize - 1) / blocksize;

    if (verbosity > 0)
        fprintf(fd_info, "size %d blocks %d blocksize %d\n", (int)st.st_size, blkcnt, blocksize);

    for (int i = 0; i < blkcnt; i++) {
        block = i;
        if (ioctl(fd, FIBMAP, &block)) {
            perror("FIBMAP ioctl failed: ");
        }
        else {
            int result;
            result = fprintf(fd_out, format, offset + block);
            if (result < 0) {
                perror("failed write: ");
            }
        }
    }
}

void dump(char* path)
{
    int fd;

    if ((fd = open(path, O_RDONLY)) < 0) {
        fprintf(stderr, "Cannot open file %s\n", path);
        perror(NULL);
    }
    else {
        fprintf(fd_info, "Dumping file %s ", path);

        struct fiemap *fiemap;

        fiemap = read_fiemap(fd);
        fiemap != NULL ? dump_fiemap(fiemap, path) : dump_fibmap(fd);
        close(fd);
    }
}


int main(int argc, char **argv)
{
    char* path = NULL;

    opterr = 0;

    int opt = 0;
    while ((opt = getopt(argc, argv, "o:f:d:i:s:vh")) != -1)
        switch (opt)
        {
        case 'o':
            offset = strtoull(optarg, NULL, 10);
            break;
        case 'f':
            format = optarg;
            break;
        case 'd':
            if (chdir(optarg) != 0) {
                perror("Failed to change working directory: ");
                return EXIT_FAILURE;
            }
            break;
        case 'i':
            infile = optarg;
            break;
        case 's':
            outfile = optarg;
        case 'v':
            verbosity++;
            break;
        case 'h':
            syntax(argv);
            return EXIT_SUCCESS;
        case '?':
            if (optopt == 'c')
                fprintf(stderr, "Option -%c requires an argument.\n", optopt);
            else if (isprint(optopt))
                fprintf(stderr, "Unknown option '-%c'.\n", optopt);
            else
                fprintf(stderr, "Unknown option character '\\x%x'.\n", optopt);
            return EXIT_FAILURE;
        default:
            abort();
        }

    if (format == NULL)
        format = "%llu\n";

    if (infile == NULL)
        fd_in = stdin;
    else if ((fd_in = fopen(infile, "rb")) == NULL) {
        perror("Cannot open input file: ");
        exit(EXIT_FAILURE);
    }

    if (outfile == NULL)
        fd_out = stdout;
    else if ((fd_out = fopen(outfile, "w")) == NULL) {
        perror("Cannot open output file: ");
        return EXIT_FAILURE;
    }

    fd_info = verbosity <= 1 ? stderr : fd_out;

    if (optind == argc | infile != NULL) { // read stdin or infile
        char *buffer;
        size_t bufsize = 2048;
        size_t characters;

        buffer = (char *)malloc(bufsize * sizeof(char));
        if (buffer == NULL)
        {
            perror("Unable to allocate buffer");
            return EXIT_FAILURE;
        }

        int size = 0;
        while ((size = getline(&buffer, &bufsize, fd_in)) > 0)
        {
            buffer[size - 1] = '\0';
            dump(buffer);
        }
    }
    else {
        for (int idx = optind; idx < argc; idx++)
            dump(argv[idx]);
    }

    if (fd_in != stdin && !fclose(fd_in)) {
        perror("Failed to close input file: ");
    }

    fflush(fd_out);
    if (fd_out != stdout && fclose(fd_out)) {
        perror("Failed to close output file: ");
    }

    return EXIT_SUCCESS;
}
\$\endgroup\$
0

1 Answer 1

1
\$\begingroup\$

Command options

The -i option isn't needed - any competent user can simply use xargs to achieve the same effect with more flexibility (e.g. using NUL-delimited stream, to correctly handle names with embedded newline characters).

Similarly, the -s option is pointless, as shell redirection is simpler for the user (more consistent than finding a program-specific option).

The -d option doesn't have value either, as that's something that all shells provide.

The -f option is dangerous, as there's no validation that the string contains a single %llu conversion, with no other conversion specifiers (e.g. %n, which can cause modification of the process memory).

        else if (isprint(optopt))
            fprintf(stderr, "Unknown option '-%c'.\n", optopt);
        else
            fprintf(stderr, "Unknown option character '\\x%x'.\n", optopt);

These error messages should tell the user to use the -h option for the full help.

    case 's':
        outfile = optarg;
    case 'v':
        verbosity++;
        break;

I don't think that -s should also increase verbosity, so I recommend adding break; before case 'v'.

Memory allocation

Consider

if ((fiemap = (struct fiemap*)malloc(sizeof(struct fiemap))) == NULL) {
    fprintf(stderr, "Out of memory allocating fiemap\n");
    return NULL;
}

In C, malloc() returns a void*, which is assignable to any other pointer type, so the cast is just visual clutter. Common practice is to use the size of the pointer target, so we don't have to find the definition of fiemap to see whether it matches:

fiemap = malloc(sizeof *fiemap)

All the error returns fail to free fiemap, meaning that memory is leaked in these cases.

We also have a leak here (a common antipattern in C):

fiemap = realloc(fiemap, sizeof *fiemap + extents_size)

When realloc() fails, we overwrite the pointer variable with a null pointer, meaning that we are unable to free the memory. Always assign to a temporary pointer, and only overwrite the original if the realloc() was successful.

In main() there's a pointless multiplication by sizeof (char) (which is always 1, since sizeof measures in units of char).

Improve function safety

void dump_fiemap(struct fiemap *fiemap, char *filename)

There's no reason to be modifying either of these values, so make the function safer with const:

void dump_fiemap(const struct fiemap *fiemap, const char *filename)

The filename argument doesn't even seem to be used - just remove it.

Similarly, dump() should accept a const char*, and format should be a const char* (since we assign it from a string literal).

Careful with integer conversions

fiemap->fm_length = ~0;

Can you be sure that promotion of the int value (-1) to __u64 works as you expect? I'd be kinder to the reader and write ~(__u64)0, or perhaps just use the provided FIEMAP_MAX_OFFSET.

extents_size ought to be a size_t, not int. And the loop indices in dump_fiemap() should be unsigned, not signed.

These simple inconsistencies suggest the code has been compiled with insufficient warnings enabled. I recommend gcc -Wall -Wextra -Wwrite-strings -Wno-parentheses -Wpedantic -Warray-bounds -Wstrict-prototypes -Wconversion, which will highlight these problems, and more.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.