/* SPDX-FileCopyrightText: 2008-2017 jerome DOT laurens AT u-bourgogne DOT fr SPDX-License-Identifier: X11 This file is part of the __SyncTeX__ package. [//]: # (Latest Revision: Fri Jul 14 16:20:41 UTC 2017) [//]: # (Version: 1.19) See `synctex_parser_readme.md` for more details */ /* In this file, we find all the functions that may depend on the operating system. */ #include #include #include #include #include #include #include #include #include #if defined(_WIN32) || defined(__WIN32__) || defined(__TOS_WIN__) || defined(__WINDOWS__) #define SYNCTEX_WINDOWS 1 #endif #if defined(__OS2__) #define SYNCTEX_OS2 1 #endif #if defined(_WIN32) #define SYNCTEX_RECENT_WINDOWS 1 #endif #ifdef SYNCTEX_WINDOWS #include /* Use shlwapi.lib */ #include #endif void *_synctex_malloc(size_t size) { void *ptr = malloc(size); if (ptr) { memset(ptr, 0, size); /* ensures null termination of strings */ } return (void *)ptr; } void _synctex_free(void *ptr) { if (ptr) { free(ptr); } } #if !defined(_WIN32) #include #endif int _synctex_log(int level, const char *prompt, const char *reason, ...) SYNCTEX_PRINTF_FORMAT(3, 4); int _synctex_log(int level, const char *prompt, const char *reason, ...) { va_list arg; int result; va_start(arg, reason); #ifdef SYNCTEX_RECENT_WINDOWS { /* This code is contributed by William Blum. As it does not work on some older computers, the _WIN32 conditional here is replaced with a SYNCTEX_RECENT_WINDOWS one. According to http://msdn.microsoft.com/en-us/library/aa363362(VS.85).aspx Minimum supported client Windows 2000 Professional Minimum supported server Windows 2000 Server People running Windows 2K standard edition will not have OutputDebugStringA. JL.*/ char *buff; size_t len; OutputDebugStringA(prompt); #ifdef _MSC_VER len = _vscprintf(reason, arg) + 1; buff = (char *)malloc(len * sizeof(char)); #else /* MinGW */ size_t buffersize = 1024; size_t max_buffersize = 1024 * buffersize; int result; buff = (char *)malloc(buffersize * sizeof(char)); result = _vsnprintf(buff, buffersize - 1, reason, arg); while (-1 == result && buffersize <= max_buffersize) { buffersize = buffersize * 2; buff = (char *)realloc(buff, buffersize * sizeof(char)); result = _vsnprintf(buff, buffersize - 1, reason, arg); } if (-1 == result) { va_end(arg); // could not make the buffer big enough or simply could not write to it free(buff); return -1; } #endif result = vsprintf(buff, reason, arg) + strlen(prompt); OutputDebugStringA(buff); OutputDebugStringA("\n"); free(buff); } #elif defined SYNCTEX_USE_SYSLOG char *buffer1 = NULL; char *buffer2 = NULL; openlog("SyncTeX", LOG_CONS | LOG_PID | LOG_PERROR | LOG_NDELAY, LOG_LOCAL0); if (vasprintf(&buffer1, reason, arg) >= 0 && asprintf(&buffer2, "%s%s", prompt, buffer1) >= 0) { syslog(level, "%s", buffer2); result = (int)strlen(buffer2); } else { syslog(level, "%s", prompt); vsyslog(level, reason, arg); result = (int)strlen(prompt); } free(buffer1); free(buffer2); closelog(); #else FILE *where = level == LOG_ERR ? stderr : stdout; result = fputs(prompt, where); result += vfprintf(where, reason, arg); result += fprintf(where, "\n"); #endif va_end(arg); return result; } int _synctex_error(const char *reason, ...) { va_list arg; int result; va_start(arg, reason); #if defined(SYNCTEX_RECENT_WINDOWS) /* LOG_ERR is not used */ result = _synctex_log(0, "! SyncTeX Error : ", reason, arg); #else result = _synctex_log(LOG_ERR, "! SyncTeX Error : ", reason, arg); #endif va_end(arg); return result; } int _synctex_debug(const char *reason, ...) { va_list arg; int result; va_start(arg, reason); #if defined(SYNCTEX_RECENT_WINDOWS) /* LOG_DEBUG is not used */ result = _synctex_log(0, "! SyncTeX Error : ", reason, arg); #else result = _synctex_log(LOG_DEBUG, "! SyncTeX Error : ", reason, arg); #endif va_end(arg); return result; } /* strip the last extension of the given string, this string is modified! */ void _synctex_strip_last_path_extension(char *string) { if (NULL != string) { char *last_component = NULL; char *last_extension = NULL; #if defined(SYNCTEX_WINDOWS) last_component = PathFindFileNameA(string); last_extension = PathFindExtensionA(string); if (last_extension == NULL) return; if (last_component == NULL) last_component = string; if (last_extension > last_component) { /* filter out paths like "my/dir/.hidden" */ last_extension[0] = '\0'; } #else char *next = NULL; /* first we find the last path component */ if (NULL == (last_component = strstr(string, "/"))) { last_component = string; } else { ++last_component; while ((next = strstr(last_component, "/"))) { last_component = next + 1; } } #if defined(SYNCTEX_OS2) /* On OS2, the '\' is also a path separator. */ while ((next = strstr(last_component, "\\"))) { last_component = next + 1; } #endif /* SYNCTEX_OS2 */ /* then we find the last path extension */ if ((last_extension = strstr(last_component, "."))) { ++last_extension; while ((next = strstr(last_extension, "."))) { last_extension = next + 1; } --last_extension; /* back to the "." */ if (last_extension > last_component) { /* filter out paths like ....my/dir/.hidden"*/ last_extension[0] = '\0'; } } #endif /* SYNCTEX_WINDOWS */ } } synctex_bool_t synctex_ignore_leading_dot_slash_in_path(const char **name_ref) { if (SYNCTEX_IS_DOT((*name_ref)[0]) && SYNCTEX_IS_PATH_SEPARATOR((*name_ref)[1])) { do { (*name_ref) += 2; while (SYNCTEX_IS_PATH_SEPARATOR((*name_ref)[0])) { ++(*name_ref); } } while (SYNCTEX_IS_DOT((*name_ref)[0]) && SYNCTEX_IS_PATH_SEPARATOR((*name_ref)[1])); return synctex_YES; } return synctex_NO; } /* The base name is necessary to deal with the 2011 file naming convention... * path is a '\0' terminated string * The return value is the trailing part of the argument, * just following the first occurrence of the regexp pattern "[^|/|\].[\|/]+".*/ const char *_synctex_base_name(const char *path) { const char *ptr = path; do { if (synctex_ignore_leading_dot_slash_in_path(&ptr)) { return ptr; } do { if (!*(++ptr)) { return path; } } while (!SYNCTEX_IS_PATH_SEPARATOR(*ptr)); } while (*(++ptr)); return path; } /* Compare two file names, windows is sometimes case insensitive... */ synctex_bool_t _synctex_is_equivalent_file_name(const char *lhs, const char *rhs) { /* Remove the leading regex '(\./+)*' in both rhs and lhs */ synctex_ignore_leading_dot_slash_in_path(&lhs); synctex_ignore_leading_dot_slash_in_path(&rhs); next_character: if (SYNCTEX_IS_PATH_SEPARATOR(*lhs)) { /* lhs points to a path separator */ if (!SYNCTEX_IS_PATH_SEPARATOR(*rhs)) { /* but not rhs */ return synctex_NO; } ++lhs; ++rhs; synctex_ignore_leading_dot_slash_in_path(&lhs); synctex_ignore_leading_dot_slash_in_path(&rhs); goto next_character; } else if (SYNCTEX_IS_PATH_SEPARATOR(*rhs)) { /* rhs points to a path separator but not lhs */ return synctex_NO; } else if (SYNCTEX_ARE_PATH_CHARACTERS_EQUAL(*lhs, *rhs)) { /* uppercase do not match */ return synctex_NO; } else if (!*lhs) { /* lhs is at the end of the string */ return *rhs ? synctex_NO : synctex_YES; } else if (!*rhs) { /* rhs is at the end of the string but not lhs */ return synctex_NO; } ++lhs; ++rhs; goto next_character; } synctex_bool_t _synctex_path_is_absolute(const char *name) { if (!strlen(name)) { return synctex_NO; } #if defined(SYNCTEX_WINDOWS) || defined(SYNCTEX_OS2) if (strlen(name) > 2) { return (name[1] == ':' && SYNCTEX_IS_PATH_SEPARATOR(name[2])) ? synctex_YES : synctex_NO; } return synctex_NO; #else return SYNCTEX_IS_PATH_SEPARATOR(name[0]) ? synctex_YES : synctex_NO; #endif } /* We do not take care of UTF-8 */ const char *_synctex_last_path_component(const char *name) { const char *c = name + strlen(name); if (c > name) { if (!SYNCTEX_IS_PATH_SEPARATOR(*c)) { do { --c; if (SYNCTEX_IS_PATH_SEPARATOR(*c)) { return c + 1; } } while (c > name); } return c; /* the last path component is the void string*/ } return c; } int _synctex_copy_with_quoting_last_path_component(const char *src, char **dest_ref, size_t size) { if (src && dest_ref) { const char *lpc; #define dest (*dest_ref) dest = NULL; /* Default behavior: no change and success. */ lpc = _synctex_last_path_component(src); if (strlen(lpc)) { if (strchr(lpc, ' ') && lpc[0] != '"' && lpc[strlen(lpc) - 1] != '"') { /* We are in the situation where adding the quotes is allowed. */ /* Time to add the quotes. */ /* Consistency test: we must have dest+size>dest+strlen(dest)+2 * or equivalently: strlen(dest)+2 0) { char *result = NULL; ++size; /* Create the memory storage */ if (NULL != (result = (char *)malloc(size))) { char *dest = result; va_start(arg, first); temp = first; do { if ((size = strlen(temp)) > 0) { /* There is something to merge */ if (dest != strncpy(dest, temp, size)) { _synctex_error("! _synctex_merge_strings: Copy problem"); free(result); result = NULL; return NULL; } dest += size; } } while ((temp = va_arg(arg, const char *)) != NULL); va_end(arg); dest[0] = '\0'; /* Terminate the merged string */ return result; } _synctex_error("! _synctex_merge_strings: Memory problem"); return NULL; } return NULL; } /* The purpose of _synctex_get_name is to find the name of the synctex file. * There is a list of possible filenames from which we return the most recent one and try to remove all the others. * With two runs of pdftex or xetex we are sure the synctex file is really the most appropriate. */ int _synctex_get_name(const char *output, const char *build_directory, char **synctex_name_ref, synctex_io_mode_t *io_mode_ref) { if (output && synctex_name_ref && io_mode_ref) { /* If output is already absolute, we just have to manage the quotes and the compress mode */ size_t size = 0; char *synctex_name = NULL; synctex_io_mode_t io_mode = *io_mode_ref; const char *base_name = _synctex_last_path_component(output); /* do not free, output is the owner. base name of output*/ /* Do we have a real base name ? */ if (strlen(base_name) > 0) { /* Yes, we do. */ const char *temp = NULL; char *core_name = NULL; /* base name of output without path extension. */ char *dir_name = NULL; /* dir name of output */ char *quoted_core_name = NULL; char *basic_name = NULL; char *gz_name = NULL; char *quoted_name = NULL; char *quoted_gz_name = NULL; char *build_name = NULL; char *build_gz_name = NULL; char *build_quoted_name = NULL; char *build_quoted_gz_name = NULL; struct stat buf; time_t the_time = 0; /* Create core_name: let temp point to the dot before the path extension of base_name; * We start form the \0 terminating character and scan the string upward until we find a dot. * The leading dot is not accepted. */ if ((temp = strrchr(base_name, '.')) && (size = temp - base_name) > 0) { /* There is a dot and it is not at the leading position */ if (NULL == (core_name = (char *)malloc(size + 1))) { _synctex_error("! _synctex_get_name: Memory problem 1"); return -1; } if (core_name != strncpy(core_name, base_name, size)) { _synctex_error("! _synctex_get_name: Copy problem 1"); free(core_name); dir_name = NULL; return -2; } core_name[size] = '\0'; } else { /* There is no path extension, * Just make a copy of base_name */ core_name = _synctex_merge_strings(base_name); } /* core_name is properly set up, owned by "self". */ /* creating dir_name. */ size = strlen(output) - strlen(base_name); if (size > 0) { /* output contains more than one path component */ if (NULL == (dir_name = (char *)malloc(size + 1))) { _synctex_error("! _synctex_get_name: Memory problem"); free(core_name); return -1; } if (dir_name != strncpy(dir_name, output, size)) { _synctex_error("! _synctex_get_name: Copy problem"); free(dir_name); dir_name = NULL; free(core_name); dir_name = NULL; return -2; } dir_name[size] = '\0'; } /* dir_name is properly set up. It ends with a path separator, if non void. */ /* creating quoted_core_name. */ if (strchr(core_name, ' ')) { quoted_core_name = _synctex_merge_strings("\"", core_name, "\""); } /* quoted_core_name is properly set up. */ if (dir_name && strlen(dir_name) > 0) { basic_name = _synctex_merge_strings(dir_name, core_name, synctex_suffix, NULL); if (quoted_core_name && strlen(quoted_core_name) > 0) { quoted_name = _synctex_merge_strings(dir_name, quoted_core_name, synctex_suffix, NULL); } } else { basic_name = _synctex_merge_strings(core_name, synctex_suffix, NULL); if (quoted_core_name && strlen(quoted_core_name) > 0) { quoted_name = _synctex_merge_strings(quoted_core_name, synctex_suffix, NULL); } } if (!_synctex_path_is_absolute(output) && build_directory && (size = strlen(build_directory))) { temp = build_directory + size - 1; if (_synctex_path_is_absolute(temp)) { build_name = _synctex_merge_strings(build_directory, basic_name, NULL); if (quoted_core_name && strlen(quoted_core_name) > 0) { build_quoted_name = _synctex_merge_strings(build_directory, quoted_name, NULL); } } else { build_name = _synctex_merge_strings(build_directory, "/", basic_name, NULL); if (quoted_core_name && strlen(quoted_core_name) > 0) { build_quoted_name = _synctex_merge_strings(build_directory, "/", quoted_name, NULL); } } } if (basic_name) { gz_name = _synctex_merge_strings(basic_name, synctex_suffix_gz, NULL); } if (quoted_name) { quoted_gz_name = _synctex_merge_strings(quoted_name, synctex_suffix_gz, NULL); } if (build_name) { build_gz_name = _synctex_merge_strings(build_name, synctex_suffix_gz, NULL); } if (build_quoted_name) { build_quoted_gz_name = _synctex_merge_strings(build_quoted_name, synctex_suffix_gz, NULL); } /* All the others names are properly set up... */ /* retain the most recently modified file */ #define TEST(FILENAME, COMPRESS_MODE) \ if (FILENAME) { \ if (stat(FILENAME, &buf)) { \ free(FILENAME); \ FILENAME = NULL; \ } else if (buf.st_mtime > the_time) { \ the_time = buf.st_mtime; \ synctex_name = FILENAME; \ if (COMPRESS_MODE) { \ io_mode |= synctex_io_gz_mask; \ } else { \ io_mode &= ~synctex_io_gz_mask; \ } \ } \ } TEST(basic_name, synctex_DONT_COMPRESS); TEST(gz_name, synctex_COMPRESS); TEST(quoted_name, synctex_DONT_COMPRESS); TEST(quoted_gz_name, synctex_COMPRESS); TEST(build_name, synctex_DONT_COMPRESS); TEST(build_gz_name, synctex_COMPRESS); TEST(build_quoted_name, synctex_DONT_COMPRESS); TEST(build_quoted_gz_name, synctex_COMPRESS); #undef TEST /* Free all the intermediate filenames, except the one that will be used as returned value. */ #define CLEAN_AND_REMOVE(FILENAME) \ if (FILENAME && (FILENAME != synctex_name)) { \ remove(FILENAME); \ printf("synctex tool info: %s removed\n", FILENAME); \ free(FILENAME); \ FILENAME = NULL; \ } CLEAN_AND_REMOVE(basic_name); CLEAN_AND_REMOVE(gz_name); CLEAN_AND_REMOVE(quoted_name); CLEAN_AND_REMOVE(quoted_gz_name); CLEAN_AND_REMOVE(build_name); CLEAN_AND_REMOVE(build_gz_name); CLEAN_AND_REMOVE(build_quoted_name); CLEAN_AND_REMOVE(build_quoted_gz_name); #undef CLEAN_AND_REMOVE /* set up the returned values */ *synctex_name_ref = synctex_name; /* synctex_name won't always end in .gz, even when compressed. */ FILE *F = fopen(synctex_name, "r"); if (F != NULL) { if (!feof(F) && 31 == fgetc(F) && !feof(F) && 139 == fgetc(F)) { io_mode = synctex_compress_mode_gz; } fclose(F); } *io_mode_ref = io_mode; return 0; } return -1; /* bad argument */ } return -2; } const char *_synctex_get_io_mode_name(synctex_io_mode_t io_mode) { static const char *synctex_io_modes[4] = {"r", "rb", "a", "ab"}; unsigned index = ((io_mode & synctex_io_gz_mask) ? 1 : 0) + ((io_mode & synctex_io_append_mask) ? 2 : 0); /* bug pointed out by Jose Alliste */ return synctex_io_modes[index]; }