/* * BRLTTY - A background process providing access to the console screen (when in * text mode) for a blind person using a refreshable braille display. * * Copyright (C) 1995-2021 by The BRLTTY Developers. * * BRLTTY comes with ABSOLUTELY NO WARRANTY. * * This is free software, placed under the terms of the * GNU Lesser General Public License, as published by the Free Software * Foundation; either version 2.1 of the License, or (at your option) any * later version. Please see the file LICENSE-LGPL for details. * * Web Page: http://brltty.app/ * * This software is maintained by Dave Mielke . */ #include "prologue.h" #include #include #include #include #include #include #include #include #include #ifdef HAVE_LANGINFO_H #include #endif /* HAVE_LANGINFO_H */ #ifdef HAVE_ICONV_H #include #endif /* HAVE_ICONV_H */ #ifdef HAVE_SYS_FILE_H #include #endif /* HAVE_SYS_FILE_H */ #include "parameters.h" #include "log.h" #include "strfmt.h" #include "file.h" #include "parse.h" #include "async_wait.h" #include "utf8.h" #include "program.h" int isPathSeparator (const char character) { return character == PATH_SEPARATOR_CHARACTER; } int isAbsolutePath (const char *path) { if (isPathSeparator(path[0])) return 1; #if defined(__MINGW32__) || defined(__MSDOS__) if (strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZ", toupper(path[0])) && (path[1] == ':')) return 1; #endif /* defined(__MINGW32__) || defined(__MSDOS__) */ return 0; } static size_t stripPathDelimiter (const char *path, size_t length) { while (length) { if (!isPathSeparator(path[length-1])) break; length -= 1; } return length; } char * getPathDirectory (const char *path) { size_t length = strlen(path); size_t end = stripPathDelimiter(path, length); if (end) { while (--end) { if (isPathSeparator(path[end-1])) { break; } } if ((length = end)) { if ((end = stripPathDelimiter(path, length))) { length = end; } } } if (!length) length = strlen((path = CURRENT_DIRECTORY_NAME)); { char *directory = malloc(length + 1); if (directory) { memcpy(directory, path, length); directory[length] = 0; } else { logMallocError(); } return directory; } } const char * locatePathName (const char *path) { const char *name = path + strlen(path); while (name != path) { if (isPathSeparator(*--name)) { ++name; break; } } return name; } const char * locatePathExtension (const char *path) { const char *name = locatePathName(path); const char *extension = strrchr(name, '.'); if (extension && extension[1]) { const char *c = extension; while (c > name) { if (*--c != '.') { return extension; } } } return NULL; } int isExplicitPath (const char *path) { return locatePathName(path) != path; } char * joinPath (const char *const *components, unsigned int count) { const char *const *component = components + count; unsigned int size = (count * 2) - 1; const char *strings[size]; unsigned int first = size; while (component != components) { const char *next = *--component; if (next && *next) { if ((first != size) && !isPathSeparator(next[strlen(next)-1])) { strings[--first] = "/"; } strings[--first] = next; if (isAbsolutePath(next)) break; } } return joinStrings(&strings[first], size-first); } char * makePath (const char *directory, const char *file) { const char *const components[] = {directory, file}; return joinPath(components, ARRAY_COUNT(components)); } int hasFileExtension (const char *path, const char *extension) { const char *tail = locatePathExtension(path); if (!tail) return 0; return strcmp(tail, extension) == 0; } char * replaceFileExtension (const char *path, const char *extension) { const char *oldExtension = locatePathExtension(path); if (oldExtension) { size_t headLength = oldExtension - path; size_t extensionLength = strlen(extension); char *newPath = malloc(headLength + extensionLength + 1); if (newPath) { char *byte = newPath; byte = mempcpy(byte, path, headLength); byte = mempcpy(byte, extension, extensionLength); *byte = 0; return newPath; } else { logMallocError(); } } else { logMessage(LOG_WARNING, "path has no extension: %s", path); } return NULL; } char * ensureFileExtension (const char *path, const char *extension) { const char *strings[2]; int count = 0; strings[count++] = path; if (extension && !locatePathExtension(path)) strings[count++] = extension; return joinStrings(strings, count); } char * makeFilePath (const char *directory, const char *name, const char *extension) { char *path = NULL; char *file = ensureFileExtension(name, extension); if (file) { if (isExplicitPath(file)) return file; path = makePath(directory, file); free(file); } return path; } int testPath (const char *path) { #ifdef F_OK return access(path, F_OK) != -1; #else /* F_OK */ errno = ENOSYS; return 0; #endif /* F_OK */ } int testFilePath (const char *path) { #ifdef S_ISREG struct stat status; if (stat(path, &status) != -1) { if (S_ISREG(status.st_mode)) return 1; errno = EEXIST; } #else /* S_ISREG */ int result = open(path, O_RDONLY); if (result != -1) { close(result); return 1; } #endif /* S_ISREG */ return 0; } int testProgramPath (const char *path) { if (!testFilePath(path)) return 0; #if defined(__MINGW32__) { const char *extension = locatePathExtension(path); if (extension) { static char **extensions = NULL; if (!extensions) { const char *string = getenv("PATHEXT"); { static char *noExtensions[] = {NULL}; extensions = noExtensions; } if (string) { char **strings = splitString(string, ';', NULL); if (strings) extensions = strings; } } { char **x = extensions; while (*x) { if (strcasecmp(*x, extension) == 0) return 1; x += 1; } } } } return 0; #elif defined(X_OK) return access(path, X_OK) != -1; #else /* X_OK */ errno = ENOSYS; return 0; #endif /* X_OK */ } int testDirectoryPath (const char *path) { #ifdef S_ISDIR struct stat status; if (stat(path, &status) != -1) { if (S_ISDIR(status.st_mode)) return 1; errno = EEXIST; } #else /* S_ISDIR */ errno = ENOSYS; #endif /* S_ISDIR */ return 0; } int createDirectory (const char *path, int worldWritable) { #if defined(GRUB_RUNTIME) errno = EROFS; #else /* make directory */ #ifdef __MINGW32__ if (mkdir(path) != -1) return 1; #else /* __MINGW32__ */ if (mkdir(path, (S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)) != -1) { if (!worldWritable) return 1; if (chmod(path, (S_IRWXU | S_IRWXG | S_IRWXO | S_ISVTX)) != -1) return 1; logMessage(LOG_WARNING, "%s: %s: %s", gettext("cannot make world writable"), path, strerror(errno) ); return 0; } #endif /* __MINGW32__ */ #endif /* make directory */ logMessage(LOG_WARNING, "%s: %s: %s", gettext("cannot create directory"), path, strerror(errno) ); return 0; } int ensureDirectory (const char *path, int worldWritable) { if (testDirectoryPath(path)) return 1; if (errno == EEXIST) { logMessage(LOG_ERR, "not a directory: %s", path); } else if (errno != ENOENT) { logMessage(LOG_ERR, "cannot access directory: %s: %s", path, strerror(errno)); } else { { char *directory = getPathDirectory(path); if (!directory) return 0; { int exists = ensureDirectory(directory, 0); free(directory); if (!exists) return 0; } } if (createDirectory(path, worldWritable)) { logMessage(LOG_NOTICE, "directory created: %s", path); return 1; } } return 0; } int ensurePathDirectory (const char *path) { char *directory = getPathDirectory(path); if (!directory) return 0; { int exists = ensureDirectory(directory, 0); free(directory); return exists; } } static void setDirectory (const char **variable, const char *directory) { *variable = directory; } static const char * getDirectory (const char *const *variable) { if (*variable && **variable) { if (ensureDirectory(*variable, 0)) { return *variable; } } return NULL; } static char * makeDirectoryPath (const char *const *variable, const char *file) { const char *directory = getDirectory(variable); if (directory) return makePath(directory, file); return NULL; } static const char *updatableDirectory = NULL; void setUpdatableDirectory (const char *directory) { setDirectory(&updatableDirectory, directory); } const char * getUpdatableDirectory (void) { return getDirectory(&updatableDirectory); } char * makeUpdatablePath (const char *file) { return makeDirectoryPath(&updatableDirectory, file); } static const char *writableDirectory = NULL; void setWritableDirectory (const char *directory) { setDirectory(&writableDirectory, directory); } const char * getWritableDirectory (void) { return getDirectory(&writableDirectory); } char * makeWritablePath (const char *file) { return makeDirectoryPath(&writableDirectory, file); } char * getWorkingDirectory (void) { #if defined(GRUB_RUNTIME) errno = ENOSYS; #else /* get working directory */ size_t size = 0X80; char *buffer = NULL; while (1) { { char *newBuffer = realloc(buffer, size<<=1); if (!newBuffer) { logMallocError(); break; } buffer = newBuffer; } if (getcwd(buffer, size)) return buffer; if (errno != ERANGE) { logSystemError("getcwd"); break; } } if (buffer) free(buffer); #endif /* get working directory */ logMessage(LOG_WARNING, "%s: %s", gettext("cannot get working directory"), strerror(errno)); return NULL; } int setWorkingDirectory (const char *path) { #if defined(GRUB_RUNTIME) errno = ENOSYS; #else /* set working directory */ if (chdir(path) != -1) return 1; #endif /* set working directory */ logMessage(LOG_WARNING, "%s: %s: %s", gettext("cannot set working directory"), path, strerror(errno)); return 0; } char * getHomeDirectory (void) { #if defined(GRUB_RUNTIME) #else /* get home directory */ char *path = getenv("HOME"); if (path && *path) { if ((path = strdup(path))) return path; logMallocError(); } #endif /* get home directory */ return NULL; } static char * makeOverridePath (const char *base, int xdg) { return makePath(base, (xdg? PACKAGE_TARNAME: (CURRENT_DIRECTORY_NAME PACKAGE_TARNAME))); } static int addOverridePath (char **paths, size_t *index, const char *base, int xdg) { char *path = makeOverridePath(base, xdg); if (!path) return 0; logMessage(LOG_DEBUG, "override directory: %s", path); paths[(*index)++] = path; return 1; } static char **overrideDirectories = NULL; const char *const * getAllOverrideDirectories (void) { if (!overrideDirectories) { logMessage(LOG_DEBUG, "determining override directories"); const char *secondaryList = getenv("XDG_CONFIG_DIRS"); int secondaryCount; char **secondaryBases = splitString(((secondaryList && *secondaryList)? secondaryList: "/etc/xdg"), ':', &secondaryCount); if (secondaryBases) { size_t count = 1 + secondaryCount + 1; char **paths; if ((paths = malloc(sizeof(*paths) * (count + 1)))) { size_t index = 0; { const char *primary = getenv("XDG_CONFIG_HOME"); if (primary && *primary) { if (!addOverridePath(paths, &index, primary, 1)) goto done; } else { char *home = getHomeDirectory(); if (home) { char *base = *home? makePath(home, ".config"): NULL; free(home); home = NULL; if (base) { int added = addOverridePath(paths, &index, base, 1); free(base); base = NULL; if (!added) goto done; } } } } if (!index) { char *primary = strdup(""); if (!primary) { logMallocError(); goto done; } paths[index++] = primary; } { char **base = secondaryBases; while (*base) { if (**base) { if (!addOverridePath(paths, &index, *base, 1)) { break; } } else { count -= 1; } base += 1; } if (*base) goto done; } { int added = 0; char *home = getHomeDirectory(); if (home && *home) { if (addOverridePath(paths, &index, home, 0)) added = 1; } else { char *current = getWorkingDirectory(); if (current) { if (addOverridePath(paths, &index, current, 0)) added = 1; free(current); } } if (home) free(home); if (!added) goto done; } done: paths[index] = NULL; if (index == count) { overrideDirectories = paths; } else { deallocateStrings(paths); } } else { logMallocError(); } deallocateStrings(secondaryBases); } if (!overrideDirectories) logMessage(LOG_WARNING, "no override directories"); } return (const char *const *)overrideDirectories; } const char * getPrimaryOverrideDirectory (void) { const char *const *directories = getAllOverrideDirectories(); if (directories) { const char *directory = directories[0]; if (directory && *directory) return directory; } logMessage(LOG_WARNING, "no primary override directory"); return NULL; } void forgetOverrideDirectories (void) { if (overrideDirectories) { logMessage(LOG_DEBUG, "forgetting override directories"); deallocateStrings(overrideDirectories); overrideDirectories = NULL; } } #if defined(F_SETLK) static int modifyFileLock (int file, int action, short type) { struct flock lock; memset(&lock, 0, sizeof(lock)); lock.l_type = type; lock.l_whence = SEEK_SET; lock.l_start = 0; lock.l_len = 0; do { if (fcntl(file, action, &lock) != -1) return 1; } while (errno == EINTR); if (errno == EACCES) errno = EAGAIN; if (errno != EAGAIN) logSystemError("fcntl[struct flock *]"); return 0; } static int lockFile (int file, int exclusive, int wait) { return modifyFileLock(file, (wait? F_SETLKW: F_SETLK), (exclusive? F_WRLCK: F_RDLCK)); } int acquireFileLock (int file, int exclusive) { return lockFile(file, exclusive, 1); } int attemptFileLock (int file, int exclusive) { return lockFile(file, exclusive, 0); } int releaseFileLock (int file) { return modifyFileLock(file, F_SETLK, F_UNLCK); } #elif defined(LOCK_EX) static int modifyFileLock (int file, int operation) { do { if (flock(file, operation) != -1) return 1; } while (errno == EINTR); #ifdef EWOULDBLOCK if (errno == EWOULDBLOCK) errno = EAGAIN; #endif /* EWOULDBLOCK */ if (errno == EACCES) errno = EAGAIN; if (errno != EAGAIN) logSystemError("flock"); return 0; } int acquireFileLock (int file, int exclusive) { return modifyFileLock(file, (exclusive? LOCK_EX: LOCK_SH)); } int attemptFileLock (int file, int exclusive) { return modifyFileLock(file, ((exclusive? LOCK_EX: LOCK_SH) | LOCK_NB)); } int releaseFileLock (int file) { return modifyFileLock(file, LOCK_UN); } #elif defined(F_LOCK) static int modifyRegionLock (int file, int command, off_t length) { do { if (lockf(file, command, length) != -1) return 1; } while (errno == EINTR); if (errno == EACCES) errno = EAGAIN; if (errno != EAGAIN) logSystemError("lockf"); return 0; } static int modifyFileLock (int file, int command) { off_t offset; if ((offset = lseek(file, 0, SEEK_CUR)) == -1) { logSystemError("lseek"); } else if (modifyRegionLock(file, command, 0)) { if (!offset) return 1; if (modifyRegionLock(file, command, -offset)) return 1; } return 0; } int acquireFileLock (int file, int exclusive) { return modifyFileLock(file, F_LOCK); } int attemptFileLock (int file, int exclusive) { return modifyFileLock(file, F_TLOCK); } int releaseFileLock (int file) { return modifyFileLock(file, F_ULOCK); } #elif defined(__MINGW32__) #include #include #include static int modifyFileLock (int file, int mode) { int ok = 0; off_t offset; if ((offset = lseek(file, 0, SEEK_CUR)) != -1) { if (lseek(file, 0, SEEK_SET) != -1) { int wait; if (mode == _LK_LOCK) { mode = _LK_NBLCK; wait = 1; } else if (mode == _LK_RLCK) { mode = _LK_NBRLCK; wait = 1; } else { wait = 0; } while (1) { if (_locking(file, mode, LONG_MAX) != -1) { ok = 1; break; } if (errno != EACCES) { logSystemError("_locking"); break; } if (!wait) break; asyncWait(WINDOWS_FILE_LOCK_RETRY_INTERVAL); } if (lseek(file, offset, SEEK_SET) == -1) { logSystemError("lseek"); ok = 0; } } else { logSystemError("lseek"); } } else { logSystemError("lseek"); } return ok; } int acquireFileLock (int file, int exclusive) { return modifyFileLock(file, (exclusive? _LK_LOCK: _LK_RLCK)); } int attemptFileLock (int file, int exclusive) { return modifyFileLock(file, (exclusive? _LK_NBLCK: _LK_NBRLCK)); } int releaseFileLock (int file) { return modifyFileLock(file, _LK_UNLCK); } #else /* file locking */ #warning file lock support not available on this platform int acquireFileLock (int file, int exclusive) { logUnsupportedFunction(); return 0; } int attemptFileLock (int file, int exclusive) { logUnsupportedFunction(); return 0; } int releaseFileLock (int file) { logUnsupportedFunction(); return 0; } #endif /* file locking */ static void exitProgramStream (void *data) { FILE **stream = data; if (*stream) { fclose(*stream); *stream = NULL; } } void registerProgramStream (const char *name, FILE **stream) { onProgramExit(name, exitProgramStream, stream); } FILE * openFile (const char *path, const char *mode, int optional) { FILE *file = fopen(path, mode); if (file) { logMessage(LOG_DEBUG, "file opened: %s fd=%d", path, fileno(file)); } else { logMessage((optional && (errno == ENOENT))? LOG_DEBUG: LOG_ERR, "cannot open file: %s: %s", path, strerror(errno)); } return file; } int readLine (FILE *file, char **buffer, size_t *size, size_t *length) { char *line; if (ferror(file)) return 0; if (feof(file)) return 0; if (!*size) { if (!(*buffer = malloc((*size = 0X80)))) { logMallocError(); return 0; } } if ((line = fgets(*buffer, *size, file))) { size_t count = strlen(line); /* Line length including new-line. */ /* No trailing new-line means that the buffer isn't big enough. */ while (line[count-1] != '\n') { /* If necessary, extend the buffer. */ if ((*size - (count + 1)) == 0) { size_t newSize = *size << 1; char *newBuffer = realloc(*buffer, newSize); if (!newBuffer) { logMallocError(); return 0; } *buffer = newBuffer; *size = newSize; } /* Read the rest of the line into the end of the buffer. */ if (!(line = fgets(&(*buffer)[count], (*size -count), file))) { if (!ferror(file)) goto done; logSystemError("fgets"); return 0; } count += strlen(line); /* New total line length. */ line = *buffer; /* Point to the beginning of the line. */ } if (--count > 0) { if (line[count-1] == '\r') { count -= 1; } } line[count] = 0; /* Remove trailing new-line. */ done: if (length) *length = count; return 1; } else if (ferror(file)) { logSystemError("fgets"); } return 0; } /* Process each line of an input text file safely. * This routine handles the actual reading of the file, * insuring that the input buffer is always big enough, * and calls a caller-supplied handler once for each line in the file. * The caller-supplied data pointer is passed straight through to the handler. */ int processLines (FILE *file, LineHandler handleLine, void *data) { char *buffer = NULL; size_t bufferSize = 0; LineHandlerParameters parameters = { .data = data, .line = { .number = 0, }, }; while (1) { parameters.line.number += 1; if (!readLine(file, &buffer, &bufferSize, ¶meters.line.length)) break; parameters.line.text = buffer; if (!handleLine(¶meters)) break; } if (buffer) free(buffer); return !ferror(file); } STR_BEGIN_FORMATTER(formatInputError, const char *file, const int *line, const char *format, va_list arguments) if (file) STR_PRINTF("%s", file); if (line) STR_PRINTF("[%d]", *line); if (STR_LENGTH) STR_PRINTF(": "); STR_VPRINTF(format, arguments); STR_END_FORMATTER #ifdef __MINGW32__ const char * getConsoleEncoding (void) { static char encoding[0X10]; if (!encoding[0]) { unsigned cp = GetConsoleOutputCP(); if (cp == CP_UTF8) { strcpy(encoding, "UTF-8"); } else { snprintf(encoding, sizeof(encoding), "CP%u", cp); } logMessage(LOG_DEBUG, "Console Encoding: %s", encoding); } return encoding; } ssize_t readFileDescriptor (FileDescriptor fileDescriptor, void *buffer, size_t size) { { DWORD count; if (ReadFile(fileDescriptor, buffer, size, &count, NULL)) return count; } setSystemErrno(); return -1; } ssize_t writeFileDescriptor (FileDescriptor fileDescriptor, const void *buffer, size_t size) { { DWORD count; if (WriteFile(fileDescriptor, buffer, size, &count, NULL)) return count; } setSystemErrno(); return -1; } const char * getNamedPipeDirectory (void) { return "//./pipe"; } int createAnonymousPipe (FileDescriptor *pipeInput, FileDescriptor *pipeOutput) { SECURITY_ATTRIBUTES attributes; ZeroMemory(&attributes, sizeof(attributes)); attributes.nLength = sizeof(attributes); attributes.bInheritHandle = TRUE; attributes.lpSecurityDescriptor = NULL; if (CreatePipe(pipeOutput, pipeInput, &attributes, 0)) { return 1; } else { logWindowsSystemError("CreatePipe"); } return 0; } #else /* unix file/socket descriptor operations */ const char * getConsoleEncoding (void) { static const char *encoding = NULL; if (!encoding) { setlocale(LC_ALL, ""); #ifdef HAVE_NL_LANGINFO encoding = nl_langinfo(CODESET); #endif /* HAVE_NL_LANGINFO */ if (encoding) { if (!(encoding = strdup(encoding))) { logMallocError(); } } if (!encoding) encoding = ""; logMessage(LOG_DEBUG, "Console Encoding: %s", encoding); } return encoding; } ssize_t readFileDescriptor (FileDescriptor fileDescriptor, void *buffer, size_t size) { return read(fileDescriptor, buffer, size); } ssize_t writeFileDescriptor (FileDescriptor fileDescriptor, const void *buffer, size_t size) { return write(fileDescriptor, buffer, size); } const char * getNamedPipeDirectory (void) { return getWritableDirectory(); } int createAnonymousPipe (FileDescriptor *pipeInput, FileDescriptor *pipeOutput) { int fileDescriptors[2]; if (pipe(fileDescriptors) != -1) { *pipeInput = fileDescriptors[1]; *pipeOutput = fileDescriptors[0]; return 1; } else { logSystemError("pipe"); } return 0; } #endif /* basic file/socket descriptor operations */ void writeWithConsoleEncoding (FILE *stream, const char *bytes, size_t count) { const char *consoleEncoding = getConsoleEncoding(); if (!consoleEncoding || isCharsetUTF8(consoleEncoding)) { consoleEncoding = ""; } if (*consoleEncoding) { #ifdef HAVE_ICONV_H static const char internalEncoding[] = "UTF-8"; static iconv_t iconvHandle = (iconv_t)-1; if (iconvHandle == (iconv_t)-1) { if ((iconvHandle = iconv_open(consoleEncoding, internalEncoding)) == (iconv_t)-1) { logMessage(LOG_WARNING, "iconv open error: %s -> %s: %s", internalEncoding, consoleEncoding, strerror(errno) ); consoleEncoding = ""; goto noTranslation; } } const char *inputNext = bytes; size_t inputLeft = count; char outputBuffer[inputLeft * MB_LEN_MAX]; char *outputNext = outputBuffer; size_t outputLeft = sizeof(outputBuffer); ssize_t result = iconv( iconvHandle, (char **)&inputNext, &inputLeft, &outputNext, &outputLeft ); if (result != -1) { size_t length = outputNext - outputBuffer; outputBuffer[length] = 0; fputs(outputBuffer, stream); } return; #endif /* HAVE_ICONV_H */ } noTranslation: fwrite(bytes, 1, count, stream); } #ifdef GOT_SOCKETS ssize_t readSocketDescriptor (SocketDescriptor socketDescriptor, void *buffer, size_t size) { return recv(socketDescriptor, buffer, size, 0); } ssize_t writeSocketDescriptor (SocketDescriptor socketDescriptor, const void *buffer, size_t size) { return send(socketDescriptor, buffer, size, 0); } #endif /* GOT_SOCKETS */