lstat and ls permissions output is somehow differs

I'm trying to rewrite ls function with some of its flags, currently I'm implementing [-l] flag but output about permissions from original ls and lstat are different

Here is my code

void mx_strmode(mode_t mode, char * buf) {
  const char chars[] = "rwxrwxrwx";
  for (size_t i = 0; i < 9; i++) {
    buf[i] = (mode & (1 << (8-i))) ? chars[i] : '-';
  }
  buf[9] = '\0';
}

int main(int ac, char **av) {
    t_flags flags = mx_get_flags(ac, av);
    char *dir_name = get_dir_name(ac, av);
    DIR *dir;
    struct dirent *entry;

    dir = opendir(dir_name);
    if (!dir) {
        perror("diropen");
        exit(1);
    };

    struct stat s_stat;
    while ((entry = readdir(dir)) != NULL) {
        lstat(entry->d_name, &s_stat);
        char buf_perm[10];
        mx_strmode(s_stat.st_mode, buf_perm);
        printf("%s  %s\n", buf_perm , entry->d_name);

    };

    closedir(dir);
}

And here is what i get from ls and my program. I'm opening directory that doesn't contain my executable(may be it's a root of a problem)

>drwxr-xr-x   3 fstaryk  4242   102 Jan  3 17:27 .
>drwxr-xr-x  11 fstaryk  4242   374 Jan 18 17:40 ..
>-rw-r--r--   1 fstaryk  4242  4365 Jan 18 17:40 main.c

>rwxr-xr-x    .
>rwx------    ..
>rwx------    main.c

Solution 1:

As you've discovered from adding the error checking as suggested in comments, you're getting issues with 'No such file or directory'. This is because lstat() resolves relative paths like the filenames in struct dirent objects starting from the current working directory, which is not the directory you're trying to list the files of.

Luckily, modern unix/linux systems have a function fstatat() that lets you specify a directory to use as the base of relative paths, and you can get the required directory descriptor from a DIR struct with dirfd().

Simplified example of using it:

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

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>

int main(void) {
  DIR *d = opendir("test/");
  if (!d) {
    perror("opendir");
    return EXIT_FAILURE;
  }

  int dfd = dirfd(d); // Get the directory file descriptor for use with fstatat()
  if (dfd < 0) {
    perror("dirfd");
    closedir(d);
    return EXIT_FAILURE;
  }

  struct dirent *entry;
  while ((entry = readdir(d))) {
    if (entry->d_name[0] == '.') {
      // Skip dotfiles
      continue;
    }

    struct stat s;
    // Resolve filenames relative to the directory being scanned
    // and don't follow symlinks to emulate lstat()'s behavior
    if (fstatat(dfd, entry->d_name, &s, AT_SYMLINK_NOFOLLOW) < 0) {
      perror("fstatat");
      closedir(d);
      return EXIT_FAILURE;
    }
    printf("%s: %ld\n", entry->d_name, (long)s.st_size);
  }

  closedir(d);
  return 0;
}

On older OSes that lack the *at() functions, you'd have to resort to creating a string holding the directory name + filename (With snprintf() or whatever) and use that as an argument to lstat().