Walk a directory/Recursively: Difference between revisions

Content added Content deleted
(→‎{{header|C}}: Add alternate solution using BSD fts(3).)
(→‎{{header|C}}: Add solution for Windows.)
Line 265: Line 265:
{
{
pmatch(".", "*.c");
pmatch(".", "*.c");
return 0;
}</lang>

=== [[Windows]] ===
{{works with|MinGW}}
<lang c>#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <wchar.h>

/* Print "message: last Win32 error" to stderr. */
void
oops(const wchar_t *message)
{
wchar_t *buf;
DWORD error;

buf = NULL;
error = GetLastError();
FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, error, 0, (wchar_t *)&buf, 0, NULL);

if (buf) {
fwprintf(stderr, L"%ls: %ls", message, buf);
LocalFree(buf);
} else {
/* FormatMessageW failed. */
fwprintf(stderr, L"%ls: unknown error 0x%x\n",
message, error);
}
}

/*
* Print all files in a given directory tree that match a given wildcard
* pattern.
*/
int
main()
{
struct stack {
wchar_t *path;
size_t pathlen;
size_t slashlen;
HANDLE ffh;
WIN32_FIND_DATAW ffd;
struct stack *next;
} *dir, dir0, *ndir;
size_t patternlen;
int argc;
wchar_t **argv, *buf, c, *pattern;

/* MinGW never provides wmain(argc, argv). */
argv = CommandLineToArgvW(GetCommandLineW(), &argc);
if (argv == NULL) {
oops(L"CommandLineToArgvW");
exit(1);
}

if (argc != 3) {
fwprintf(stderr, L"usage: %ls dir pattern\n", argv[0]);
exit(1);
}

dir0.path = argv[1];
dir0.pathlen = wcslen(dir0.path);
pattern = argv[2];
patternlen = wcslen(pattern);

if (patternlen == 0 ||
wcscmp(pattern, L".") == 0 ||
wcscmp(pattern, L"..") == 0 ||
wcschr(pattern, L'/') ||
wcschr(pattern, L'\\')) {
fwprintf(stderr, L"%ls: invalid pattern\n", pattern);
exit(1);
}

/*
* Must put backslash between path and pattern, unless
* last character of path is slash or colon.
*
* 'dir' => 'dir\*'
* 'dir\' => 'dir\*'
* 'dir/' => 'dir/*'
* 'c:' => 'c:*'
*
* 'c:*' and 'c:\*' are different files!
*/
c = dir0.path[dir0.pathlen - 1];
if (c == ':' || c == '/' || c == '\\')
dir0.slashlen = dir0.pathlen;
else
dir0.slashlen = dir0.pathlen + 1;

/* Allocate space for path + backslash + pattern + \0. */
buf = calloc(dir0.slashlen + patternlen + 1, sizeof buf[0]);
if (buf == NULL) {
perror("calloc");
exit(1);
}
dir0.path = wmemcpy(buf, dir0.path, dir0.pathlen + 1);

dir0.ffh = INVALID_HANDLE_VALUE;
dir0.next = NULL;
dir = &dir0;

/* Loop for each directory in linked list. */
loop:
while (dir) {
/*
* At first visit to directory:
* Print the matching files. Then, begin to find
* subdirectories.
*
* At later visit:
* dir->ffh is the handle to find subdirectories.
* Continue to find them.
*/
if (dir->ffh == INVALID_HANDLE_VALUE) {
/* Append backslash + pattern + \0 to path. */
dir->path[dir->pathlen] = '\\';
wmemcpy(dir->path + dir->slashlen,
pattern, patternlen + 1);

/* Find all files to match pattern. */
dir->ffh = FindFirstFileW(dir->path, &dir->ffd);
if (dir->ffh == INVALID_HANDLE_VALUE) {
/* Check if no files match pattern. */
if (GetLastError() == ERROR_FILE_NOT_FOUND)
goto subdirs;

/* Bail out from other errors. */
dir->path[dir->pathlen] = '\0';
oops(dir->path);
goto popdir;
}

/* Remove pattern from path; keep backslash. */
dir->path[dir->slashlen] = '\0';

/* Print all files to match pattern. */
do {
wprintf(L"%ls%ls\n",
dir->path, dir->ffd.cFileName);
} while (FindNextFileW(dir->ffh, &dir->ffd) != 0);
if (GetLastError() != ERROR_NO_MORE_FILES) {
dir->path[dir->pathlen] = '\0';
oops(dir->path);
}
FindClose(dir->ffh);

subdirs:
/* Append * + \0 to path. */
dir->path[dir->slashlen] = '*';
dir->path[dir->slashlen + 1] = '\0';

/* Find first possible subdirectory. */
dir->ffh = FindFirstFileExW(dir->path,
FindExInfoStandard, &dir->ffd,
FindExSearchLimitToDirectories, NULL, 0);
if (dir->ffh == INVALID_HANDLE_VALUE) {
dir->path[dir->pathlen] = '\0';
oops(dir->path);
goto popdir;
}
} else {
/* Find next possible subdirectory. */
if (FindNextFileW(dir->ffh, &dir->ffd) == 0)
goto closeffh;
}

/* Enter subdirectories. */
do {
const wchar_t *fn = dir->ffd.cFileName;
const DWORD attr = dir->ffd.dwFileAttributes;
size_t buflen, fnlen;

/*
* Skip '.' and '..', because they are links to
* the current and parent directories, so they
* are not subdirectories.
*
* Skip any file that is not a directory.
*
* Skip all reparse points, because they might
* be symbolic links. They might form a cycle,
* with a directory inside itself.
*/
if (wcscmp(fn, L".") == 0 ||
wcscmp(fn, L"..") == 0 ||
(attr & FILE_ATTRIBUTE_DIRECTORY) == 0 ||
(attr & FILE_ATTRIBUTE_REPARSE_POINT))
continue;

ndir = malloc(sizeof *ndir);
if (ndir == NULL) {
perror("malloc");
exit(1);
}

/*
* Allocate space for path + backslash +
* fn + backslash + pattern + \0.
*/
fnlen = wcslen(fn);
buflen = dir->slashlen + fnlen + patternlen + 2;
buf = calloc(buflen, sizeof buf[0]);
if (buf == NULL) {
perror("malloc");
exit(1);
}

/* Copy path + backslash + fn + \0. */
wmemcpy(buf, dir->path, dir->slashlen);
wmemcpy(buf + dir->slashlen, fn, fnlen + 1);

/* Push dir to list. Enter dir. */
ndir->path = buf;
ndir->pathlen = dir->slashlen + fnlen;
ndir->slashlen = ndir->pathlen + 1;
ndir->ffh = INVALID_HANDLE_VALUE;
ndir->next = dir;
dir = ndir;
goto loop; /* Continue outer loop. */
} while (FindNextFileW(dir->ffh, &dir->ffd) != 0);
closeffh:
if (GetLastError() != ERROR_NO_MORE_FILES) {
dir->path[dir->pathlen] = '\0';
oops(dir->path);
}
FindClose(dir->ffh);

popdir:
/* Pop dir from list, free dir, but never free dir0. */
free(dir->path);
if (ndir = dir->next)
free(dir);
dir = ndir;
}

return 0;
return 0;
}</lang>
}</lang>