Skip to main content
Integrate comments.
Source Link
Thomas Weller
  • 1.7k
  • 14
  • 25
/**
 * @file fsize.c
 * @license This file is licensed under the GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007. You may obtain a copy of this license at https://www.gnu.org/licenses/gpl-3.0.en.html.
 * @author Tushar Chaurasia (Dark-CodeX)
 */
/**
 * @file fsize.c
 * @license This file is licensed under the GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007. You may obtain a copy of this license at https://www.gnu.org/licenses/gpl-3.0.en.html.
 * @author Tushar Chaurasia (Dark-CodeX)
 */
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <stdbool.h>
#include <ctype.h>
#if defined _WIN32 || defined _WIN64 || defined __CYGWIN__
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#else
#include <sys/stat.h>
#include <sys/types.h>
#endif

#define FSIZE_VER "1.0.0"

void show_help(void)
{
    puts(
        "Usage: [option] [files]"[files]\n"
        "Options:"\n"
        "    --help                   Display this help message."\n"
        "    --version                Display the version of the app."\n"
        "    --size=<unit_of_byte>    Define output unit."\n"
        "          =bit, bits             Output in bits."\n"
        "          =byte, bytes           Output in bytes (default)."\n"
        "          =kib                   Output in kibibytes."\n"
        "          =mib                   Output in mebibytes."\n"
        "          =gib                   Output in gibibytes."\n"
        "          =kb                    Output in kilobytes."\n"
        "          =mb                    Output in megabytes."\n"
        "          =gb                    Output in gigabytes."\n"
    );
}

bool is_directory(const char* loc)
{
    if (!loc)
        return false;
#if defined _WIN32 || defined _WIN64 || defined __CYGWIN__
    DWORD fileAttributes = GetFileAttributesA(loc);
    if (fileAttributes == INVALID_FILE_ATTRIBUTES)
        return false;
    return fileAttributes & FILE_ATTRIBUTE_DIRECTORY;
#else
    struct stat buffer;
    return stat(loc, &buffer) == 0 && S_ISDIR(buffer.st_mode);
#endif
}

typedef struct UNIT
{
    const char* input_name;
    const char* output_name;
    const char* output_format;
    const unsigned long long divider;
} UNIT;


UNIT UNITS[] = {
    // Put the default unit first
    { "byte", "bytes", "%0.f", 8ULL },
    {"bit", "bits", "%0.f", 1ULL},
    {"bits", "bits", "%0.f", 1ULL},
    {"bytes", "bytes", "%0.f", 8ULL},
    {"kib", "kibibytes" , "%0.3f", 8ULL<<10},
    {"mib", "mebibytes", "%0.3f", 8ULL<<20},
    {"gib", "gibibytes", "%0.3f", 8ULL<<30},
    {"kb", "kilobytes", "%0.3f", 8ULL*1000},
    {"mb", "megabytes", "%0.3f", 8ULL*1000*1000},
    {"gb", "gigabytes", "%0.3f", 8ULL*1000*1000*1000},
    // Put the "no unit" last
    { NULL, NULL, NULL, 0 },
};

UNIT* parse_unit(char* unit_name, int const argc)
{
    UNIT* NO_UNIT = &UNITS[sizeof(UNITS) / sizeof(UNITUNITS[0]) - 1];
    UNIT* result = &UNITS[0];

    // Parse unit as lower case
    for (int c = 0; unit_name[c]; ++c) {
        unit_name[c] = (char) tolower((unsigned char)unit_name[c]);
    }

    // Unit lookup by input name
    bool found = false;
    for (size_t j = 0; UNITS[j].input_name != NULL; j++)
    {
        if (strcmp(unit_name, UNITS[j].input_name) == 0)
        {
            result =  &UNITS[j];
            found = true;
        }
    }

    // Error handling
    if (!found)
    {
        fprintf(stderr, "err: '%s' is not a valid unit, try using '--help' flag\n", unit_name);
        return NO_UNIT;
    }

    if (argc == 2)
    {
        fprintf(stderr, "err: no file was given\n");
        return NO_UNIT;
    }
    return result;
}

void print_file_size(char* file_name, UNIT unit)
{
    if (is_directory(file_name))
    {
        fprintf(stderr, "err: '%s': Is a directory\n", file_name);
        return;
    }
    FILE* fptr = fopen(file_name, "rb");
    if (!fptr)
    {
        fprintf(stderr, "err: fopen(): '%s': %s\n", file_name, strerror(errno));
        return;
    }
    int result = fseek(fptr, 0, SEEK_END);
    if (result != 0)
    {
        fprintf(stderr, "err: fseek(): '%s': %s\n", file_name, strerror(errno));
        fclose(fptr);
        return;
    }
    size_t size_in_bytes = ftell(fptr);
    fclose(fptr);

    char format[] = "xxxxxx %s : %s\n";
    sprintf_s(format, sizeof(format) - 1, "%s %%s : %%s\n", unit.output_format);
    printf(format, 8.f * (float)size_in_bytes / (float)unit.divider, unit.output_name, file_name);
}

int main(int argc, char** argv)
{
    if (argc == 1)
    {
        fprintf(stderr, "err: no file was given\n");
        return EXIT_FAILURE;
    }

    int current_arg = 1;
    if (strcmp(argv[current_arg], "--help") == 0)
    {
        show_help();
        return EXIT_SUCCESS;
    }

    if (strcmp(argv[current_arg], "--version") == 0)
    {
        printf("%s: %s\n", argv[0], FSIZE_VER);
        return EXIT_SUCCESS;
    }

    UNIT* unit = &UNITS[0];
    if (strncmp(argv[1]argv[current_arg], "--size=", sizeof("--size=") - 1) == 0)
    {
        unit = parse_unit(argv[current_arg] + sizeof("--size=") - 1, argc);
        if (unit->input_name == NULL) return EXIT_FAILURE;
        current_arg++;
    }

    for (; current_arg < argc; current_arg++)
    {
        print_file_size(argv[current_arg], *unit);
    }
    return EXIT_SUCCESS;
}

After: 174180 lines, 4 helper methods, main has 33 lines and the 5 steps of processing are clearly visible. No switch/case and no if/else cascade.

/**
 * @file fsize.c
 * @license This file is licensed under the GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007. You may obtain a copy of this license at https://www.gnu.org/licenses/gpl-3.0.en.html.
 * @author Tushar Chaurasia (Dark-CodeX)
 */
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <stdbool.h>
#include <ctype.h>
#if defined _WIN32 || defined _WIN64 || defined __CYGWIN__
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#else
#include <sys/stat.h>
#include <sys/types.h>
#endif

#define FSIZE_VER "1.0.0"

void show_help(void)
{
    puts(
        "Usage: [option] [files]"
        "Options:"
        "    --help                   Display this help message."
        "    --version                Display the version of the app."
        "    --size=<unit_of_byte>    Define output unit."
        "          =bit, bits             Output in bits."
        "          =byte, bytes           Output in bytes (default)."
        "          =kib                   Output in kibibytes."
        "          =mib                   Output in mebibytes."
        "          =gib                   Output in gibibytes."
        "          =kb                    Output in kilobytes."
        "          =mb                    Output in megabytes."
        "          =gb                    Output in gigabytes."
    );
}

bool is_directory(const char* loc)
{
    if (!loc)
        return false;
#if defined _WIN32 || defined _WIN64 || defined __CYGWIN__
    DWORD fileAttributes = GetFileAttributesA(loc);
    if (fileAttributes == INVALID_FILE_ATTRIBUTES)
        return false;
    return fileAttributes & FILE_ATTRIBUTE_DIRECTORY;
#else
    struct stat buffer;
    return stat(loc, &buffer) == 0 && S_ISDIR(buffer.st_mode);
#endif
}

typedef struct UNIT
{
    const char* input_name;
    const char* output_name;
    const char* output_format;
    const unsigned long long divider;
} UNIT;


UNIT UNITS[] = {
    // Put the default unit first
    { "byte", "bytes", "%0.f", 8ULL },
    {"bit", "bits", "%0.f", 1ULL},
    {"bits", "bits", "%0.f", 1ULL},
    {"bytes", "bytes", "%0.f", 8ULL},
    {"kib", "kibibytes" , "%0.3f", 8ULL<<10},
    {"mib", "mebibytes", "%0.3f", 8ULL<<20},
    {"gib", "gibibytes", "%0.3f", 8ULL<<30},
    {"kb", "kilobytes", "%0.3f", 8ULL*1000},
    {"mb", "megabytes", "%0.3f", 8ULL*1000*1000},
    {"gb", "gigabytes", "%0.3f", 8ULL*1000*1000*1000},
    // Put the "no unit" last
    { NULL, NULL, NULL, 0 },
};

UNIT* parse_unit(char* unit_name, int const argc)
{
    UNIT* NO_UNIT = &UNITS[sizeof(UNITS) / sizeof(UNIT) - 1];
    UNIT* result = &UNITS[0];

    // Parse unit as lower case
    for (int c = 0; unit_name[c]; ++c) {
        unit_name[c] = (char) tolower(unit_name[c]);
    }

    // Unit lookup by input name
    bool found = false;
    for (size_t j = 0; UNITS[j].input_name != NULL; j++)
    {
        if (strcmp(unit_name, UNITS[j].input_name) == 0)
        {
            result =  &UNITS[j];
            found = true;
        }
    }

    // Error handling
    if (!found)
    {
        fprintf(stderr, "err: '%s' is not a valid unit, try using '--help' flag\n", unit_name);
        return NO_UNIT;
    }

    if (argc == 2)
    {
        fprintf(stderr, "err: no file was given\n");
        return NO_UNIT;
    }
    return result;
}

void print_file_size(char* file_name, UNIT unit)
{
    if (is_directory(file_name))
    {
        fprintf(stderr, "err: '%s': Is a directory\n", file_name);
        return;
    }
    FILE* fptr = fopen(file_name, "rb");
    if (!fptr)
    {
        fprintf(stderr, "err: fopen(): '%s': %s\n", file_name, strerror(errno));
        return;
    }
    fseek(fptr, 0, SEEK_END);
    size_t size_in_bytes = ftell(fptr);
    fclose(fptr);

    char format[] = "xxxxxx %s : %s\n";
    sprintf_s(format, sizeof(format) - 1, "%s %%s : %%s\n", unit.output_format);
    printf(format, 8.f * (float)size_in_bytes / (float)unit.divider, unit.output_name, file_name);
}

int main(int argc, char** argv)
{
    if (argc == 1)
    {
        fprintf(stderr, "err: no file was given\n");
        return EXIT_FAILURE;
    }

    int current_arg = 1;
    if (strcmp(argv[current_arg], "--help") == 0)
    {
        show_help();
        return EXIT_SUCCESS;
    }

    if (strcmp(argv[current_arg], "--version") == 0)
    {
        printf("%s: %s\n", argv[0], FSIZE_VER);
        return EXIT_SUCCESS;
    }

    UNIT* unit = &UNITS[0];
    if (strncmp(argv[1], "--size=", sizeof("--size=") - 1) == 0)
    {
        unit = parse_unit(argv[current_arg] + sizeof("--size=") - 1, argc);
        if (unit->input_name == NULL) return EXIT_FAILURE;
        current_arg++;
    }

    for (; current_arg < argc; current_arg++)
    {
        print_file_size(argv[current_arg], *unit);
    }
    return EXIT_SUCCESS;
}

After: 174 lines, 4 helper methods, main has 33 lines and the 5 steps of processing are clearly visible. No switch/case and no if/else cascade.

/**
 * @file fsize.c
 * @license This file is licensed under the GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007. You may obtain a copy of this license at https://www.gnu.org/licenses/gpl-3.0.en.html.
 * @author Tushar Chaurasia (Dark-CodeX)
 */
/**
 * @file fsize.c
 * @license This file is licensed under the GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007. You may obtain a copy of this license at https://www.gnu.org/licenses/gpl-3.0.en.html.
 * @author Tushar Chaurasia (Dark-CodeX)
 */
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <stdbool.h>
#include <ctype.h>
#if defined _WIN32 || defined _WIN64 || defined __CYGWIN__
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#else
#include <sys/stat.h>
#include <sys/types.h>
#endif

#define FSIZE_VER "1.0.0"

void show_help(void)
{
    puts(
        "Usage: [option] [files]\n"
        "Options:\n"
        "    --help                   Display this help message.\n"
        "    --version                Display the version of the app.\n"
        "    --size=<unit_of_byte>    Define output unit.\n"
        "          =bit, bits             Output in bits.\n"
        "          =byte, bytes           Output in bytes (default).\n"
        "          =kib                   Output in kibibytes.\n"
        "          =mib                   Output in mebibytes.\n"
        "          =gib                   Output in gibibytes.\n"
        "          =kb                    Output in kilobytes.\n"
        "          =mb                    Output in megabytes.\n"
        "          =gb                    Output in gigabytes.\n"
    );
}

bool is_directory(const char* loc)
{
    if (!loc)
        return false;
#if defined _WIN32 || defined _WIN64 || defined __CYGWIN__
    DWORD fileAttributes = GetFileAttributesA(loc);
    if (fileAttributes == INVALID_FILE_ATTRIBUTES)
        return false;
    return fileAttributes & FILE_ATTRIBUTE_DIRECTORY;
#else
    struct stat buffer;
    return stat(loc, &buffer) == 0 && S_ISDIR(buffer.st_mode);
#endif
}

typedef struct UNIT
{
    const char* input_name;
    const char* output_name;
    const char* output_format;
    const unsigned long long divider;
} UNIT;


UNIT UNITS[] = {
    // Put the default unit first
    { "byte", "bytes", "%0.f", 8ULL },
    {"bit", "bits", "%0.f", 1ULL},
    {"bits", "bits", "%0.f", 1ULL},
    {"bytes", "bytes", "%0.f", 8ULL},
    {"kib", "kibibytes" , "%0.3f", 8ULL<<10},
    {"mib", "mebibytes", "%0.3f", 8ULL<<20},
    {"gib", "gibibytes", "%0.3f", 8ULL<<30},
    {"kb", "kilobytes", "%0.3f", 8ULL*1000},
    {"mb", "megabytes", "%0.3f", 8ULL*1000*1000},
    {"gb", "gigabytes", "%0.3f", 8ULL*1000*1000*1000},
    // Put the "no unit" last
    { NULL, NULL, NULL, 0 },
};

UNIT* parse_unit(char* unit_name, int const argc)
{
    UNIT* NO_UNIT = &UNITS[sizeof(UNITS) / sizeof(UNITS[0]) - 1];
    UNIT* result = &UNITS[0];

    // Parse unit as lower case
    for (int c = 0; unit_name[c]; ++c) {
        unit_name[c] =  tolower((unsigned char)unit_name[c]);
    }

    // Unit lookup by input name
    bool found = false;
    for (size_t j = 0; UNITS[j].input_name != NULL; j++)
    {
        if (strcmp(unit_name, UNITS[j].input_name) == 0)
        {
            result =  &UNITS[j];
            found = true;
        }
    }

    // Error handling
    if (!found)
    {
        fprintf(stderr, "err: '%s' is not a valid unit, try using '--help' flag\n", unit_name);
        return NO_UNIT;
    }

    if (argc == 2)
    {
        fprintf(stderr, "err: no file was given\n");
        return NO_UNIT;
    }
    return result;
}

void print_file_size(char* file_name, UNIT unit)
{
    if (is_directory(file_name))
    {
        fprintf(stderr, "err: '%s': Is a directory\n", file_name);
        return;
    }
    FILE* fptr = fopen(file_name, "rb");
    if (!fptr)
    {
        fprintf(stderr, "err: fopen(): '%s': %s\n", file_name, strerror(errno));
        return;
    }
    int result = fseek(fptr, 0, SEEK_END);
    if (result != 0)
    {
        fprintf(stderr, "err: fseek(): '%s': %s\n", file_name, strerror(errno));
        fclose(fptr);
        return;
    }
    size_t size_in_bytes = ftell(fptr);
    fclose(fptr);

    char format[] = "xxxxxx %s : %s\n";
    sprintf_s(format, sizeof(format) - 1, "%s %%s : %%s\n", unit.output_format);
    printf(format, 8.f * (float)size_in_bytes / (float)unit.divider, unit.output_name, file_name);
}

int main(int argc, char** argv)
{
    if (argc == 1)
    {
        fprintf(stderr, "err: no file was given\n");
        return EXIT_FAILURE;
    }

    int current_arg = 1;
    if (strcmp(argv[current_arg], "--help") == 0)
    {
        show_help();
        return EXIT_SUCCESS;
    }

    if (strcmp(argv[current_arg], "--version") == 0)
    {
        printf("%s: %s\n", argv[0], FSIZE_VER);
        return EXIT_SUCCESS;
    }

    UNIT* unit = &UNITS[0];
    if (strncmp(argv[current_arg], "--size=", sizeof("--size=") - 1) == 0)
    {
        unit = parse_unit(argv[current_arg] + sizeof("--size=") - 1, argc);
        if (unit->input_name == NULL) return EXIT_FAILURE;
        current_arg++;
    }

    for (; current_arg < argc; current_arg++)
    {
        print_file_size(argv[current_arg], *unit);
    }
    return EXIT_SUCCESS;
}

After: 180 lines, 4 helper methods, main has 33 lines and the 5 steps of processing are clearly visible. No switch/case and no if/else cascade.

Source Link
Thomas Weller
  • 1.7k
  • 14
  • 25

Compiling for Windows

I couldn't compile your code on MSVC.

I had to add

#define _CRT_SECURE_NO_WARNINGS

in order to use the strerror() function.

Also, I suggest you define

#define WIN32_LEAN_AND_MEAN

if you don't need most of the stuff that the Windows header gives you.

i

I like reserving i for for loops. Here, it can be named current_arg or similar.

Help

[files] is not described in help.

It's unclear whether options can be combined or not. The implementation does not allow it.

It sounds a bit strange to me that bit is a unit of bytes.

Output

I have never seen a program output "kibibytes" and similar. Maybe use "kiB" instead.

Constants

Someone has suggested to use 0x400, 0x100000 etc. for the KIB and MIB definitions. I'd prefer

KIB = 1<<10,
MIB = 1<<20,
GIB = 1<<30,

Consistency

I somehow don't like the idea that some of the values need to be used with multiplication while others need division.

enum FILE_SIZE_TYPE
{
    BIT = 8,          // multiply
    BYTES = 1,        // as-it-is BASE
    KIB = 1024,       // divide

If everything were bits, then the calculation would always be the same. To do that, you need C23, because 8*1<<30 is 2^3*2^30 = 2^33, which does not fit into an int anymore. And :unsigned long is only available since C23. Visual Studio 2022 (17.13) does not have support for that... Meh...

But since I'll rework that anyways, it's not a big deal.

Redundant == true

if (is_directory(argv[i]) == true)

Moving Help into a method

Moving help into its own method makes main() shorter. If you put it to the top of the file, a reader will see it and understand the purpose of the program quickly.

I'll apply the string chaining technique proposed by @CPlus as well.

Moving unit parsing into its own method

Moving the unit parsing into its own method makes main() shorter. Many readers will start reading the main() method to get an overview.

Comments instead of names

size_t len = ftell(fptr); // size of file in bytes

can be

size_t size_in_bytes = ftell(fptr);

Maintainability: enums and switch/case

Enums are inherently bad for maintainability. Typically you need a switch/case or else/if cascade somewhere to handle all the cases. This means: if you add or remove an item from the list, you need to adapt all the switch/case or else/if cascades.

When removing an item from an enum, the compiler will help you finding all occurrences. But adding an item is dangerous, because the code will fall into the default branch.

Since this is a major redesign, simply look at the resulting code.

Suggested code

/**
 * @file fsize.c
 * @license This file is licensed under the GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007. You may obtain a copy of this license at https://www.gnu.org/licenses/gpl-3.0.en.html.
 * @author Tushar Chaurasia (Dark-CodeX)
 */
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <stdbool.h>
#include <ctype.h>
#if defined _WIN32 || defined _WIN64 || defined __CYGWIN__
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#else
#include <sys/stat.h>
#include <sys/types.h>
#endif

#define FSIZE_VER "1.0.0"

void show_help(void)
{
    puts(
        "Usage: [option] [files]"
        "Options:"
        "    --help                   Display this help message."
        "    --version                Display the version of the app."
        "    --size=<unit_of_byte>    Define output unit."
        "          =bit, bits             Output in bits."
        "          =byte, bytes           Output in bytes (default)."
        "          =kib                   Output in kibibytes."
        "          =mib                   Output in mebibytes."
        "          =gib                   Output in gibibytes."
        "          =kb                    Output in kilobytes."
        "          =mb                    Output in megabytes."
        "          =gb                    Output in gigabytes."
    );
}

bool is_directory(const char* loc)
{
    if (!loc)
        return false;
#if defined _WIN32 || defined _WIN64 || defined __CYGWIN__
    DWORD fileAttributes = GetFileAttributesA(loc);
    if (fileAttributes == INVALID_FILE_ATTRIBUTES)
        return false;
    return fileAttributes & FILE_ATTRIBUTE_DIRECTORY;
#else
    struct stat buffer;
    return stat(loc, &buffer) == 0 && S_ISDIR(buffer.st_mode);
#endif
}

typedef struct UNIT
{
    const char* input_name;
    const char* output_name;
    const char* output_format;
    const unsigned long long divider;
} UNIT;


UNIT UNITS[] = {
    // Put the default unit first
    { "byte", "bytes", "%0.f", 8ULL },
    {"bit", "bits", "%0.f", 1ULL},
    {"bits", "bits", "%0.f", 1ULL},
    {"bytes", "bytes", "%0.f", 8ULL},
    {"kib", "kibibytes" , "%0.3f", 8ULL<<10},
    {"mib", "mebibytes", "%0.3f", 8ULL<<20},
    {"gib", "gibibytes", "%0.3f", 8ULL<<30},
    {"kb", "kilobytes", "%0.3f", 8ULL*1000},
    {"mb", "megabytes", "%0.3f", 8ULL*1000*1000},
    {"gb", "gigabytes", "%0.3f", 8ULL*1000*1000*1000},
    // Put the "no unit" last
    { NULL, NULL, NULL, 0 },
};

UNIT* parse_unit(char* unit_name, int const argc)
{
    UNIT* NO_UNIT = &UNITS[sizeof(UNITS) / sizeof(UNIT) - 1];
    UNIT* result = &UNITS[0];

    // Parse unit as lower case
    for (int c = 0; unit_name[c]; ++c) {
        unit_name[c] = (char) tolower(unit_name[c]);
    }

    // Unit lookup by input name
    bool found = false;
    for (size_t j = 0; UNITS[j].input_name != NULL; j++)
    {
        if (strcmp(unit_name, UNITS[j].input_name) == 0)
        {
            result =  &UNITS[j];
            found = true;
        }
    }

    // Error handling
    if (!found)
    {
        fprintf(stderr, "err: '%s' is not a valid unit, try using '--help' flag\n", unit_name);
        return NO_UNIT;
    }

    if (argc == 2)
    {
        fprintf(stderr, "err: no file was given\n");
        return NO_UNIT;
    }
    return result;
}

void print_file_size(char* file_name, UNIT unit)
{
    if (is_directory(file_name))
    {
        fprintf(stderr, "err: '%s': Is a directory\n", file_name);
        return;
    }
    FILE* fptr = fopen(file_name, "rb");
    if (!fptr)
    {
        fprintf(stderr, "err: fopen(): '%s': %s\n", file_name, strerror(errno));
        return;
    }
    fseek(fptr, 0, SEEK_END);
    size_t size_in_bytes = ftell(fptr);
    fclose(fptr);

    char format[] = "xxxxxx %s : %s\n";
    sprintf_s(format, sizeof(format) - 1, "%s %%s : %%s\n", unit.output_format);
    printf(format, 8.f * (float)size_in_bytes / (float)unit.divider, unit.output_name, file_name);
}

int main(int argc, char** argv)
{
    if (argc == 1)
    {
        fprintf(stderr, "err: no file was given\n");
        return EXIT_FAILURE;
    }

    int current_arg = 1;
    if (strcmp(argv[current_arg], "--help") == 0)
    {
        show_help();
        return EXIT_SUCCESS;
    }

    if (strcmp(argv[current_arg], "--version") == 0)
    {
        printf("%s: %s\n", argv[0], FSIZE_VER);
        return EXIT_SUCCESS;
    }

    UNIT* unit = &UNITS[0];
    if (strncmp(argv[1], "--size=", sizeof("--size=") - 1) == 0)
    {
        unit = parse_unit(argv[current_arg] + sizeof("--size=") - 1, argc);
        if (unit->input_name == NULL) return EXIT_FAILURE;
        current_arg++;
    }

    for (; current_arg < argc; current_arg++)
    {
        print_file_size(argv[current_arg], *unit);
    }
    return EXIT_SUCCESS;
}

Result of the change

Before: 182 lines, 1 helper method, main has 130 lines.

After: 174 lines, 4 helper methods, main has 33 lines and the 5 steps of processing are clearly visible. No switch/case and no if/else cascade.