| /* | |
| * Copyright (c) Meta Platforms, Inc. and affiliates. | |
| * All rights reserved. | |
| * | |
| * This source code is licensed under both the BSD-style license (found in the | |
| * LICENSE file in the root directory of this source tree) and the GPLv2 (found | |
| * in the COPYING file in the root directory of this source tree). | |
| * You may select, at your option, one of the above-listed licenses. | |
| */ | |
| /*-**************************************** | |
| * Dependencies | |
| ******************************************/ | |
| #include "util.h" /* note : ensure that platform.h is included first ! */ | |
| #include <stdlib.h> /* malloc, realloc, free */ | |
| #include <stdio.h> /* fprintf */ | |
| #include <time.h> /* clock_t, clock, CLOCKS_PER_SEC, nanosleep */ | |
| #include <errno.h> | |
| #include <assert.h> | |
| #if defined(__FreeBSD__) | |
| #include <sys/param.h> /* __FreeBSD_version */ | |
| #endif /* #ifdef __FreeBSD__ */ | |
| #if defined(_WIN32) | |
| # include <sys/utime.h> /* utime */ | |
| # include <io.h> /* _chmod */ | |
| # define ZSTD_USE_UTIMENSAT 0 | |
| #else | |
| # include <unistd.h> /* chown, stat */ | |
| # include <sys/stat.h> /* utimensat, st_mtime */ | |
| # if (PLATFORM_POSIX_VERSION >= 200809L && defined(st_mtime)) \ | |
| || (defined(__FreeBSD__) && __FreeBSD_version >= 1100056) | |
| # define ZSTD_USE_UTIMENSAT 1 | |
| # else | |
| # define ZSTD_USE_UTIMENSAT 0 | |
| # endif | |
| # if ZSTD_USE_UTIMENSAT | |
| # include <fcntl.h> /* AT_FDCWD */ | |
| # else | |
| # include <utime.h> /* utime */ | |
| # endif | |
| #endif | |
| #if defined(_MSC_VER) || defined(__MINGW32__) || defined (__MSVCRT__) | |
| #include <direct.h> /* needed for _mkdir in windows */ | |
| #endif | |
| #if defined(__linux__) || (PLATFORM_POSIX_VERSION >= 200112L) /* opendir, readdir require POSIX.1-2001 */ | |
| # include <dirent.h> /* opendir, readdir */ | |
| # include <string.h> /* strerror, memcpy */ | |
| #endif /* #ifdef _WIN32 */ | |
| /*-**************************************** | |
| * Internal Macros | |
| ******************************************/ | |
| /* CONTROL is almost like an assert(), but is never disabled. | |
| * It's designed for failures that may happen rarely, | |
| * but we don't want to maintain a specific error code path for them, | |
| * such as a malloc() returning NULL for example. | |
| * Since it's always active, this macro can trigger side effects. | |
| */ | |
| #define CONTROL(c) { \ | |
| if (!(c)) { \ | |
| UTIL_DISPLAYLEVEL(1, "Error : %s, %i : %s", \ | |
| __FILE__, __LINE__, #c); \ | |
| exit(1); \ | |
| } } | |
| /* console log */ | |
| #define UTIL_DISPLAY(...) fprintf(stderr, __VA_ARGS__) | |
| #define UTIL_DISPLAYLEVEL(l, ...) { if (g_utilDisplayLevel>=l) { UTIL_DISPLAY(__VA_ARGS__); } } | |
| static int g_traceDepth = 0; | |
| int g_traceFileStat = 0; | |
| #define UTIL_TRACE_CALL(...) \ | |
| { \ | |
| if (g_traceFileStat) { \ | |
| UTIL_DISPLAY("Trace:FileStat: %*s> ", g_traceDepth, ""); \ | |
| UTIL_DISPLAY(__VA_ARGS__); \ | |
| UTIL_DISPLAY("\n"); \ | |
| ++g_traceDepth; \ | |
| } \ | |
| } | |
| #define UTIL_TRACE_RET(ret) \ | |
| { \ | |
| if (g_traceFileStat) { \ | |
| --g_traceDepth; \ | |
| UTIL_DISPLAY("Trace:FileStat: %*s< %d\n", g_traceDepth, "", (ret)); \ | |
| } \ | |
| } | |
| /* A modified version of realloc(). | |
| * If UTIL_realloc() fails the original block is freed. | |
| */ | |
| UTIL_STATIC void* UTIL_realloc(void *ptr, size_t size) | |
| { | |
| void *newptr = realloc(ptr, size); | |
| if (newptr) return newptr; | |
| free(ptr); | |
| return NULL; | |
| } | |
| #if defined(_MSC_VER) | |
| #define chmod _chmod | |
| #endif | |
| #ifndef ZSTD_HAVE_FCHMOD | |
| #if PLATFORM_POSIX_VERSION >= 199309L | |
| #define ZSTD_HAVE_FCHMOD | |
| #endif | |
| #endif | |
| #ifndef ZSTD_HAVE_FCHOWN | |
| #if PLATFORM_POSIX_VERSION >= 200809L | |
| #define ZSTD_HAVE_FCHOWN | |
| #endif | |
| #endif | |
| /*-**************************************** | |
| * Console log | |
| ******************************************/ | |
| int g_utilDisplayLevel; | |
| int UTIL_requireUserConfirmation(const char* prompt, const char* abortMsg, | |
| const char* acceptableLetters, int hasStdinInput) { | |
| int ch, result; | |
| if (hasStdinInput) { | |
| UTIL_DISPLAY("stdin is an input - not proceeding.\n"); | |
| return 1; | |
| } | |
| UTIL_DISPLAY("%s", prompt); | |
| ch = getchar(); | |
| result = 0; | |
| if (strchr(acceptableLetters, ch) == NULL) { | |
| UTIL_DISPLAY("%s \n", abortMsg); | |
| result = 1; | |
| } | |
| /* flush the rest */ | |
| while ((ch!=EOF) && (ch!='\n')) | |
| ch = getchar(); | |
| return result; | |
| } | |
| /*-************************************* | |
| * Constants | |
| ***************************************/ | |
| #define LIST_SIZE_INCREASE (8*1024) | |
| #define MAX_FILE_OF_FILE_NAMES_SIZE (1<<20)*50 | |
| /*-************************************* | |
| * Functions | |
| ***************************************/ | |
| void UTIL_traceFileStat(void) | |
| { | |
| g_traceFileStat = 1; | |
| } | |
| int UTIL_fstat(const int fd, const char* filename, stat_t* statbuf) | |
| { | |
| int ret; | |
| UTIL_TRACE_CALL("UTIL_stat(%d, %s)", fd, filename); | |
| #if defined(_MSC_VER) | |
| if (fd >= 0) { | |
| ret = !_fstat64(fd, statbuf); | |
| } else { | |
| ret = !_stat64(filename, statbuf); | |
| } | |
| #elif defined(__MINGW32__) && defined (__MSVCRT__) | |
| if (fd >= 0) { | |
| ret = !_fstati64(fd, statbuf); | |
| } else { | |
| ret = !_stati64(filename, statbuf); | |
| } | |
| #else | |
| if (fd >= 0) { | |
| ret = !fstat(fd, statbuf); | |
| } else { | |
| ret = !stat(filename, statbuf); | |
| } | |
| #endif | |
| UTIL_TRACE_RET(ret); | |
| return ret; | |
| } | |
| int UTIL_stat(const char* filename, stat_t* statbuf) | |
| { | |
| return UTIL_fstat(-1, filename, statbuf); | |
| } | |
| int UTIL_isRegularFile(const char* infilename) | |
| { | |
| stat_t statbuf; | |
| int ret; | |
| UTIL_TRACE_CALL("UTIL_isRegularFile(%s)", infilename); | |
| ret = UTIL_stat(infilename, &statbuf) && UTIL_isRegularFileStat(&statbuf); | |
| UTIL_TRACE_RET(ret); | |
| return ret; | |
| } | |
| int UTIL_isRegularFileStat(const stat_t* statbuf) | |
| { | |
| #if defined(_MSC_VER) | |
| return (statbuf->st_mode & S_IFREG) != 0; | |
| #else | |
| return S_ISREG(statbuf->st_mode) != 0; | |
| #endif | |
| } | |
| /* like chmod, but avoid changing permission of /dev/null */ | |
| int UTIL_chmod(char const* filename, const stat_t* statbuf, mode_t permissions) | |
| { | |
| return UTIL_fchmod(-1, filename, statbuf, permissions); | |
| } | |
| int UTIL_fchmod(const int fd, char const* filename, const stat_t* statbuf, mode_t permissions) | |
| { | |
| stat_t localStatBuf; | |
| UTIL_TRACE_CALL("UTIL_chmod(%s, %#4o)", filename, (unsigned)permissions); | |
| if (statbuf == NULL) { | |
| if (!UTIL_fstat(fd, filename, &localStatBuf)) { | |
| UTIL_TRACE_RET(0); | |
| return 0; | |
| } | |
| statbuf = &localStatBuf; | |
| } | |
| if (!UTIL_isRegularFileStat(statbuf)) { | |
| UTIL_TRACE_RET(0); | |
| return 0; /* pretend success, but don't change anything */ | |
| } | |
| #ifdef ZSTD_HAVE_FCHMOD | |
| if (fd >= 0) { | |
| int ret; | |
| UTIL_TRACE_CALL("fchmod"); | |
| ret = fchmod(fd, permissions); | |
| UTIL_TRACE_RET(ret); | |
| UTIL_TRACE_RET(ret); | |
| return ret; | |
| } else | |
| #endif | |
| { | |
| int ret; | |
| UTIL_TRACE_CALL("chmod"); | |
| ret = chmod(filename, permissions); | |
| UTIL_TRACE_RET(ret); | |
| UTIL_TRACE_RET(ret); | |
| return ret; | |
| } | |
| } | |
| /* set access and modification times */ | |
| int UTIL_utime(const char* filename, const stat_t *statbuf) | |
| { | |
| int ret; | |
| UTIL_TRACE_CALL("UTIL_utime(%s)", filename); | |
| /* We check that st_mtime is a macro here in order to give us confidence | |
| * that struct stat has a struct timespec st_mtim member. We need this | |
| * check because there are some platforms that claim to be POSIX 2008 | |
| * compliant but which do not have st_mtim... */ | |
| /* FreeBSD has implemented POSIX 2008 for a long time but still only | |
| * advertises support for POSIX 2001. They have a version macro that | |
| * lets us safely gate them in. | |
| * See https://docs.freebsd.org/en/books/porters-handbook/versions/. | |
| */ | |
| #if ZSTD_USE_UTIMENSAT | |
| { | |
| /* (atime, mtime) */ | |
| struct timespec timebuf[2] = { {0, UTIME_NOW} }; | |
| timebuf[1] = statbuf->st_mtim; | |
| ret = utimensat(AT_FDCWD, filename, timebuf, 0); | |
| } | |
| #else | |
| { | |
| struct utimbuf timebuf; | |
| timebuf.actime = time(NULL); | |
| timebuf.modtime = statbuf->st_mtime; | |
| ret = utime(filename, &timebuf); | |
| } | |
| #endif | |
| errno = 0; | |
| UTIL_TRACE_RET(ret); | |
| return ret; | |
| } | |
| int UTIL_setFileStat(const char *filename, const stat_t *statbuf) | |
| { | |
| return UTIL_setFDStat(-1, filename, statbuf); | |
| } | |
| int UTIL_setFDStat(const int fd, const char *filename, const stat_t *statbuf) | |
| { | |
| int res = 0; | |
| stat_t curStatBuf; | |
| UTIL_TRACE_CALL("UTIL_setFileStat(%d, %s)", fd, filename); | |
| if (!UTIL_fstat(fd, filename, &curStatBuf) || !UTIL_isRegularFileStat(&curStatBuf)) { | |
| UTIL_TRACE_RET(-1); | |
| return -1; | |
| } | |
| /* Mimic gzip's behavior: | |
| * | |
| * "Change the group first, then the permissions, then the owner. | |
| * That way, the permissions will be correct on systems that allow | |
| * users to give away files, without introducing a security hole. | |
| * Security depends on permissions not containing the setuid or | |
| * setgid bits." */ | |
| #if !defined(_WIN32) | |
| #ifdef ZSTD_HAVE_FCHOWN | |
| if (fd >= 0) { | |
| res += fchown(fd, -1, statbuf->st_gid); /* Apply group ownership */ | |
| } else | |
| #endif | |
| { | |
| res += chown(filename, -1, statbuf->st_gid); /* Apply group ownership */ | |
| } | |
| #endif | |
| res += UTIL_fchmod(fd, filename, &curStatBuf, statbuf->st_mode & 0777); /* Copy file permissions */ | |
| #if !defined(_WIN32) | |
| #ifdef ZSTD_HAVE_FCHOWN | |
| if (fd >= 0) { | |
| res += fchown(fd, statbuf->st_uid, -1); /* Apply user ownership */ | |
| } else | |
| #endif | |
| { | |
| res += chown(filename, statbuf->st_uid, -1); /* Apply user ownership */ | |
| } | |
| #endif | |
| errno = 0; | |
| UTIL_TRACE_RET(-res); | |
| return -res; /* number of errors is returned */ | |
| } | |
| int UTIL_isDirectory(const char* infilename) | |
| { | |
| stat_t statbuf; | |
| int ret; | |
| UTIL_TRACE_CALL("UTIL_isDirectory(%s)", infilename); | |
| ret = UTIL_stat(infilename, &statbuf) && UTIL_isDirectoryStat(&statbuf); | |
| UTIL_TRACE_RET(ret); | |
| return ret; | |
| } | |
| int UTIL_isDirectoryStat(const stat_t* statbuf) | |
| { | |
| int ret; | |
| UTIL_TRACE_CALL("UTIL_isDirectoryStat()"); | |
| #if defined(_MSC_VER) | |
| ret = (statbuf->st_mode & _S_IFDIR) != 0; | |
| #else | |
| ret = S_ISDIR(statbuf->st_mode) != 0; | |
| #endif | |
| UTIL_TRACE_RET(ret); | |
| return ret; | |
| } | |
| int UTIL_compareStr(const void *p1, const void *p2) { | |
| return strcmp(* (char * const *) p1, * (char * const *) p2); | |
| } | |
| int UTIL_isSameFile(const char* fName1, const char* fName2) | |
| { | |
| int ret; | |
| assert(fName1 != NULL); assert(fName2 != NULL); | |
| UTIL_TRACE_CALL("UTIL_isSameFile(%s, %s)", fName1, fName2); | |
| #if defined(_MSC_VER) || defined(_WIN32) | |
| /* note : Visual does not support file identification by inode. | |
| * inode does not work on Windows, even with a posix layer, like msys2. | |
| * The following work-around is limited to detecting exact name repetition only, | |
| * aka `filename` is considered different from `subdir/../filename` */ | |
| ret = !strcmp(fName1, fName2); | |
| #else | |
| { stat_t file1Stat; | |
| stat_t file2Stat; | |
| ret = UTIL_stat(fName1, &file1Stat) | |
| && UTIL_stat(fName2, &file2Stat) | |
| && UTIL_isSameFileStat(fName1, fName2, &file1Stat, &file2Stat); | |
| } | |
| #endif | |
| UTIL_TRACE_RET(ret); | |
| return ret; | |
| } | |
| int UTIL_isSameFileStat( | |
| const char* fName1, const char* fName2, | |
| const stat_t* file1Stat, const stat_t* file2Stat) | |
| { | |
| int ret; | |
| assert(fName1 != NULL); assert(fName2 != NULL); | |
| UTIL_TRACE_CALL("UTIL_isSameFileStat(%s, %s)", fName1, fName2); | |
| #if defined(_MSC_VER) || defined(_WIN32) | |
| /* note : Visual does not support file identification by inode. | |
| * inode does not work on Windows, even with a posix layer, like msys2. | |
| * The following work-around is limited to detecting exact name repetition only, | |
| * aka `filename` is considered different from `subdir/../filename` */ | |
| (void)file1Stat; | |
| (void)file2Stat; | |
| ret = !strcmp(fName1, fName2); | |
| #else | |
| { | |
| ret = (file1Stat->st_dev == file2Stat->st_dev) | |
| && (file1Stat->st_ino == file2Stat->st_ino); | |
| } | |
| #endif | |
| UTIL_TRACE_RET(ret); | |
| return ret; | |
| } | |
| /* UTIL_isFIFO : distinguish named pipes */ | |
| int UTIL_isFIFO(const char* infilename) | |
| { | |
| UTIL_TRACE_CALL("UTIL_isFIFO(%s)", infilename); | |
| /* macro guards, as defined in : https://linux.die.net/man/2/lstat */ | |
| #if PLATFORM_POSIX_VERSION >= 200112L | |
| { | |
| stat_t statbuf; | |
| if (UTIL_stat(infilename, &statbuf) && UTIL_isFIFOStat(&statbuf)) { | |
| UTIL_TRACE_RET(1); | |
| return 1; | |
| } | |
| } | |
| #endif | |
| (void)infilename; | |
| UTIL_TRACE_RET(0); | |
| return 0; | |
| } | |
| /* UTIL_isFIFO : distinguish named pipes */ | |
| int UTIL_isFIFOStat(const stat_t* statbuf) | |
| { | |
| /* macro guards, as defined in : https://linux.die.net/man/2/lstat */ | |
| #if PLATFORM_POSIX_VERSION >= 200112L | |
| if (S_ISFIFO(statbuf->st_mode)) return 1; | |
| #endif | |
| (void)statbuf; | |
| return 0; | |
| } | |
| /* UTIL_isBlockDevStat : distinguish named pipes */ | |
| int UTIL_isBlockDevStat(const stat_t* statbuf) | |
| { | |
| /* macro guards, as defined in : https://linux.die.net/man/2/lstat */ | |
| #if PLATFORM_POSIX_VERSION >= 200112L | |
| if (S_ISBLK(statbuf->st_mode)) return 1; | |
| #endif | |
| (void)statbuf; | |
| return 0; | |
| } | |
| int UTIL_isLink(const char* infilename) | |
| { | |
| UTIL_TRACE_CALL("UTIL_isLink(%s)", infilename); | |
| /* macro guards, as defined in : https://linux.die.net/man/2/lstat */ | |
| #if PLATFORM_POSIX_VERSION >= 200112L | |
| { | |
| stat_t statbuf; | |
| int const r = lstat(infilename, &statbuf); | |
| if (!r && S_ISLNK(statbuf.st_mode)) { | |
| UTIL_TRACE_RET(1); | |
| return 1; | |
| } | |
| } | |
| #endif | |
| (void)infilename; | |
| UTIL_TRACE_RET(0); | |
| return 0; | |
| } | |
| static int g_fakeStdinIsConsole = 0; | |
| static int g_fakeStderrIsConsole = 0; | |
| static int g_fakeStdoutIsConsole = 0; | |
| int UTIL_isConsole(FILE* file) | |
| { | |
| int ret; | |
| UTIL_TRACE_CALL("UTIL_isConsole(%d)", fileno(file)); | |
| if (file == stdin && g_fakeStdinIsConsole) | |
| ret = 1; | |
| else if (file == stderr && g_fakeStderrIsConsole) | |
| ret = 1; | |
| else if (file == stdout && g_fakeStdoutIsConsole) | |
| ret = 1; | |
| else | |
| ret = IS_CONSOLE(file); | |
| UTIL_TRACE_RET(ret); | |
| return ret; | |
| } | |
| void UTIL_fakeStdinIsConsole(void) | |
| { | |
| g_fakeStdinIsConsole = 1; | |
| } | |
| void UTIL_fakeStdoutIsConsole(void) | |
| { | |
| g_fakeStdoutIsConsole = 1; | |
| } | |
| void UTIL_fakeStderrIsConsole(void) | |
| { | |
| g_fakeStderrIsConsole = 1; | |
| } | |
| U64 UTIL_getFileSize(const char* infilename) | |
| { | |
| stat_t statbuf; | |
| UTIL_TRACE_CALL("UTIL_getFileSize(%s)", infilename); | |
| if (!UTIL_stat(infilename, &statbuf)) { | |
| UTIL_TRACE_RET(-1); | |
| return UTIL_FILESIZE_UNKNOWN; | |
| } | |
| { | |
| U64 const size = UTIL_getFileSizeStat(&statbuf); | |
| UTIL_TRACE_RET((int)size); | |
| return size; | |
| } | |
| } | |
| U64 UTIL_getFileSizeStat(const stat_t* statbuf) | |
| { | |
| if (!UTIL_isRegularFileStat(statbuf)) return UTIL_FILESIZE_UNKNOWN; | |
| #if defined(_MSC_VER) | |
| if (!(statbuf->st_mode & S_IFREG)) return UTIL_FILESIZE_UNKNOWN; | |
| #elif defined(__MINGW32__) && defined (__MSVCRT__) | |
| if (!(statbuf->st_mode & S_IFREG)) return UTIL_FILESIZE_UNKNOWN; | |
| #else | |
| if (!S_ISREG(statbuf->st_mode)) return UTIL_FILESIZE_UNKNOWN; | |
| #endif | |
| return (U64)statbuf->st_size; | |
| } | |
| UTIL_HumanReadableSize_t UTIL_makeHumanReadableSize(U64 size) | |
| { | |
| UTIL_HumanReadableSize_t hrs; | |
| if (g_utilDisplayLevel > 3) { | |
| /* In verbose mode, do not scale sizes down, except in the case of | |
| * values that exceed the integral precision of a double. */ | |
| if (size >= (1ull << 53)) { | |
| hrs.value = (double)size / (1ull << 20); | |
| hrs.suffix = " MiB"; | |
| /* At worst, a double representation of a maximal size will be | |
| * accurate to better than tens of kilobytes. */ | |
| hrs.precision = 2; | |
| } else { | |
| hrs.value = (double)size; | |
| hrs.suffix = " B"; | |
| hrs.precision = 0; | |
| } | |
| } else { | |
| /* In regular mode, scale sizes down and use suffixes. */ | |
| if (size >= (1ull << 60)) { | |
| hrs.value = (double)size / (1ull << 60); | |
| hrs.suffix = " EiB"; | |
| } else if (size >= (1ull << 50)) { | |
| hrs.value = (double)size / (1ull << 50); | |
| hrs.suffix = " PiB"; | |
| } else if (size >= (1ull << 40)) { | |
| hrs.value = (double)size / (1ull << 40); | |
| hrs.suffix = " TiB"; | |
| } else if (size >= (1ull << 30)) { | |
| hrs.value = (double)size / (1ull << 30); | |
| hrs.suffix = " GiB"; | |
| } else if (size >= (1ull << 20)) { | |
| hrs.value = (double)size / (1ull << 20); | |
| hrs.suffix = " MiB"; | |
| } else if (size >= (1ull << 10)) { | |
| hrs.value = (double)size / (1ull << 10); | |
| hrs.suffix = " KiB"; | |
| } else { | |
| hrs.value = (double)size; | |
| hrs.suffix = " B"; | |
| } | |
| if (hrs.value >= 100 || (U64)hrs.value == size) { | |
| hrs.precision = 0; | |
| } else if (hrs.value >= 10) { | |
| hrs.precision = 1; | |
| } else if (hrs.value > 1) { | |
| hrs.precision = 2; | |
| } else { | |
| hrs.precision = 3; | |
| } | |
| } | |
| return hrs; | |
| } | |
| U64 UTIL_getTotalFileSize(const char* const * fileNamesTable, unsigned nbFiles) | |
| { | |
| U64 total = 0; | |
| unsigned n; | |
| UTIL_TRACE_CALL("UTIL_getTotalFileSize(%u)", nbFiles); | |
| for (n=0; n<nbFiles; n++) { | |
| U64 const size = UTIL_getFileSize(fileNamesTable[n]); | |
| if (size == UTIL_FILESIZE_UNKNOWN) { | |
| UTIL_TRACE_RET(-1); | |
| return UTIL_FILESIZE_UNKNOWN; | |
| } | |
| total += size; | |
| } | |
| UTIL_TRACE_RET((int)total); | |
| return total; | |
| } | |
| /* condition : @file must be valid, and not have reached its end. | |
| * @return : length of line written into @buf, ended with `\0` instead of '\n', | |
| * or 0, if there is no new line */ | |
| static size_t readLineFromFile(char* buf, size_t len, FILE* file) | |
| { | |
| assert(!feof(file)); | |
| if ( fgets(buf, (int) len, file) == NULL ) return 0; | |
| { size_t linelen = strlen(buf); | |
| if (strlen(buf)==0) return 0; | |
| if (buf[linelen-1] == '\n') linelen--; | |
| buf[linelen] = '\0'; | |
| return linelen+1; | |
| } | |
| } | |
| /* Conditions : | |
| * size of @inputFileName file must be < @dstCapacity | |
| * @dst must be initialized | |
| * @return : nb of lines | |
| * or -1 if there's an error | |
| */ | |
| static int | |
| readLinesFromFile(void* dst, size_t dstCapacity, | |
| const char* inputFileName) | |
| { | |
| int nbFiles = 0; | |
| size_t pos = 0; | |
| char* const buf = (char*)dst; | |
| FILE* const inputFile = fopen(inputFileName, "r"); | |
| assert(dst != NULL); | |
| if(!inputFile) { | |
| if (g_utilDisplayLevel >= 1) perror("zstd:util:readLinesFromFile"); | |
| return -1; | |
| } | |
| while ( !feof(inputFile) ) { | |
| size_t const lineLength = readLineFromFile(buf+pos, dstCapacity-pos, inputFile); | |
| if (lineLength == 0) break; | |
| assert(pos + lineLength <= dstCapacity); /* '=' for inputFile not terminated with '\n' */ | |
| pos += lineLength; | |
| ++nbFiles; | |
| } | |
| CONTROL( fclose(inputFile) == 0 ); | |
| return nbFiles; | |
| } | |
| /*Note: buf is not freed in case function successfully created table because filesTable->fileNames[0] = buf*/ | |
| FileNamesTable* | |
| UTIL_createFileNamesTable_fromFileName(const char* inputFileName) | |
| { | |
| size_t nbFiles = 0; | |
| char* buf; | |
| size_t bufSize; | |
| stat_t statbuf; | |
| if (!UTIL_stat(inputFileName, &statbuf) || !UTIL_isRegularFileStat(&statbuf)) | |
| return NULL; | |
| { U64 const inputFileSize = UTIL_getFileSizeStat(&statbuf); | |
| if(inputFileSize > MAX_FILE_OF_FILE_NAMES_SIZE) | |
| return NULL; | |
| bufSize = (size_t)(inputFileSize + 1); /* (+1) to add '\0' at the end of last filename */ | |
| } | |
| buf = (char*) malloc(bufSize); | |
| CONTROL( buf != NULL ); | |
| { int const ret_nbFiles = readLinesFromFile(buf, bufSize, inputFileName); | |
| if (ret_nbFiles <= 0) { | |
| free(buf); | |
| return NULL; | |
| } | |
| nbFiles = (size_t)ret_nbFiles; | |
| } | |
| { const char** filenamesTable = (const char**) malloc(nbFiles * sizeof(*filenamesTable)); | |
| CONTROL(filenamesTable != NULL); | |
| { size_t fnb, pos = 0; | |
| for (fnb = 0; fnb < nbFiles; fnb++) { | |
| filenamesTable[fnb] = buf+pos; | |
| pos += strlen(buf+pos)+1; /* +1 for the finishing `\0` */ | |
| } | |
| assert(pos <= bufSize); | |
| } | |
| return UTIL_assembleFileNamesTable(filenamesTable, nbFiles, buf); | |
| } | |
| } | |
| static FileNamesTable* | |
| UTIL_assembleFileNamesTable2(const char** filenames, size_t tableSize, size_t tableCapacity, char* buf) | |
| { | |
| FileNamesTable* const table = (FileNamesTable*) malloc(sizeof(*table)); | |
| CONTROL(table != NULL); | |
| table->fileNames = filenames; | |
| table->buf = buf; | |
| table->tableSize = tableSize; | |
| table->tableCapacity = tableCapacity; | |
| return table; | |
| } | |
| FileNamesTable* | |
| UTIL_assembleFileNamesTable(const char** filenames, size_t tableSize, char* buf) | |
| { | |
| return UTIL_assembleFileNamesTable2(filenames, tableSize, tableSize, buf); | |
| } | |
| void UTIL_freeFileNamesTable(FileNamesTable* table) | |
| { | |
| if (table==NULL) return; | |
| free((void*)table->fileNames); | |
| free(table->buf); | |
| free(table); | |
| } | |
| FileNamesTable* UTIL_allocateFileNamesTable(size_t tableSize) | |
| { | |
| const char** const fnTable = (const char**)malloc(tableSize * sizeof(*fnTable)); | |
| FileNamesTable* fnt; | |
| if (fnTable==NULL) return NULL; | |
| fnt = UTIL_assembleFileNamesTable(fnTable, tableSize, NULL); | |
| fnt->tableSize = 0; /* the table is empty */ | |
| return fnt; | |
| } | |
| int UTIL_searchFileNamesTable(FileNamesTable* table, char const* name) { | |
| size_t i; | |
| for(i=0 ;i < table->tableSize; i++) { | |
| if(!strcmp(table->fileNames[i], name)) { | |
| return (int)i; | |
| } | |
| } | |
| return -1; | |
| } | |
| void UTIL_refFilename(FileNamesTable* fnt, const char* filename) | |
| { | |
| assert(fnt->tableSize < fnt->tableCapacity); | |
| fnt->fileNames[fnt->tableSize] = filename; | |
| fnt->tableSize++; | |
| } | |
| static size_t getTotalTableSize(FileNamesTable* table) | |
| { | |
| size_t fnb, totalSize = 0; | |
| for(fnb = 0 ; fnb < table->tableSize && table->fileNames[fnb] ; ++fnb) { | |
| totalSize += strlen(table->fileNames[fnb]) + 1; /* +1 to add '\0' at the end of each fileName */ | |
| } | |
| return totalSize; | |
| } | |
| FileNamesTable* | |
| UTIL_mergeFileNamesTable(FileNamesTable* table1, FileNamesTable* table2) | |
| { | |
| unsigned newTableIdx = 0; | |
| size_t pos = 0; | |
| size_t newTotalTableSize; | |
| char* buf; | |
| FileNamesTable* const newTable = UTIL_assembleFileNamesTable(NULL, 0, NULL); | |
| CONTROL( newTable != NULL ); | |
| newTotalTableSize = getTotalTableSize(table1) + getTotalTableSize(table2); | |
| buf = (char*) calloc(newTotalTableSize, sizeof(*buf)); | |
| CONTROL ( buf != NULL ); | |
| newTable->buf = buf; | |
| newTable->tableSize = table1->tableSize + table2->tableSize; | |
| newTable->fileNames = (const char **) calloc(newTable->tableSize, sizeof(*(newTable->fileNames))); | |
| CONTROL ( newTable->fileNames != NULL ); | |
| { unsigned idx1; | |
| for( idx1=0 ; (idx1 < table1->tableSize) && table1->fileNames[idx1] && (pos < newTotalTableSize); ++idx1, ++newTableIdx) { | |
| size_t const curLen = strlen(table1->fileNames[idx1]); | |
| memcpy(buf+pos, table1->fileNames[idx1], curLen); | |
| assert(newTableIdx <= newTable->tableSize); | |
| newTable->fileNames[newTableIdx] = buf+pos; | |
| pos += curLen+1; | |
| } } | |
| { unsigned idx2; | |
| for( idx2=0 ; (idx2 < table2->tableSize) && table2->fileNames[idx2] && (pos < newTotalTableSize) ; ++idx2, ++newTableIdx) { | |
| size_t const curLen = strlen(table2->fileNames[idx2]); | |
| memcpy(buf+pos, table2->fileNames[idx2], curLen); | |
| assert(newTableIdx < newTable->tableSize); | |
| newTable->fileNames[newTableIdx] = buf+pos; | |
| pos += curLen+1; | |
| } } | |
| assert(pos <= newTotalTableSize); | |
| newTable->tableSize = newTableIdx; | |
| UTIL_freeFileNamesTable(table1); | |
| UTIL_freeFileNamesTable(table2); | |
| return newTable; | |
| } | |
| #ifdef _WIN32 | |
| static int UTIL_prepareFileList(const char* dirName, | |
| char** bufStart, size_t* pos, | |
| char** bufEnd, int followLinks) | |
| { | |
| char* path; | |
| size_t dirLength, pathLength; | |
| int nbFiles = 0; | |
| WIN32_FIND_DATAA cFile; | |
| HANDLE hFile; | |
| dirLength = strlen(dirName); | |
| path = (char*) malloc(dirLength + 3); | |
| if (!path) return 0; | |
| memcpy(path, dirName, dirLength); | |
| path[dirLength] = '\\'; | |
| path[dirLength+1] = '*'; | |
| path[dirLength+2] = 0; | |
| hFile=FindFirstFileA(path, &cFile); | |
| if (hFile == INVALID_HANDLE_VALUE) { | |
| UTIL_DISPLAYLEVEL(1, "Cannot open directory '%s'\n", dirName); | |
| return 0; | |
| } | |
| free(path); | |
| do { | |
| size_t const fnameLength = strlen(cFile.cFileName); | |
| path = (char*) malloc(dirLength + fnameLength + 2); | |
| if (!path) { FindClose(hFile); return 0; } | |
| memcpy(path, dirName, dirLength); | |
| path[dirLength] = '\\'; | |
| memcpy(path+dirLength+1, cFile.cFileName, fnameLength); | |
| pathLength = dirLength+1+fnameLength; | |
| path[pathLength] = 0; | |
| if (cFile.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { | |
| if ( strcmp (cFile.cFileName, "..") == 0 | |
| || strcmp (cFile.cFileName, ".") == 0 ) | |
| continue; | |
| /* Recursively call "UTIL_prepareFileList" with the new path. */ | |
| nbFiles += UTIL_prepareFileList(path, bufStart, pos, bufEnd, followLinks); | |
| if (*bufStart == NULL) { free(path); FindClose(hFile); return 0; } | |
| } else if ( (cFile.dwFileAttributes & FILE_ATTRIBUTE_NORMAL) | |
| || (cFile.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE) | |
| || (cFile.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) ) { | |
| if (*bufStart + *pos + pathLength >= *bufEnd) { | |
| ptrdiff_t const newListSize = (*bufEnd - *bufStart) + LIST_SIZE_INCREASE; | |
| *bufStart = (char*)UTIL_realloc(*bufStart, newListSize); | |
| if (*bufStart == NULL) { free(path); FindClose(hFile); return 0; } | |
| *bufEnd = *bufStart + newListSize; | |
| } | |
| if (*bufStart + *pos + pathLength < *bufEnd) { | |
| memcpy(*bufStart + *pos, path, pathLength+1 /* include final \0 */); | |
| *pos += pathLength + 1; | |
| nbFiles++; | |
| } } | |
| free(path); | |
| } while (FindNextFileA(hFile, &cFile)); | |
| FindClose(hFile); | |
| return nbFiles; | |
| } | |
| #elif defined(__linux__) || (PLATFORM_POSIX_VERSION >= 200112L) /* opendir, readdir require POSIX.1-2001 */ | |
| static int UTIL_prepareFileList(const char *dirName, | |
| char** bufStart, size_t* pos, | |
| char** bufEnd, int followLinks) | |
| { | |
| DIR* dir; | |
| struct dirent * entry; | |
| size_t dirLength; | |
| int nbFiles = 0; | |
| if (!(dir = opendir(dirName))) { | |
| UTIL_DISPLAYLEVEL(1, "Cannot open directory '%s': %s\n", dirName, strerror(errno)); | |
| return 0; | |
| } | |
| dirLength = strlen(dirName); | |
| errno = 0; | |
| while ((entry = readdir(dir)) != NULL) { | |
| char* path; | |
| size_t fnameLength, pathLength; | |
| if (strcmp (entry->d_name, "..") == 0 || | |
| strcmp (entry->d_name, ".") == 0) continue; | |
| fnameLength = strlen(entry->d_name); | |
| path = (char*) malloc(dirLength + fnameLength + 2); | |
| if (!path) { closedir(dir); return 0; } | |
| memcpy(path, dirName, dirLength); | |
| path[dirLength] = '/'; | |
| memcpy(path+dirLength+1, entry->d_name, fnameLength); | |
| pathLength = dirLength+1+fnameLength; | |
| path[pathLength] = 0; | |
| if (!followLinks && UTIL_isLink(path)) { | |
| UTIL_DISPLAYLEVEL(2, "Warning : %s is a symbolic link, ignoring\n", path); | |
| free(path); | |
| continue; | |
| } | |
| if (UTIL_isDirectory(path)) { | |
| nbFiles += UTIL_prepareFileList(path, bufStart, pos, bufEnd, followLinks); /* Recursively call "UTIL_prepareFileList" with the new path. */ | |
| if (*bufStart == NULL) { free(path); closedir(dir); return 0; } | |
| } else { | |
| if (*bufStart + *pos + pathLength >= *bufEnd) { | |
| ptrdiff_t newListSize = (*bufEnd - *bufStart) + LIST_SIZE_INCREASE; | |
| assert(newListSize >= 0); | |
| *bufStart = (char*)UTIL_realloc(*bufStart, (size_t)newListSize); | |
| if (*bufStart != NULL) { | |
| *bufEnd = *bufStart + newListSize; | |
| } else { | |
| free(path); closedir(dir); return 0; | |
| } | |
| } | |
| if (*bufStart + *pos + pathLength < *bufEnd) { | |
| memcpy(*bufStart + *pos, path, pathLength + 1); /* with final \0 */ | |
| *pos += pathLength + 1; | |
| nbFiles++; | |
| } } | |
| free(path); | |
| errno = 0; /* clear errno after UTIL_isDirectory, UTIL_prepareFileList */ | |
| } | |
| if (errno != 0) { | |
| UTIL_DISPLAYLEVEL(1, "readdir(%s) error: %s \n", dirName, strerror(errno)); | |
| free(*bufStart); | |
| *bufStart = NULL; | |
| } | |
| closedir(dir); | |
| return nbFiles; | |
| } | |
| #else | |
| static int UTIL_prepareFileList(const char *dirName, | |
| char** bufStart, size_t* pos, | |
| char** bufEnd, int followLinks) | |
| { | |
| (void)bufStart; (void)bufEnd; (void)pos; (void)followLinks; | |
| UTIL_DISPLAYLEVEL(1, "Directory %s ignored (compiled without _WIN32 or _POSIX_C_SOURCE) \n", dirName); | |
| return 0; | |
| } | |
| #endif /* #ifdef _WIN32 */ | |
| int UTIL_isCompressedFile(const char *inputName, const char *extensionList[]) | |
| { | |
| const char* ext = UTIL_getFileExtension(inputName); | |
| while(*extensionList!=NULL) | |
| { | |
| const int isCompressedExtension = strcmp(ext,*extensionList); | |
| if(isCompressedExtension==0) | |
| return 1; | |
| ++extensionList; | |
| } | |
| return 0; | |
| } | |
| /*Utility function to get file extension from file */ | |
| const char* UTIL_getFileExtension(const char* infilename) | |
| { | |
| const char* extension = strrchr(infilename, '.'); | |
| if(!extension || extension==infilename) return ""; | |
| return extension; | |
| } | |
| static int pathnameHas2Dots(const char *pathname) | |
| { | |
| /* We need to figure out whether any ".." present in the path is a whole | |
| * path token, which is the case if it is bordered on both sides by either | |
| * the beginning/end of the path or by a directory separator. | |
| */ | |
| const char *needle = pathname; | |
| while (1) { | |
| needle = strstr(needle, ".."); | |
| if (needle == NULL) { | |
| return 0; | |
| } | |
| if ((needle == pathname || needle[-1] == PATH_SEP) | |
| && (needle[2] == '\0' || needle[2] == PATH_SEP)) { | |
| return 1; | |
| } | |
| /* increment so we search for the next match */ | |
| needle++; | |
| }; | |
| return 0; | |
| } | |
| static int isFileNameValidForMirroredOutput(const char *filename) | |
| { | |
| return !pathnameHas2Dots(filename); | |
| } | |
| #define DIR_DEFAULT_MODE 0755 | |
| static mode_t getDirMode(const char *dirName) | |
| { | |
| stat_t st; | |
| if (!UTIL_stat(dirName, &st)) { | |
| UTIL_DISPLAY("zstd: failed to get DIR stats %s: %s\n", dirName, strerror(errno)); | |
| return DIR_DEFAULT_MODE; | |
| } | |
| if (!UTIL_isDirectoryStat(&st)) { | |
| UTIL_DISPLAY("zstd: expected directory: %s\n", dirName); | |
| return DIR_DEFAULT_MODE; | |
| } | |
| return st.st_mode; | |
| } | |
| static int makeDir(const char *dir, mode_t mode) | |
| { | |
| #if defined(_MSC_VER) || defined(__MINGW32__) || defined (__MSVCRT__) | |
| int ret = _mkdir(dir); | |
| (void) mode; | |
| #else | |
| int ret = mkdir(dir, mode); | |
| #endif | |
| if (ret != 0) { | |
| if (errno == EEXIST) | |
| return 0; | |
| UTIL_DISPLAY("zstd: failed to create DIR %s: %s\n", dir, strerror(errno)); | |
| } | |
| return ret; | |
| } | |
| /* this function requires a mutable input string */ | |
| static void convertPathnameToDirName(char *pathname) | |
| { | |
| size_t len = 0; | |
| char* pos = NULL; | |
| /* get dir name from pathname similar to 'dirname()' */ | |
| assert(pathname != NULL); | |
| /* remove trailing '/' chars */ | |
| len = strlen(pathname); | |
| assert(len > 0); | |
| while (pathname[len] == PATH_SEP) { | |
| pathname[len] = '\0'; | |
| len--; | |
| } | |
| if (len == 0) return; | |
| /* if input is a single file, return '.' instead. i.e. | |
| * "xyz/abc/file.txt" => "xyz/abc" | |
| "./file.txt" => "." | |
| "file.txt" => "." | |
| */ | |
| pos = strrchr(pathname, PATH_SEP); | |
| if (pos == NULL) { | |
| pathname[0] = '.'; | |
| pathname[1] = '\0'; | |
| } else { | |
| *pos = '\0'; | |
| } | |
| } | |
| /* pathname must be valid */ | |
| static const char* trimLeadingRootChar(const char *pathname) | |
| { | |
| assert(pathname != NULL); | |
| if (pathname[0] == PATH_SEP) | |
| return pathname + 1; | |
| return pathname; | |
| } | |
| /* pathname must be valid */ | |
| static const char* trimLeadingCurrentDirConst(const char *pathname) | |
| { | |
| assert(pathname != NULL); | |
| if ((pathname[0] == '.') && (pathname[1] == PATH_SEP)) | |
| return pathname + 2; | |
| return pathname; | |
| } | |
| static char* | |
| trimLeadingCurrentDir(char *pathname) | |
| { | |
| /* 'union charunion' can do const-cast without compiler warning */ | |
| union charunion { | |
| char *chr; | |
| const char* cchr; | |
| } ptr; | |
| ptr.cchr = trimLeadingCurrentDirConst(pathname); | |
| return ptr.chr; | |
| } | |
| /* remove leading './' or '/' chars here */ | |
| static const char * trimPath(const char *pathname) | |
| { | |
| return trimLeadingRootChar( | |
| trimLeadingCurrentDirConst(pathname)); | |
| } | |
| static char* mallocAndJoin2Dir(const char *dir1, const char *dir2) | |
| { | |
| assert(dir1 != NULL && dir2 != NULL); | |
| { const size_t dir1Size = strlen(dir1); | |
| const size_t dir2Size = strlen(dir2); | |
| char *outDirBuffer, *buffer; | |
| outDirBuffer = (char *) malloc(dir1Size + dir2Size + 2); | |
| CONTROL(outDirBuffer != NULL); | |
| memcpy(outDirBuffer, dir1, dir1Size); | |
| outDirBuffer[dir1Size] = '\0'; | |
| buffer = outDirBuffer + dir1Size; | |
| if (dir1Size > 0 && *(buffer - 1) != PATH_SEP) { | |
| *buffer = PATH_SEP; | |
| buffer++; | |
| } | |
| memcpy(buffer, dir2, dir2Size); | |
| buffer[dir2Size] = '\0'; | |
| return outDirBuffer; | |
| } | |
| } | |
| /* this function will return NULL if input srcFileName is not valid name for mirrored output path */ | |
| char* UTIL_createMirroredDestDirName(const char* srcFileName, const char* outDirRootName) | |
| { | |
| char* pathname = NULL; | |
| if (!isFileNameValidForMirroredOutput(srcFileName)) | |
| return NULL; | |
| pathname = mallocAndJoin2Dir(outDirRootName, trimPath(srcFileName)); | |
| convertPathnameToDirName(pathname); | |
| return pathname; | |
| } | |
| static int | |
| mirrorSrcDir(char* srcDirName, const char* outDirName) | |
| { | |
| mode_t srcMode; | |
| int status = 0; | |
| char* newDir = mallocAndJoin2Dir(outDirName, trimPath(srcDirName)); | |
| if (!newDir) | |
| return -ENOMEM; | |
| srcMode = getDirMode(srcDirName); | |
| status = makeDir(newDir, srcMode); | |
| free(newDir); | |
| return status; | |
| } | |
| static int | |
| mirrorSrcDirRecursive(char* srcDirName, const char* outDirName) | |
| { | |
| int status = 0; | |
| char* pp = trimLeadingCurrentDir(srcDirName); | |
| char* sp = NULL; | |
| while ((sp = strchr(pp, PATH_SEP)) != NULL) { | |
| if (sp != pp) { | |
| *sp = '\0'; | |
| status = mirrorSrcDir(srcDirName, outDirName); | |
| if (status != 0) | |
| return status; | |
| *sp = PATH_SEP; | |
| } | |
| pp = sp + 1; | |
| } | |
| status = mirrorSrcDir(srcDirName, outDirName); | |
| return status; | |
| } | |
| static void | |
| makeMirroredDestDirsWithSameSrcDirMode(char** srcDirNames, unsigned nbFile, const char* outDirName) | |
| { | |
| unsigned int i = 0; | |
| for (i = 0; i < nbFile; i++) | |
| mirrorSrcDirRecursive(srcDirNames[i], outDirName); | |
| } | |
| static int | |
| firstIsParentOrSameDirOfSecond(const char* firstDir, const char* secondDir) | |
| { | |
| size_t firstDirLen = strlen(firstDir), | |
| secondDirLen = strlen(secondDir); | |
| return firstDirLen <= secondDirLen && | |
| (secondDir[firstDirLen] == PATH_SEP || secondDir[firstDirLen] == '\0') && | |
| 0 == strncmp(firstDir, secondDir, firstDirLen); | |
| } | |
| static int compareDir(const void* pathname1, const void* pathname2) { | |
| /* sort it after remove the leading '/' or './'*/ | |
| const char* s1 = trimPath(*(char * const *) pathname1); | |
| const char* s2 = trimPath(*(char * const *) pathname2); | |
| return strcmp(s1, s2); | |
| } | |
| static void | |
| makeUniqueMirroredDestDirs(char** srcDirNames, unsigned nbFile, const char* outDirName) | |
| { | |
| unsigned int i = 0, uniqueDirNr = 0; | |
| char** uniqueDirNames = NULL; | |
| if (nbFile == 0) | |
| return; | |
| uniqueDirNames = (char** ) malloc(nbFile * sizeof (char *)); | |
| CONTROL(uniqueDirNames != NULL); | |
| /* if dirs is "a/b/c" and "a/b/c/d", we only need call: | |
| * we just need "a/b/c/d" */ | |
| qsort((void *)srcDirNames, nbFile, sizeof(char*), compareDir); | |
| uniqueDirNr = 1; | |
| uniqueDirNames[uniqueDirNr - 1] = srcDirNames[0]; | |
| for (i = 1; i < nbFile; i++) { | |
| char* prevDirName = srcDirNames[i - 1]; | |
| char* currDirName = srcDirNames[i]; | |
| /* note: we always compare trimmed path, i.e.: | |
| * src dir of "./foo" and "/foo" will be both saved into: | |
| * "outDirName/foo/" */ | |
| if (!firstIsParentOrSameDirOfSecond(trimPath(prevDirName), | |
| trimPath(currDirName))) | |
| uniqueDirNr++; | |
| /* we need to maintain original src dir name instead of trimmed | |
| * dir, so we can retrieve the original src dir's mode_t */ | |
| uniqueDirNames[uniqueDirNr - 1] = currDirName; | |
| } | |
| makeMirroredDestDirsWithSameSrcDirMode(uniqueDirNames, uniqueDirNr, outDirName); | |
| free(uniqueDirNames); | |
| } | |
| static void | |
| makeMirroredDestDirs(char** srcFileNames, unsigned nbFile, const char* outDirName) | |
| { | |
| unsigned int i = 0; | |
| for (i = 0; i < nbFile; ++i) | |
| convertPathnameToDirName(srcFileNames[i]); | |
| makeUniqueMirroredDestDirs(srcFileNames, nbFile, outDirName); | |
| } | |
| void UTIL_mirrorSourceFilesDirectories(const char** inFileNames, unsigned int nbFile, const char* outDirName) | |
| { | |
| unsigned int i = 0, validFilenamesNr = 0; | |
| char** srcFileNames = (char **) malloc(nbFile * sizeof (char *)); | |
| CONTROL(srcFileNames != NULL); | |
| /* check input filenames is valid */ | |
| for (i = 0; i < nbFile; ++i) { | |
| if (isFileNameValidForMirroredOutput(inFileNames[i])) { | |
| char* fname = STRDUP(inFileNames[i]); | |
| CONTROL(fname != NULL); | |
| srcFileNames[validFilenamesNr++] = fname; | |
| } | |
| } | |
| if (validFilenamesNr > 0) { | |
| makeDir(outDirName, DIR_DEFAULT_MODE); | |
| makeMirroredDestDirs(srcFileNames, validFilenamesNr, outDirName); | |
| } | |
| for (i = 0; i < validFilenamesNr; i++) | |
| free(srcFileNames[i]); | |
| free(srcFileNames); | |
| } | |
| FileNamesTable* | |
| UTIL_createExpandedFNT(const char* const* inputNames, size_t nbIfns, int followLinks) | |
| { | |
| unsigned nbFiles; | |
| char* buf = (char*)malloc(LIST_SIZE_INCREASE); | |
| char* bufend = buf + LIST_SIZE_INCREASE; | |
| if (!buf) return NULL; | |
| { size_t ifnNb, pos; | |
| for (ifnNb=0, pos=0, nbFiles=0; ifnNb<nbIfns; ifnNb++) { | |
| if (!UTIL_isDirectory(inputNames[ifnNb])) { | |
| size_t const len = strlen(inputNames[ifnNb]); | |
| if (buf + pos + len >= bufend) { | |
| ptrdiff_t newListSize = (bufend - buf) + LIST_SIZE_INCREASE; | |
| assert(newListSize >= 0); | |
| buf = (char*)UTIL_realloc(buf, (size_t)newListSize); | |
| if (!buf) return NULL; | |
| bufend = buf + newListSize; | |
| } | |
| if (buf + pos + len < bufend) { | |
| memcpy(buf+pos, inputNames[ifnNb], len+1); /* including final \0 */ | |
| pos += len + 1; | |
| nbFiles++; | |
| } | |
| } else { | |
| nbFiles += (unsigned)UTIL_prepareFileList(inputNames[ifnNb], &buf, &pos, &bufend, followLinks); | |
| if (buf == NULL) return NULL; | |
| } } } | |
| /* note : even if nbFiles==0, function returns a valid, though empty, FileNamesTable* object */ | |
| { size_t ifnNb, pos; | |
| size_t const fntCapacity = nbFiles + 1; /* minimum 1, allows adding one reference, typically stdin */ | |
| const char** const fileNamesTable = (const char**)malloc(fntCapacity * sizeof(*fileNamesTable)); | |
| if (!fileNamesTable) { free(buf); return NULL; } | |
| for (ifnNb = 0, pos = 0; ifnNb < nbFiles; ifnNb++) { | |
| fileNamesTable[ifnNb] = buf + pos; | |
| if (buf + pos > bufend) { free(buf); free((void*)fileNamesTable); return NULL; } | |
| pos += strlen(fileNamesTable[ifnNb]) + 1; | |
| } | |
| return UTIL_assembleFileNamesTable2(fileNamesTable, nbFiles, fntCapacity, buf); | |
| } | |
| } | |
| void UTIL_expandFNT(FileNamesTable** fnt, int followLinks) | |
| { | |
| FileNamesTable* const newFNT = UTIL_createExpandedFNT((*fnt)->fileNames, (*fnt)->tableSize, followLinks); | |
| CONTROL(newFNT != NULL); | |
| UTIL_freeFileNamesTable(*fnt); | |
| *fnt = newFNT; | |
| } | |
| FileNamesTable* UTIL_createFNT_fromROTable(const char** filenames, size_t nbFilenames) | |
| { | |
| size_t const sizeof_FNTable = nbFilenames * sizeof(*filenames); | |
| const char** const newFNTable = (const char**)malloc(sizeof_FNTable); | |
| if (newFNTable==NULL) return NULL; | |
| memcpy((void*)newFNTable, filenames, sizeof_FNTable); /* void* : mitigate a Visual compiler bug or limitation */ | |
| return UTIL_assembleFileNamesTable(newFNTable, nbFilenames, NULL); | |
| } | |
| /*-**************************************** | |
| * count the number of cores | |
| ******************************************/ | |
| #if defined(_WIN32) || defined(WIN32) | |
| #include <windows.h> | |
| typedef BOOL(WINAPI* LPFN_GLPI)(PSYSTEM_LOGICAL_PROCESSOR_INFORMATION, PDWORD); | |
| DWORD CountSetBits(ULONG_PTR bitMask) | |
| { | |
| DWORD LSHIFT = sizeof(ULONG_PTR)*8 - 1; | |
| DWORD bitSetCount = 0; | |
| ULONG_PTR bitTest = (ULONG_PTR)1 << LSHIFT; | |
| DWORD i; | |
| for (i = 0; i <= LSHIFT; ++i) | |
| { | |
| bitSetCount += ((bitMask & bitTest)?1:0); | |
| bitTest/=2; | |
| } | |
| return bitSetCount; | |
| } | |
| int UTIL_countCores(int logical) | |
| { | |
| static int numCores = 0; | |
| if (numCores != 0) return numCores; | |
| { LPFN_GLPI glpi; | |
| BOOL done = FALSE; | |
| PSYSTEM_LOGICAL_PROCESSOR_INFORMATION buffer = NULL; | |
| PSYSTEM_LOGICAL_PROCESSOR_INFORMATION ptr = NULL; | |
| DWORD returnLength = 0; | |
| size_t byteOffset = 0; | |
| #if defined(_MSC_VER) | |
| /* Visual Studio does not like the following cast */ | |
| # pragma warning( disable : 4054 ) /* conversion from function ptr to data ptr */ | |
| # pragma warning( disable : 4055 ) /* conversion from data ptr to function ptr */ | |
| #endif | |
| glpi = (LPFN_GLPI)(void*)GetProcAddress(GetModuleHandle(TEXT("kernel32")), | |
| "GetLogicalProcessorInformation"); | |
| if (glpi == NULL) { | |
| goto failed; | |
| } | |
| while(!done) { | |
| DWORD rc = glpi(buffer, &returnLength); | |
| if (FALSE == rc) { | |
| if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { | |
| if (buffer) | |
| free(buffer); | |
| buffer = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION)malloc(returnLength); | |
| if (buffer == NULL) { | |
| perror("zstd"); | |
| exit(1); | |
| } | |
| } else { | |
| /* some other error */ | |
| goto failed; | |
| } | |
| } else { | |
| done = TRUE; | |
| } } | |
| ptr = buffer; | |
| while (byteOffset + sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION) <= returnLength) { | |
| if (ptr->Relationship == RelationProcessorCore) { | |
| if (logical) | |
| numCores += CountSetBits(ptr->ProcessorMask); | |
| else | |
| numCores++; | |
| } | |
| ptr++; | |
| byteOffset += sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION); | |
| } | |
| free(buffer); | |
| return numCores; | |
| } | |
| failed: | |
| /* try to fall back on GetSystemInfo */ | |
| { SYSTEM_INFO sysinfo; | |
| GetSystemInfo(&sysinfo); | |
| numCores = sysinfo.dwNumberOfProcessors; | |
| if (numCores == 0) numCores = 1; /* just in case */ | |
| } | |
| return numCores; | |
| } | |
| #elif defined(__APPLE__) | |
| #include <sys/sysctl.h> | |
| /* Use apple-provided syscall | |
| * see: man 3 sysctl */ | |
| int UTIL_countCores(int logical) | |
| { | |
| static S32 numCores = 0; /* apple specifies int32_t */ | |
| if (numCores != 0) return numCores; | |
| { size_t size = sizeof(S32); | |
| int const ret = sysctlbyname(logical ? "hw.logicalcpu" : "hw.physicalcpu", &numCores, &size, NULL, 0); | |
| if (ret != 0) { | |
| if (errno == ENOENT) { | |
| /* entry not present, fall back on 1 */ | |
| numCores = 1; | |
| } else { | |
| perror("zstd: can't get number of cpus"); | |
| exit(1); | |
| } | |
| } | |
| return numCores; | |
| } | |
| } | |
| #elif defined(__linux__) | |
| /* parse /proc/cpuinfo | |
| * siblings / cpu cores should give hyperthreading ratio | |
| * otherwise fall back on sysconf */ | |
| int UTIL_countCores(int logical) | |
| { | |
| static int numCores = 0; | |
| if (numCores != 0) return numCores; | |
| numCores = (int)sysconf(_SC_NPROCESSORS_ONLN); | |
| if (numCores == -1) { | |
| /* value not queryable, fall back on 1 */ | |
| return numCores = 1; | |
| } | |
| /* try to determine if there's hyperthreading */ | |
| { FILE* const cpuinfo = fopen("/proc/cpuinfo", "r"); | |
| #define BUF_SIZE 80 | |
| char buff[BUF_SIZE]; | |
| int siblings = 0; | |
| int cpu_cores = 0; | |
| int ratio = 1; | |
| if (cpuinfo == NULL) { | |
| /* fall back on the sysconf value */ | |
| return numCores; | |
| } | |
| /* assume the cpu cores/siblings values will be constant across all | |
| * present processors */ | |
| while (!feof(cpuinfo)) { | |
| if (fgets(buff, BUF_SIZE, cpuinfo) != NULL) { | |
| if (strncmp(buff, "siblings", 8) == 0) { | |
| const char* const sep = strchr(buff, ':'); | |
| if (sep == NULL || *sep == '\0') { | |
| /* formatting was broken? */ | |
| goto failed; | |
| } | |
| siblings = atoi(sep + 1); | |
| } | |
| if (strncmp(buff, "cpu cores", 9) == 0) { | |
| const char* const sep = strchr(buff, ':'); | |
| if (sep == NULL || *sep == '\0') { | |
| /* formatting was broken? */ | |
| goto failed; | |
| } | |
| cpu_cores = atoi(sep + 1); | |
| } | |
| } else if (ferror(cpuinfo)) { | |
| /* fall back on the sysconf value */ | |
| goto failed; | |
| } } | |
| if (siblings && cpu_cores && siblings > cpu_cores) { | |
| ratio = siblings / cpu_cores; | |
| } | |
| if (ratio && numCores > ratio && !logical) { | |
| numCores = numCores / ratio; | |
| } | |
| failed: | |
| fclose(cpuinfo); | |
| return numCores; | |
| } | |
| } | |
| #elif defined(__FreeBSD__) | |
| #include <sys/sysctl.h> | |
| /* Use physical core sysctl when available | |
| * see: man 4 smp, man 3 sysctl */ | |
| int UTIL_countCores(int logical) | |
| { | |
| static int numCores = 0; /* freebsd sysctl is native int sized */ | |
| #if __FreeBSD_version >= 1300008 | |
| static int perCore = 1; | |
| #endif | |
| if (numCores != 0) return numCores; | |
| #if __FreeBSD_version >= 1300008 | |
| { size_t size = sizeof(numCores); | |
| int ret = sysctlbyname("kern.smp.cores", &numCores, &size, NULL, 0); | |
| if (ret == 0) { | |
| if (logical) { | |
| ret = sysctlbyname("kern.smp.threads_per_core", &perCore, &size, NULL, 0); | |
| /* default to physical cores if logical cannot be read */ | |
| if (ret == 0) | |
| numCores *= perCore; | |
| } | |
| return numCores; | |
| } | |
| if (errno != ENOENT) { | |
| perror("zstd: can't get number of cpus"); | |
| exit(1); | |
| } | |
| /* sysctl not present, fall through to older sysconf method */ | |
| } | |
| #else | |
| /* suppress unused parameter warning */ | |
| (void) logical; | |
| #endif | |
| numCores = (int)sysconf(_SC_NPROCESSORS_ONLN); | |
| if (numCores == -1) { | |
| /* value not queryable, fall back on 1 */ | |
| numCores = 1; | |
| } | |
| return numCores; | |
| } | |
| #elif defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) || defined(__CYGWIN__) | |
| /* Use POSIX sysconf | |
| * see: man 3 sysconf */ | |
| int UTIL_countCores(int logical) | |
| { | |
| static int numCores = 0; | |
| /* suppress unused parameter warning */ | |
| (void)logical; | |
| if (numCores != 0) return numCores; | |
| numCores = (int)sysconf(_SC_NPROCESSORS_ONLN); | |
| if (numCores == -1) { | |
| /* value not queryable, fall back on 1 */ | |
| return numCores = 1; | |
| } | |
| return numCores; | |
| } | |
| #else | |
| int UTIL_countCores(int logical) | |
| { | |
| /* suppress unused parameter warning */ | |
| (void)logical; | |
| /* assume 1 */ | |
| return 1; | |
| } | |
| #endif | |
| int UTIL_countPhysicalCores(void) | |
| { | |
| return UTIL_countCores(0); | |
| } | |
| int UTIL_countLogicalCores(void) | |
| { | |
| return UTIL_countCores(1); | |
| } |