Make love, not var_dump())

سجلات القبطان

Geliştirmesini devraldığımız bir sistemde "bir dizindeki 5 milyon dosyayı listlemek" gibi "challange(!)" ile karşılaştık.

İlk başta her zaman olduğu gibi "ls" ile denedik, ls, çoook uzun süre bekledikten sonra "memory exhausted"lar dönüyordu.

ls'e verdiğimiz wildcard parametreleri de "argument list is too long"lar döndürmeye başlamıştı. ls ile bu işin olmayacağını anlamıştık.

find'da çare aradık, find da çook uzun sürelerde dönüyordu, ama bazı wildcard'lar ile dosya listesi almayı başarabilmiştik, ama çoğu yerde, yine sorun oluyordu.

Araştırdık ettik ki, ls, find falan, libc'deki readdir()'ı kullanıyormuş ve bunun read buffer'ı çok düşükmüş. Bunun için ise getdents(get directory entries) system çağrısı kullanılması tavsiye ediliyor.

getdents'in manual sayfasında örnek bir C kodu da var. O koddaki buffer size'ı birazcık arttırdığınızda, içinde 5 milyon dosya olan bir dizini listeleyebiliyorsunuz. Kodun sadece dosyaları listeleyen ve buffer size'ı biraz arttırılmış örnek kodunu da aşağıya ekleyeyim.
#define _GNU_SOURCE
#include <dirent.h>     /* Defines DT_* constants */
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/syscall.h>

#define handle_error(msg) \
        do { perror(msg); exit(EXIT_FAILURE); } while (0)

struct linux_dirent {
    long           d_ino;
    off_t          d_off;
    unsigned short d_reclen;
    char           d_name[];
};


#define BUF_SIZE 5000000

int
main(int argc, char *argv[])
{
    int fd, nread;
    char buf[BUF_SIZE];
    struct linux_dirent *d;
    int bpos;
    char d_type;

    fd = open(argc > 1 ? argv[1] : ".", O_RDONLY | O_DIRECTORY);
    if (fd == -1)
        handle_error("open");

    for ( ; ; ) {
        nread = syscall(SYS_getdents, fd, buf, BUF_SIZE);
        if (nread == -1)
            handle_error("getdents");

        if (nread == 0)
            break;

        for (bpos = 0; bpos < nread;) {
            d = (struct linux_dirent *) (buf + bpos);
            printf("%s\n", (char *) d->d_name);
            bpos += d->d_reclen;
        }

    }

    exit(EXIT_SUCCESS);
}
C ile, GCC ile çok haşır neşir olmayanlar, kabaca;
yukarıdaki kodu bir dosyaya(örn: getdents.c) kaydedip
$ gcc getdents.c -o getdents 
$ ./getdents /listelenmesini/istedginiz/dizin
şeklinde kodu derleyip çalıştırabilirsiniz.
Tek dizinde 5 milyon dosya olması, mimarideki bir hata olsa da, eğer bu sorunu çözmeniz isteniyorsa, getdents()'den faydalanabilirsiniz. Bu da böyle bir anımızdır ;)