Zde je rekurzivní verze:
#include <unistd.h>
#include <sys/types.h>
#include <dirent.h>
#include <stdio.h>
#include <string.h>
void listdir(const char *name, int indent)
{
DIR *dir;
struct dirent *entry;
if (!(dir = opendir(name)))
return;
while ((entry = readdir(dir)) != NULL) {
if (entry->d_type == DT_DIR) {
char path[1024];
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
continue;
snprintf(path, sizeof(path), "%s/%s", name, entry->d_name);
printf("%*s[%s]\n", indent, "", entry->d_name);
listdir(path, indent + 2);
} else {
printf("%*s- %s\n", indent, "", entry->d_name);
}
}
closedir(dir);
}
int main(void) {
listdir(".", 0);
return 0;
}
Proč všichni trvají na znovu a znovu objevování kola?
POSIX.1-2008 standardizoval nftw()
funkce, také definovaná v Single Unix Specification v4 (SuSv4) a dostupná v Linuxu (glibc, man 3 nftw
), OS X a většina aktuálních variant BSD. Není to vůbec nové.
Naivní opendir()
/readdir()
/closedir()
-založené implementace téměř nikdy nezvládají případy, kdy jsou adresáře nebo soubory přesunuty, přejmenovány nebo odstraněny během procházení stromu, zatímco nftw()
měl by s nimi zacházet elegantně.
Jako příklad si vezměme následující program v jazyce C, který uvádí adresářový strom začínající v aktuálním pracovním adresáři nebo v každém z adresářů pojmenovaných na příkazovém řádku nebo pouze soubory pojmenované na příkazovém řádku:
/* We want POSIX.1-2008 + XSI, i.e. SuSv4, features */
#define _XOPEN_SOURCE 700
/* Added on 2017-06-25:
If the C library can support 64-bit file sizes
and offsets, using the standard names,
these defines tell the C library to do so. */
#define _LARGEFILE64_SOURCE
#define _FILE_OFFSET_BITS 64
#include <stdlib.h>
#include <unistd.h>
#include <ftw.h>
#include <time.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
/* POSIX.1 says each process has at least 20 file descriptors.
* Three of those belong to the standard streams.
* Here, we use a conservative estimate of 15 available;
* assuming we use at most two for other uses in this program,
* we should never run into any problems.
* Most trees are shallower than that, so it is efficient.
* Deeper trees are traversed fine, just a bit slower.
* (Linux allows typically hundreds to thousands of open files,
* so you'll probably never see any issues even if you used
* a much higher value, say a couple of hundred, but
* 15 is a safe, reasonable value.)
*/
#ifndef USE_FDS
#define USE_FDS 15
#endif
int print_entry(const char *filepath, const struct stat *info,
const int typeflag, struct FTW *pathinfo)
{
/* const char *const filename = filepath + pathinfo->base; */
const double bytes = (double)info->st_size; /* Not exact if large! */
struct tm mtime;
localtime_r(&(info->st_mtime), &mtime);
printf("%04d-%02d-%02d %02d:%02d:%02d",
mtime.tm_year+1900, mtime.tm_mon+1, mtime.tm_mday,
mtime.tm_hour, mtime.tm_min, mtime.tm_sec);
if (bytes >= 1099511627776.0)
printf(" %9.3f TiB", bytes / 1099511627776.0);
else
if (bytes >= 1073741824.0)
printf(" %9.3f GiB", bytes / 1073741824.0);
else
if (bytes >= 1048576.0)
printf(" %9.3f MiB", bytes / 1048576.0);
else
if (bytes >= 1024.0)
printf(" %9.3f KiB", bytes / 1024.0);
else
printf(" %9.0f B ", bytes);
if (typeflag == FTW_SL) {
char *target;
size_t maxlen = 1023;
ssize_t len;
while (1) {
target = malloc(maxlen + 1);
if (target == NULL)
return ENOMEM;
len = readlink(filepath, target, maxlen);
if (len == (ssize_t)-1) {
const int saved_errno = errno;
free(target);
return saved_errno;
}
if (len >= (ssize_t)maxlen) {
free(target);
maxlen += 1024;
continue;
}
target[len] = '\0';
break;
}
printf(" %s -> %s\n", filepath, target);
free(target);
} else
if (typeflag == FTW_SLN)
printf(" %s (dangling symlink)\n", filepath);
else
if (typeflag == FTW_F)
printf(" %s\n", filepath);
else
if (typeflag == FTW_D || typeflag == FTW_DP)
printf(" %s/\n", filepath);
else
if (typeflag == FTW_DNR)
printf(" %s/ (unreadable)\n", filepath);
else
printf(" %s (unknown)\n", filepath);
return 0;
}
int print_directory_tree(const char *const dirpath)
{
int result;
/* Invalid directory path? */
if (dirpath == NULL || *dirpath == '\0')
return errno = EINVAL;
result = nftw(dirpath, print_entry, USE_FDS, FTW_PHYS);
if (result >= 0)
errno = result;
return errno;
}
int main(int argc, char *argv[])
{
int arg;
if (argc < 2) {
if (print_directory_tree(".")) {
fprintf(stderr, "%s.\n", strerror(errno));
return EXIT_FAILURE;
}
} else {
for (arg = 1; arg < argc; arg++) {
if (print_directory_tree(argv[arg])) {
fprintf(stderr, "%s.\n", strerror(errno));
return EXIT_FAILURE;
}
}
}
return EXIT_SUCCESS;
}
Většina výše uvedeného kódu je v print_entry()
. Jeho úkolem je vytisknout každou položku adresáře. V print_directory_tree()
, řekneme nftw()
volat jej pro každou položku adresáře, kterou vidí.
Jediným detailem výše uvedeným ručně zvlněným je rozhodnutí o tom, kolik deskriptorů souborů by mělo být nftw()
použití. Pokud váš program používá během procházení stromem souborů maximálně dva další deskriptory souborů (kromě standardních proudů), je známo, že 15 je bezpečné (na všech systémech s nftw()
a je většinou kompatibilní s POSIX).
V Linuxu můžete použít sysconf(_SC_OPEN_MAX)
najít maximální počet otevřených souborů a odečíst počet, který používáte současně s nftw()
volání, ale neobtěžoval bych se (pokud jsem nevěděl, že se nástroj bude používat většinou s patologicky hlubokými adresářovými strukturami). Patnáct deskriptorů není omezit hloubku stromu; nftw()
jen se zpomalí (a nemusí detekovat změny v adresáři, pokud procházíte adresářem hlouběji než 13 adresářů z tohoto jednoho, ačkoli kompromisy a obecná schopnost detekovat změny se liší mezi systémy a implementacemi knihovny C). Pouhé použití takové konstanty doby kompilace udržuje kód přenosný – měl by fungovat nejen na Linuxu, ale i na Mac OS X a všech současných variantách BSD a také na většině ostatních nepříliš starých unixových variant.
V komentáři Ruslan zmínil, že museli přejít na nftw64()
protože obsahovaly položky souborového systému, které vyžadovaly 64bitové velikosti/offsety, a "normální" verzi nftw()
se nezdařilo s errno == EOVERFLOW
. Správným řešením je nepřepínat na 64bitové funkce specifické pro GLIBC, ale definovat _LARGEFILE64_SOURCE
a _FILE_OFFSET_BITS 64
. Ty říkají knihovně C, aby přešla na 64bitové velikosti souborů a offsety, pokud je to možné, při použití standardních funkcí (nftw()
, fstat()
, a tak dále) a názvy typů (off_t
atd.).