/* * A simple and quick! "find . -type f" replacement program for Windows. * * Features: * 1. Only prints file names - it does *not* print directory names. * 2. Prints all a directory's files, before processing its subdirectories. * 3. Does *not* sort the output. But FindNextFile() does that on NTFS. * 4. Prints filenames in UNIX "/" separated format. * 5. Does not prefix outputted filenames with redundant "./". * * Copyright (c) 2007-2008 Alex Davies. All rights reserved. This program * is free software; you can redistribute it and/or modify it under the * same terms as Perl itself. */ #include #include #define QFIND_VERSION "1.05" /* MAX_PATH = 260, but have seen filenames with a few more characters in. */ #define MAXPATH 270 #define DIR_STACK_SIZE (512 * 1024) #define DIR_STACK_BUF_SIZE (DIR_STACK_SIZE * 10) /* Use a stack of directories to allow us to print all the files * in a directory before processing its subdirectories. */ static char* dir_stack[DIR_STACK_SIZE]; /* The string pointers in dir_stack[i] point inside: */ static char dir_stack_buf[DIR_STACK_BUF_SIZE]; /* The next free slot in dir_stack: */ static int dir_stack_top; static char *dir_stack_buf_end = dir_stack_buf + DIR_STACK_BUF_SIZE; /* Functions */ static void scan_dir(char *dirname); static void print_files(char *dirname); static void warn(char *dirname, DWORD err, char *msg); static char * get_error_msg(DWORD err); /**********************************************************************/ int __cdecl main(int argc, char *argv[]) { char *s, rootDir[MAXPATH+1]; /* Initialise the dir_stack system. */ dir_stack[0] = &dir_stack_buf[0]; dir_stack_top = 0; if (argc <= 1) { scan_dir(""); } else if (argc == 2 && *argv[1]) { strncpy(rootDir, argv[1], MAXPATH); /* Replace \'s with /'s and ensure it ends in a /. */ for (s = rootDir; *s; ++s) { if (*s == '\\') *s = '/'; } if (*(s-1) != '/') { *s = '/'; *(s+1) = 0; } scan_dir(rootDir); } else { fprintf(stderr, "Usage: qfind [DIR]\nVersion %s\n", QFIND_VERSION); exit(1); } return 0; } /**********************************************************************/ static void scan_dir(char *dirname) /* dirname is either "" or terminated with a '/' */ { char subDir[MAXPATH+1]; int i, start, end; start = dir_stack_top; print_files(dirname); end = dir_stack_top; /* And now process sub directories. */ for (i = start; i < end; ++i) { sprintf(subDir, "%s%s/", dirname, dir_stack[i]); scan_dir(subDir); } dir_stack_top = start; } /**********************************************************************/ static void print_files(char *dirname) { WIN32_FIND_DATA fData; HANDLE hList; char *next_slot; char dirBuf[MAXPATH+1]; char err; /* NB. dirname is either "" or terminated with a '/'. */ sprintf(dirBuf, "%s*", dirname); /* the glob pattern */ /* Get the *first* file. */ hList = FindFirstFile(dirBuf, &fData); if (hList == INVALID_HANDLE_VALUE) { warn(dirname, GetLastError(), ""); return; } for (;;) { if (fData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { /* Ignore "." and ".." */ if (!(fData.cFileName[0] == '.' && (!fData.cFileName[1] || (fData.cFileName[1] == '.' && !fData.cFileName[2])))) { next_slot = dir_stack[dir_stack_top] + strlen(fData.cFileName) + 1; if (dir_stack_top >= DIR_STACK_SIZE) { err = 1; goto process_dir_now; } else if (next_slot >= dir_stack_buf_end) { err = 2; process_dir_now: /* No space to store the directory in the dir_stack, * so process it now. */ sprintf(dirBuf, "%s%s/", dirname, (char *) fData.cFileName); warn(dirBuf, 0, ((err == 1) ? "exceeded DIR_STACK_SIZE: " : "exceeded DIR_STACK_BUF_SIZE: ")); scan_dir(dirBuf); } else { /* Add the directory to the dir_stack. */ strcpy(dir_stack[dir_stack_top++], (char *) fData.cFileName); dir_stack[dir_stack_top] = next_slot; } } } else { printf("%s%s\n", dirname, fData.cFileName); } /* Get the *next* file. */ if (!FindNextFile(hList, &fData)) { DWORD err = GetLastError(); if (err != ERROR_NO_MORE_FILES) { warn(dirname, err, "FindNextFile error: "); } break; } } if (FindClose(hList) == 0) { warn(dirname, GetLastError(), "FindClose error: "); } } /**********************************************************************/ static void warn(char *dirname, DWORD err, char *msg) { char *s, *slash = NULL; /* Remove any trailing '/' from the dirname in the error message * except for the case of the root directory. */ for (s = dirname; *s; ++s) { if (!*(s+1) && *s == '/' && s != dirname) { slash = s; *s = 0; break; } } if (err) { fprintf(stderr, "qfind: %s%s: %s\n", msg, dirname, get_error_msg(err)); } else { fprintf(stderr, "qfind: %s%s\n", msg, dirname); } if (slash != NULL) *slash = '/'; } /**********************************************************************/ static char * get_error_msg(DWORD err) { static char msgBuf[256]; char *s; if (FormatMessage( /* ...returns 0 on error */ FORMAT_MESSAGE_FROM_SYSTEM, NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), /* Default language */ msgBuf, 255, NULL )) { /* Chomp any trailing newline from the error message. Needed! */ for (s = msgBuf; *s; ++s) { if (*s == '\n') { *s = 0; break; } } } else { sprintf(msgBuf, "error code %d (FormatMessage error: %s)", err, GetLastError()); } return (char *) msgBuf; }