2

I am trying to implement the ls command and am getting stuck in the -i option using the stat() function to list inode numbers.

The format is ./myls [options] [list of files]

If I run ./myls -i . I get correct output (this prints files in the current directory)

./myls -i
1446018     myls.c
1441809     myls
1445497     something

and if I run ./myls .. I get correct output (printing files in parent directory)

./myls ..
Assignment2
Assignment3
Assignment4
Assignment1

But if combine them with parent directory and run ./myls -i .. I get the error message

/myls -i ..
Error finding file: No such file or directory

"Error finding file" is an error message I am printing out in my ls function.

//printf("%s", name);
if (stat(name, fileStat) != 0) {
    error("Error finding file");
}

If I uncomment the printf statement right before this error message, I get the following

/myls -i ..
Assignment2
Error finding file: No such file or directory

So it is getting the name of the first file of the parent directory but the stat function fails. Anyone know how I can fix this?

I read somewhere that it might be failing because it is only passing the name of the file and not its path. But this works in the current directory so this may not be the issue. If it is, then how would I pass the path and not just the name of file?

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

int i = 0, R = 0, l = 0;
int anyFiles = 0;

void error(char *msg) {
    perror(msg);
    exit(0);
}

void ls(char *path) {
    DIR *d;
    struct dirent *dir;
    d = opendir(path);
    if (!d || d == NULL)
        error("Unable to read directory");

    char name[256]; //to store file name of each file in directory

    while ((dir = readdir(d)) != NULL) {
        strcpy(name, dir -> d_name);
        if (name[0] == '.' || strncmp(name, "..", 2) == 0)
            continue;

        if (i || R || l) {
            struct stat *fileStat;

            //printf("%s\n",name);
            if (stat(name, fileStat) != 0) {
                error("Error finding file");
            }

            if (i) {
                printf("%lu\t\t%s\n", fileStat->st_ino, name);
                continue;
            }
            /*else if (R) {
                continue;
            }
            else if (l) {
                continue;
            }*/
        }
        printf("%s\n", name);
    }
    closedir(d);
}

void process(int args, char *argsList[]) {
    int j; 
    for (j = 1; j < args; j++) {
        if (argsList[j][0] == '-') {
            int k;
            for (k = 1; k < (strlen(argsList[j])); k++) {
                if (argsList[j][k] == 'i')
                    i = 1;
                else if (argsList[j][k] == 'R')
                    R =  1;
                else if (argsList[j][k] == 'l')
                    l = 1;
                else
                    error("option not supported");
            }
        }
        else if (argsList[j][0] != '-') {
            ls(argsList[j]);
            anyFiles = 1;
        }
    }
    if (anyFiles == 0)
        ls(".");
}

int main(int argc, char *argv[]) {
    if (argc == 1)
        ls(".");
    else if (argc > 1) {
        process(argc, argv); 
    }
    return 0;
}
chqrlie
  • 131,814
  • 10
  • 121
  • 189
callum arul
  • 159
  • 6
  • 1
    Printing the file name you tried to `stat`would be a good start. – Gerhardh Aug 03 '22 at 11:52
  • Assignment2 is the file name that I tried to stat but got the error with. It is the first file in the parent directory '..' – callum arul Aug 03 '22 at 11:56
  • Well.... after thinking about it a few more seconds... Of course you cannot open the file. It does not exist in your current folder. You must add the path as prefix. – Gerhardh Aug 03 '22 at 11:58
  • 1
    The `d_name` is _not_ the complete path to the file, only the simple file name. If you wish to `stat()` files in other directories, you must construct a complete path to that file, either absolute or relative. (Or change your current working directory to the other directory first. Prefer using a path as argument over changing directories, though...) – Dúthomhas Aug 03 '22 at 11:59
  • 1
    Knowing this, it is rather obvious why it works if you only use `-i` or `..` as arguments. In first case you list files from current directory which can be found correctly. In second case you only list file names in parent directory but do not try to do anything with the files. – Gerhardh Aug 03 '22 at 12:03

2 Answers2

3

The name passed to stat in stat(name, fileStat) is relative to the open directory. You must either construct the actual name from path and dir->d_name or use fstatat(). It works for "." because the relative paths happen to be relative to the current directory.

Furthermore, the stat structure should be declared with automatic storage, not as an uninitialized pointer.

Note that the ls utility does not use stat when enumerating directory contents and command line arguments, but lstat which returns the values for symbolic links instead of resolving them, unless they have a trailing slash and resolve to a directory.

Here is a modified version with many other issues fixed:

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

int flag_a, flag_i, flag_R, flag_l;

typedef struct entry_t {
    struct entry_t *next;
    char name[];
} entry_t;

typedef struct list_t {
    struct entry_t *head;
    struct entry_t *tail;
} list_t;

void free_entries(list_t *list) {
    while (list->head) {
        entry_t *ep = list->head;
        list->head = ep->next;
        free(ep);
    }
    list->tail = NULL;
}

void add_entry(list_t *list, const char *name) {
    entry_t *ep = malloc(sizeof(*ep) + strlen(name) + 1);
    if (ep == NULL) {
        fprintf(stderr, "out of memory\n");
        exit(1);
    }
    strcpy(ep->name, name);
    /* simplistic insertion sort */
    if (!list->head || strcmp(name, list->head->name) < 0) {
        ep->next = list->head;
        list->head = ep;
        if (list->tail == NULL)
            list->tail = ep;
    } else {
        entry_t *p = list->head;
        while (p->next && strcmp(name, p->next->name) > 0)
            p = p->next;
        ep->next = p->next;
        p->next = ep;
    }
    if (list->tail->next)
        list->tail = list->tail->next;
}

int isdir(const char *name) {
    struct stat fileStat;
    return !lstat(name, &fileStat) && S_ISDIR(fileStat.st_mode);
}

int ls_files(list_t *list, list_t *dirs, const char *path) {
    int status = 0;

    for (entry_t *ep = list->head; ep; ep = ep->next) {
        if (flag_i + flag_l + flag_R) {
            char buf[1024]; //to store the constructed path name
            struct stat fileStat;
            char *name = ep->name;
            if (path) {
                snprintf(name = buf, sizeof buf, "%s/%s", path, ep->name);
            }
            if (lstat(name, &fileStat) != 0) {
                fprintf(stderr, "cannot stat file %s: %s\n",
                        name, strerror(errno));
                status |= 1;
                continue;
            }
            if (flag_R && S_ISDIR(fileStat.st_mode) &&
                strcmp(ep->name, ".") && strcmp(ep->name, "..")) {
                add_entry(dirs, name);
            }
            if (flag_i) {
                printf("%lu\t", (unsigned long)fileStat.st_ino);
            }
            if (flag_l) {
                printf("%lu\t", (unsigned long)fileStat.st_size);
                /* should also output mode and date */
            }
        }
        printf("%s\n", ep->name);
    }
    return status;
}

int ls_dir(const char *path, list_t *subdirs) {
    DIR *d = opendir(path);
    if (d == NULL) {
        fprintf(stderr, "cannot open directory %s: %s\n",
                path, strerror(errno));
        return 1;
    }

    struct dirent *dir;
    list_t files = { NULL, NULL };

    /* enumerate directory entries and store the names in a sorted list */
    while ((dir = readdir(d)) != NULL) {
        if (*dir->d_name == '.' && !flag_a)
            continue;
        add_entry(&files, dir->d_name);
    }
    closedir(d);
    int status = ls_files(&files, subdirs, path);
    free_entries(&files);
    return status;
}

int ls_dirs(list_t *dirs, int header) {
    int status = 0;
    list_t subdirs = { NULL, NULL };
    for (entry_t *ep = dirs->head; ep; ep = ep->next) {
        if (header) {
            if (header > 1)
                printf("\n");
            printf("%s:\n", ep->name);
        }
        ls_dir(ep->name, &subdirs);
        header = 2;
        if (subdirs.head) {
            /* insert the sorted list of subdirectories */
            subdirs.tail->next = ep->next;
            ep->next = subdirs.head;
            subdirs.head = subdirs.tail = NULL;
        }
    }
    return status;
}

int process(int args, char *argsList[]) {
    int status = 0;
    list_t files = { NULL, NULL };
    list_t dirs = { NULL, NULL };
    for (int j = 1; j < args; j++) {
        char *arg = argsList[j];
        if (*arg == '-') {
            for (int k = 1; arg[k] != '\0'; k++) {
                switch (arg[k]) {
                case 'a':  flag_a = 1;  continue;
                case 'i':  flag_i = 1;  continue;
                case 'R':  flag_R = 1;  continue;
                case 'l':  flag_l = 1;  continue;
                default:   fprintf(stderr, "option not supported: -%c\n", arg[k]);
                    return 1;
                }
            }
        } else {
            if (isdir(arg))
                add_entry(&dirs, arg);
            else
                add_entry(&files, arg);
        }
    }
    if (!dirs.head && !files.head) {
        add_entry(&dirs, ".");
    }
    int header = 0;
    if (files.head) {
        status |= ls_files(&files, &dirs, NULL);
        header = 2;
    }
    if (dirs.head) {
        if (!header && dirs.head->next)
            header = 1;
        status |= ls_dirs(&dirs, header);
    }
    free_entries(&files);
    free_entries(&dirs);
    return status;
}

int main(int argc, char *argv[]) {
    return process(argc, argv);
}
chqrlie
  • 131,814
  • 10
  • 121
  • 189
  • Im having trouble making the absolute path using realpath(). My cwd is Assignment4. If I print realpath() using ./myls -i .. as an argument it gives /home/CMPT300Assignments/Assignment4/Assignment2. How do I make it so that it becomes /home/CMPT300Assignments/Assignment2? – callum arul Aug 03 '22 at 13:51
  • @callumarul: don't use `realpath`, construct the pathname from `path` and `dir->d_name` as shown in the updated answer. – chqrlie Aug 03 '22 at 15:00
  • Hey a follow up, I need to also support taking file name as an argument. For example ./myls file.txt. Right now I can only take in directories and not files as arguments. How do I do this? I was thinking of using S_ISDIR but if the file is in a different directory how would I construct a path considering I don't have a directory to use d_name? – callum arul Aug 04 '22 at 14:13
  • @callumarul: to handle both files and directories, it is easier to write 2 separate functions as posted in my updated answer. – chqrlie Aug 04 '22 at 19:05
  • Got it, its working perfectly now. The only thing left is to sort it lexicographically like the ls command does. Is there any way to traverse through the directory lexicographically? Im hoping I don't have to store all the files in an array, sort it and then print it out. – callum arul Aug 04 '22 at 21:02
  • @callumarul: I posted an update with sorting compatible with that of `bin/ls`. It is a little tricky, the code uses lists of entries for files and directories. The implementation is not recursive so there is no risk of **stack overflow** :) – chqrlie Aug 05 '22 at 11:22
2

Refer to the question, Correct use of Stat on C After declaring fileStat, you need to allocate memory for it: add the line

struct stat *fileStat;
fileStat = malloc(sizeof(struct stat));
....
....
free(fileStat);
Yalpay
  • 21
  • 2