1

I am a beginner programmer (learning how to use things like hash tables and tries at present) and so am not well informed, and would value your advice.

I want to write a program that:

  1. Receives a directory address as an argv.
  2. Goes through each file in that directory, one by one (they will all be BMPs) and after reading to a buffer...
  3. Performs a function on the RGB values in that buffer, nothing special -- imagine something like a box blur or a greyscale function.
  4. Saves the buffer to a file in a new folder, closes the file in the original directory currently being accessed, and moves onto the next one until it reaches the final file.

I am experimenting with dirent as best I can, but no matter how I phrase this question, I end up with something that tells me how to read filenames and list them, and how to read those into a dirent struct that doesn't itself hold the file data; I get nothing about specifically accessing a directory and looking for files within them with the explicit purpose of fopen()ing them.

An excerpt of my code, to give you an example of my (probably awful) logic:

DIR *folder;
folder = opendir(argv[3]);
if (folder == NULL);
{
    printf("Unable to read folder");
    return 2;
}

struct dirent *input;
FILE *fileinput;
int files = 0;

// Use this file loop to go through each 
while(   (input = readdir(folder)) != NULL  )
{
    fileinput = fopen(input->d_name, "r");
    if (filepointer != NULL)
    {
        // checks for file headers, open another FILE for writing, my actual function etc.
    }

But again, it seems that the FOPEN there is accessing a copy of a name, and not the file itself indicated so. And I simply don't have the vocabulary to find a similar question answering this, on SO or elsewhere.

Would anyone mind pointing me in the right direction? Apologies for any hassle as I'm sure this is a very basic question...

ーーーーEDIT: requested to post updated code for review:

#include <dirent.h> //必要
#include <sys/types.h>
#include "helpers.h" //bmp.h declared within
#include <getopt.h> //parse argvs

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

#include <math.h>
#include <string.h>

const int PATH_MAX = 260;

int main(int argc, char *argv[])
{
    char *filters = "rbg";

    char filter = getopt(argc, argv, filters);
    if (filter == '?') {
        printf("Invalid filter.\nUsage: ./colourfilter [flag]\n r = red\t b = blue\t g = green\n");

        return 2;
    }
    if (getopt(argc, argv, filters) != -1) {
        printf("Only one filter may be used.\n");
        return 3;
    }

    // OPEN INPUT FOLDER
    const char *inputs = "inputs";
    DIR *infolder = opendir(inputs);

    if (infolder == NULL) {
        //fprintf(stderr,"Unable to read folder %s\n", infolder);
        printf("Unable to read folder.\n");
        return 4;
    }
    
    // Declare variables
    struct dirent *input;
    int counter = 0;
    char name[8];
    FILE *imgout;

    while((input = readdir(infolder)) != NULL) 
    {
        char path[PATH_MAX];
        if (!strcmp(input->d_name, ".") || !strcmp(input->d_name, "..")) {
            continue;
            
        }

  
        if ((size_t)snprintf(path, sizeof(path), "%s/%s", infolder, input->d_name) >= sizeof(path)) {
            printf("Filename too long: %s/%s\n", infolder, input->d_name);
            continue;
        } 

        // FOPEN THINGS
        // "Also make sure you open the BMP files as binary with "rb" and "wb".:" (see: https://stackoverflow.com/questions/71321367/)
        
        sprintf(name, "%03i.bmp", counter);
        FILE *imgin = fopen(path, "rb");
              imgout = fopen(name, "wb");
        
        if (imgin == NULL) {
            printf("Could not open %s.\n", path);
            return 7;
        }
        if (imgout == NULL) {
            fclose(imgin);
            printf("Could not create images.\n");
            return 8;
        }
    
        BITMAPFILEHEADER bf;
        fread(&bf, sizeof(BITMAPFILEHEADER), 1, imgin);

        BITMAPINFOHEADER bi;
        fread(&bi, sizeof(BITMAPINFOHEADER), 1, imgin);

        // Ensure infile is (likely) a 24-bit uncompressed BMP 4.0
        if (bf.bfType != 0x4d42 || bf.bfOffBits != 54 || bi.biSize != 40 ||
            bi.biBitCount != 24 || bi.biCompression != 0)
        {
            fclose(imgout);
            fclose(imgin);
            printf("Unsupported file format.\n");
            return 8;
        } // ... other stuff after this for implementing functions etc.
CBCB
  • 13
  • 3

1 Answers1

1

The problem is fopen(input->d_name, "r"); tries to open the file in the current directory instead of the one specified in argv[3]. You must construct the path to the file in a separate string.

Also make sure you open the BMP files as binary with "rb" and "wb".

    char *foldername = argv[3];
    DIR *folder = opendir(foldername);
    if (folder == NULL) {
        fprintf(stderr, "Unable to read folder %s\n", foldername);
        return 2;
    }
    
    struct dirent *input;
    FILE *fileinput;
    int files = 0;
    
    // Use this file loop to go through each 
    while ((input = readdir(folder)) != NULL) {
        char path[PATH_MAX];
        if (!strcmp(input->d_name, ".") || !strcmp(input->d_name, "..")) }
            continue;
        }
        if ((size_t)snprintf(path, sizeof path, "%s/%s", foldername, input->d_name) >= sizeof path) {
            fprintf(stderr, "filename too long: %s/%s\n", foldername, input->d_name);
            continue;
        }
        fileinput = fopen(path, "rb");
        ...
chqrlie
  • 131,814
  • 10
  • 121
  • 189
  • I adopted that code to the project, but got a few errors, including "format specifies type 'char *' but the argument has type 'DIR *' [-Werror,-Wformat] ///the line quoted: if ((size_t)snprintf(path, sizeof(path), "%s/%s", folder, input->d_name) >= sizeof(path))" Additionally, when I try to use fileinput following "fileinput = fopen(path, "rb");", my NULL check returns true & if I comment that out just to check what's going on, I get a segfault. Would you mind advising further? Sorry for the hassle & thanks for your time thus far. – CBCB Mar 03 '22 at 23:02
  • @BeaneyC: it looks like you pass `input` instead of `input->d_name` to `snprintf`. – chqrlie Mar 04 '22 at 00:18
  • The code as written above contains that `input->d_name`, unless I have to declare a specific e.g. `char *newstring = input->d_name` and make use of that in the snprintf function due to the way it takes arguments of x type? – CBCB Mar 04 '22 at 22:59
  • @BeaneyC: can you post your updated code at the end of the question? – chqrlie Mar 05 '22 at 08:03
  • Sorry for my slow reply. I've done so above. It seems to specifically fail at `if (imgin == NULL)` with the above error msg. – CBCB Mar 06 '22 at 10:58
  • @BeaneyC: my bad, I misread the error message, the 4th argument to `snprintf` should be `argv[3]` (the folder name) instead of `folder` (the `DIR` pointer with a confusing name). Answer updated. – chqrlie Mar 06 '22 at 11:06
  • @BeaneyC: Regarding your code: you should use `snprintf` instead of `sprintf` to compose the name of the output file without risking a buffer overflow if the folder happens to contain more than 999 files. There is no reason to use `sprintf` when `snprintf` is available. – chqrlie Mar 06 '22 at 11:10
  • Ah, It worked! I've got some malloc problems, but that's a different issue I'll figure out myself. Thank you so so much for your time and patience. It won't let me upvote you but It'll be applied retroactively once I reach the status threshold. ED: and thanks for the snprintf things -- I will have a read up on these and use them as you recommend. – CBCB Mar 06 '22 at 12:03
  • @BeaneyC: Thank you for your comment, you can accept this answer by clicking on the grey checkmark below its score. – chqrlie Mar 06 '22 at 12:54