Search code examples
cfileopendir

C opendir() not opening a directory?


I'm not all that experienced in C, but have an assignment in one of my classes, and I'm having a few issues opening a directory.

The purpose of the program is to (paraphrased):

  1. If no flag or directory is provided, list names of files in current directory.
  2. If a flag (-l) is provided, list more information about each file.
  3. If a flag and a directory are provided, go to that directory and list information about each file (or directory) in that directory. It's not necessary to recurse through subdirectories.

It doesn't need to handle getting only a directory but no flag, or an invalid directory. It's more proof-of-concept.

I've managed to satisfy parts 1 and 2, but part 3 keeps breaking, giving a segmentation fault.

My code is as follows:

void recursedirect(char* flag, char* directory)
{
    DIR* dir;
    struct dirent *entry;

    printf("Flag: %s\nDirectory: %s\n\n", flag, directory);

    if (flag == NULL)
    // No flag provided, therefore only do file names.
    {
        dir = opendir(".");
        entry = readdir(dir);

        while (entry != NULL) 
        {
            printf ("%s\n", entry->d_name);
            entry = readdir(dir);
        }
    }
    else if (strcmp(flag, "-l") == 0 && directory == NULL)
    // No directory provided, but flag provided
    {
        dir = opendir(".");
        entry = readdir(dir);

        while (entry != NULL) 
        {
            fileinfo(directory);
            entry = readdir(dir);
        }   
    }
    else if (strcmp(flag, "-l") != 0)
    {
        printf("Invalid flag.\n");
    }
    else
    // A directory is provided
    {
        dir = opendir(directory);

        if (dir != NULL)
        {
            entry = readdir(dir);

            while (entry != NULL) 
            {
                fileinfo(directory);
                entry = readdir(dir);
            }   
        }
        else
            printf("Something went wrong. It might be an invalid filename.\n");
    }
}

Specifically, the else statement at the end is giving me trouble. I've tried giving it any filepaths I can think of, hardcoded and otherwise, and it's still giving me issues. I'm guessing it's something simple, but I don't have enough experience to know what it is.

fileinfo is a function that prints out the file information given a DIR and dirent as arguments. However, the error definitely isn't in that code best I can tell.

EDIT: I made the mistake of not sharing everything. My main method is pretty simple.

int main (int argc, char** argv)
{
    char* flag = argv[1];
    char* directory = argv[2];

    recursedirect(flag, directory);
}//main

It includes the following:

#include <dirent.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <time.h>
#include <fcntl.h>

And the fileinfo function is as follows (edited slightly, as I realised I no longer needed to give it an argument of DIR *dir):

void fileinfo(struct dirent *entry)
{
    struct stat fileStats;
    stat(entry->d_name, &fileStats);

    printf("File Name: %s\n", entry->d_name);
    printf("File Size: %d bytes\n", (int) fileStats.st_size);
    printf("Number of Blocks: %d\n", (int) fileStats.st_blocks);
    printf("Number of Links/References: %d\n", (int) fileStats.st_nlink);
    printf("I-Node: %d\n", (int) fileStats.st_ino);
    printf("Device ID: %d\n", (int) fileStats.st_dev);
    printf("User ID: %d\n", (int) fileStats.st_uid);
    printf("Group ID: %d\n", (int) fileStats.st_gid);

    stat("alphabet",&fileStats);
    printf("Time of Last Access: %s", ctime(&fileStats.st_atime));
    printf("Time of Last Modification: %s", ctime(&fileStats.st_mtime));
    printf("Time of Last Status Change: %s", ctime(&fileStats.st_ctime));

    stat(entry->d_name, &fileStats);
    printf("File Permissions: ");
    printf((int) (S_ISDIR(fileStats.st_mode)) ? "d" : "-");
    printf((int) (fileStats.st_mode & S_IRUSR) ? "r" : "-");
    printf((int) (fileStats.st_mode & S_IWUSR) ? "w" : "-");
    printf((int) (fileStats.st_mode & S_IXUSR) ? "x" : "-");
    printf((int) (fileStats.st_mode & S_IRGRP) ? "r" : "-");
    printf((int) (fileStats.st_mode & S_IWGRP) ? "w" : "-");
    printf((int) (fileStats.st_mode & S_IXGRP) ? "x" : "-");
    printf((int) (fileStats.st_mode & S_IROTH) ? "r" : "-");
    printf((int) (fileStats.st_mode & S_IWOTH) ? "w" : "-");
    printf((int) (fileStats.st_mode & S_IXOTH) ? "x\n\n" : "-\n\n");
}

As for more clarification on failed inputs, ./files and ./files -l both work. ./files with any invalid flag is caught as intended. However, ./files -l *any valid filepath* consistently fails.

Everything is compiled using gcc in Ubuntu 14.04.5 in the c9.io IDE.

Thank you!

EDIT 2: I've tried deleting and putting back the file, and now strangely I'm not getting the error. However, now it won't print full file info for anything. I'll troubleshoot some more.

EDIT 3: I've fixed it with one exception. The issue was an incorrect if statement checking the flag. Apparently something was going wrong in the IDE. I'm now getting this error: Couldn't open MANPATH=/home/ubuntu/.nvm/versions/node/v6.11.2/share/man:/usr/local/rvm/rubies/ruby-2.4.0/share/man:/usr/local/man:/usr/local/share/man:/usr/share/man:/usr/local/rvm/man

It looks like this isn't the code and is a problem with the c9 server in some way. I've tried giving both the source code and compiled code files full permissions, restarting the workspace, and restarting Apache2. Thank you all for everything!


Solution

  • In addition to comments left by chux, you may benefit from restructuring your code to reuse similar blocks. You mention that you've solved parts 1 & 2 (represented by the first two branches in your if statements). So, in theory the following if blocks produce the correct output. If you simplify it so code is rewritten less, you may benefit.

    if (flag == NULL)
    // No flag provided, therefore only do file names.
    {
        dir = opendir(".");
        entry = readdir(dir);
    
        while (entry != NULL) 
        {
            printf ("%s\n", entry->d_name);
            entry = readdir(dir);
        }
    }
    else if (strcmp(flag, "-l") == 0 && directory == NULL)
    // No directory provided, but flag provided
    {
        dir = opendir(".");
        entry = readdir(dir);
    
        while (entry != NULL) 
        {
            fileinfo(directory);
            entry = readdir(dir);
        }   
    }
    

    Instead of writing it this way, you could check the flag beforehand, and set a variable (in this case more_file_info != 0 tells us to print, and we return if the flag is invalid).

    int more_file_info = 0;
    if (flag != NULL) {
        if(strcmp(flag, "-l") == 0) {
            more_file_info = 1;
        } else {
            printf("Invalid flag.\n");
            return;
        }
    }
    

    Then, you can simplify/refactor the above (presumably working) code as follows:

    void recursedirect(char* flag, char* directory)
    {
        DIR* dir;
        struct dirent *entry;
        int more_file_info = 0;
    
        printf("Flag: %s\nDirectory: %s\n\n", flag, directory);
        // Validate argument up front
        if (flag != NULL) {
            if(strcmp(flag, "-l") == 0) {
                more_file_info = 1;
            } else {
                printf("Invalid flag.\n");
                return;
            }
        }
    
        // The 'directory' parameter only changes opendir, so we reflect that here
        if(directory != NULL) {
            dir = opendir(directory);
        } else {
            dir = opendir(".");
        }
        // Adding some error checking, per comment
        if(dir == NULL) {
            printf("Couldn't open %s\n", directory);
            return;
        }
        // The readdir() loop is basically the same
        entry = readdir(dir);
        while (entry != NULL)
        {
            // Although we check a flag every time this loop runs,
            // it makes the code easier to follow
            if(more_file_info != 0) {
                printf ("%s\n", entry->d_name);
            } else {
                fileinfo(directory);
                // Or should this be:
                // fileinfo(entry);
            }
            entry = readdir(dir); 
        }
    }
    

    This eliminates a lot of duplicated code, and should hopefully make troubleshooting easier. Also note that this handles the case where a directory is specified but flag is not, which you could explicitly check for if different behavior is desired. Hopefully with more information we can provide a more suitable answer.