Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

working with directories in POSIX with C

I will go ahead and say this is a homework assignment for an intro to Linux class. I would not be posting it without extensive attempts on my own, and seeing as I am a distance student this semester, I cannot make it to campus for tutoring. I need some help finding out what the issue is.

Essentially the assignment asks us to make a program that serves the same basic function as the pwd command in POSIX, to show the absolute path for the current directory. We are to use three functions along with main. We are not to use the getcwd command as well. I'll list them and their purpose

  • inum_to_filename: Accepts three arguments (inode number to translate, a pointer to a buffer where the name is written, and the size of the buffer). Returns nothing. It is to:

    1. Open the current directory,
    2. Read the first directory entry,
    3. If the inode of the current directory matches the one passed in, copy name to buffer and return.
    4. Otherwise read the next directory entry and repeat the previous step.
  • filename_to_inum: Accepts one argument (a char * representing the filename). It returns the corresponding inode number. It is to:

    1. Read the information from the files inode into a structure in memory.
    2. If there is any problem, display the appropriate error.
    3. Return the inode number from the structure.
  • display_path: Accepts one argument (inode from the current working directory). It returns nothing. It is to:

    1. Create an array of characters to use as a buffer for the name of the directory.
    2. Get the inode for the parent directory using filename_to_inode.
    3. If the parent inode is equal to the current inode, we have reached root and can return.
    4. Otherwise, change to the parent directory and use inum_to_filename to find the name for the inode that was passed into the function. Use the buffer from step 1 to store it.
    5. Recursively call display_path to display the absolute path.

Here is the code:

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

void inum_to_filename (int inode_arg, char *pathBuffer, int size_arg) {
    DIR *dir_ptr = opendir(".");
    struct dirent *dirent_ptr = readdir(dir_ptr);
    int counter = 0;

    while (counter != 1) {
        if (inode_arg == dirent_ptr->d_ino) {

            strcat(pathBuffer, "/");
            strcat(pathBuffer, dirent_ptr->d_name);

            counter = counter + 1;
            return;

        } else {
            dirent_ptr = readdir(dir_ptr);
        }
    }

    closedir(dir_ptr);
}

int filename_to_inum (char *src) {
    int res = 0;
    struct stat info;
    int result = stat(src, &info);

    if (result != 0) {
        fprintf(stderr, "Cannot stat ");
        perror(src);
        exit(EXIT_FAILURE);
    } else {
        res = info.st_ino;
    }

    return res;
} 

void display_path (int ino_src) {
    int bufSize = 4096;
    char pathBuffer[bufSize];
    int ino_prnt = filename_to_inum("..");

    if (ino_src == ino_prnt) {
        //print for test
        inum_to_filename(ino_src, pathBuffer, bufSize);
        printf("%s", pathBuffer);
        return;
    } else {
        //print for test
        chdir("..");
        inum_to_filename(ino_src, pathBuffer, bufSize);
        display_path(ino_prnt);
        printf("%s", pathBuffer);
    }
}

int main (int argc, char *argv[]) {
    int c_ino = filename_to_inum(".");
    display_path(c_ino);
    printf("\n");
}

As of right now it is displaying "/./MyName" with MyName being my personal named directory on the server. It is the directory I am running the program from. When using pwd I return "/home/MyName". I'm not really sure what my next step to getting the absolute path correct is.

like image 678
Monobus Avatar asked Oct 04 '15 16:10

Monobus


1 Answers

The code is mostly set up to print one name at a time in the correct order, so the primary problem is the use of strcat() rather than strcpy(). Also, detecting when you're in the root directory at the start is important; if you don't, you can end up with /. or something similar (depending on exactly how you coordinate the printing) when the current directory is the root directory.

This version of your code has:

  • Squished the loop in inum_to_filename(), but also added error reporting. Remember, a process can be run in a directory which it does not have permission to get to (it requires a setuid program, usually — although permissions could be changed after the program is launched). In that case, it may fail to open .. (or .).

  • Lost variable count; it wasn't serving a useful purpose. Using the assign-and-test idiom allows the code to contain a single call to readdir().

  • Use strcpy() instead of strcat().

  • Use type ino_t to store inode numbers. Use size_t for sizes.

  • Reduce number of intermediate variables in filename_to_inum().

  • Note that the code in the if (ino_src == ino_prnt) statement body is for the root directory; in the absence of the testing print, it would do nothing.

  • Note that the printing in the else part is a major part of the operations, not just test printing.

  • Error check chdir("..");

  • Detect root in main().

  • Observe that this code is not directly suitable for rewriting into a function because it changes the process's current directory to / when it succeeds.

Revised code:

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

static void inum_to_filename(ino_t inode_arg, char *pathBuffer, size_t size_arg)
{
    assert(size_arg > 0);
    DIR *dir_ptr = opendir(".");
    if (dir_ptr == 0)
    {
        fprintf(stderr, "Failed to open directory '.' (%d: %s)\n", errno, strerror(errno));
        exit(EXIT_FAILURE);
    }
    struct dirent *dirent_ptr;

    while ((dirent_ptr = readdir(dir_ptr)) != 0)
    {
        if (inode_arg == dirent_ptr->d_ino)
        {
            if (strlen(dirent_ptr->d_name) >= size_arg)
            {
                fprintf(stderr, "File name %s too long (%zu vs %zu max)\n",
                        dirent_ptr->d_name, strlen(dirent_ptr->d_name), size_arg);
                exit(EXIT_FAILURE);
            }
            strcpy(pathBuffer, dirent_ptr->d_name);
            break;
        }
    }

    closedir(dir_ptr);
}

static ino_t filename_to_inum(char *src)
{
    struct stat info;

    if (stat(src, &info) != 0)
    {
        fprintf(stderr, "Cannot stat ");
        perror(src);
        exit(EXIT_FAILURE);
    }
    return info.st_ino;
}

static void display_path(ino_t ino_src)
{
    size_t bufSize = 4096;
    char pathBuffer[bufSize];
    ino_t ino_prnt = filename_to_inum("..");

    if (ino_src == ino_prnt)
    {
        // print for test
        inum_to_filename(ino_src, pathBuffer, bufSize);
        printf("%s", "(root): /\n");
    }
    else
    {
        // print for real
        if (chdir("..") != 0)
        {
            fprintf(stderr, "Failed to chdir to .. (%d: %s)\n",
                    errno, strerror(errno));
        }
        inum_to_filename(ino_src, pathBuffer, bufSize);
        display_path(ino_prnt);
        printf("/%s", pathBuffer);
    }
}

int main(void)
{
    ino_t c_ino = filename_to_inum(".");
    ino_t r_ino = filename_to_inum("/");
    if (r_ino == c_ino)
        putchar('/');
    else
        display_path(c_ino);
    printf("\n");
}

There are undoubtedly other ways to fix this.

Caveat: this is giving me some grief when working in /Volumes/CRUZER/Sub-Directory which is a memory stick. It fails to find the inode (1, which is surprising) when scanning /Volumes, and I've not worked out why. One of my programs — a getpwd implementation — is working fine; another is having a different problem. I expect I'll get to the bottom of it all. Testing on Mac OS X 10.10.5 with GCC 5.1.0.

like image 110
Jonathan Leffler Avatar answered Oct 08 '22 11:10

Jonathan Leffler