Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What determines the order directory entries are returned by getdents?

Background is I have an existing application which lists directory entries; strace reveals it just calls getdents and lists them in the order returned. I would like them displayed in the same order as a call to ls with no arguments. Is it possible to update the directory data in some way to achieve this?

FS is ext4, if that makes any difference.

like image 775
Barney Avatar asked Jan 15 '12 00:01

Barney


People also ask

What does Getdents return?

getdents will return struct linux_dirent . It will do this for any underlying type of filesystem.

What is Getdents?

The getdents() function attempts to read nbyte bytes from the directory associated with the file descriptor fildes and to format them as file system independent directory entries in the buffer pointed to by buf.


2 Answers

If you really are determined to change this program's behaviour (of which I assume that you don't have the source code available), you can use LD_PRELOAD to hook the call to opendir and readdir and replace it with your own, sorting wrapper. An example how such a hook could look like is the following:

#define _GNU_SOURCE 1
#include <stdio.h>
#include <dirent.h>
#include <dlfcn.h>
#include <stdlib.h>
#include <string.h>

struct __dirstream
{
  int __fd;
  char *__data;
  size_t __allocation;
  size_t __offset;
  size_t __size;
  struct dirent __entry;
};

typedef struct _dirent_list {
  struct dirent *value;
  struct _dirent_list *next;
} dirent_list;

typedef struct _my_DIR {
  struct __dirstream orig;
  dirent_list *first_entry;
  int first_readdir;
} my_DIR;

DIR *opendir(const char *name) {
  DIR *(*orig_opendir)(const char*) = dlsym(RTLD_NEXT, "opendir");
  DIR *dir = orig_opendir(name);

  // save additional information along with the
  // original DIR structure
  my_DIR *my_dir = calloc(1, sizeof(*my_dir));
  my_dir->first_readdir = 1;
  memcpy(my_dir, dir, sizeof(*dir));
  return (DIR*)my_dir;
}

struct dirent *readdir(DIR *dir) {
  struct dirent *(*orig_readdir)(DIR*) = dlsym(RTLD_NEXT, "readdir");
  my_DIR *my_dir = (my_DIR*)dir;
  dirent_list *item;

  if (my_dir->first_readdir) {
    struct dirent *entry;
    while ((entry = orig_readdir(dir))) {
      // exercise for the reader:
      // implement insertion sort here 
      item = calloc(1, sizeof(*item));
      item->value = entry;
      item->next = my_dir->first_entry;
      my_dir->first_entry = item;
    }
    my_dir->first_readdir = 0;
  }

  if (!my_dir->first_entry)
    return NULL;

  item = my_dir->first_entry;
  struct dirent *result = item->value;
  my_dir->first_entry = item->next;
  free(item);

  return result;
}

It overrides opendir and readdir to return the entries in reverse order (you can adapt this for sorting too). This is how you use it with a program test that simply lists the directory entries in the order they are received:

$ gcc -Wall -shared -fPIC -o libhookdir.so hookdir.c -ldl
$ ./test
..
test
.
hookdir.c
libhookdir.so
test.c
$ LD_PRELOAD=./libhookdir.so ./test
test.c
libhookdir.so
hookdir.c
.
test
..

Hah! This works. We just hooked a libc function.

like image 174
Niklas B. Avatar answered Sep 26 '22 00:09

Niklas B.


No, there is no way you can manipulate the filesystem metadata to have getdents(2) return directory entires in the same order as the sort order that ls(1) applies to the directory entires.

You can always modify your program to sort entries using the same algorithms that ls(1) provides, though this requires at least O(N) memory and O(N Log N) time for sorting a directory with N entries. You'll have to decide if it is worth the implementation, memory, and time, to sort in the same manner as ls(1).

like image 39
sarnold Avatar answered Sep 23 '22 00:09

sarnold