File indexing completed on 2024-05-12 04:33:37
0001 /* 0002 SPDX-FileCopyrightText: 2008-2017 jerome DOT laurens AT u-bourgogne DOT fr 0003 SPDX-License-Identifier: X11 0004 0005 This file is part of the __SyncTeX__ package. 0006 0007 [//]: # (Latest Revision: Fri Jul 14 16:20:41 UTC 2017) 0008 [//]: # (Version: 1.19) 0009 0010 See `synctex_parser_readme.md` for more details 0011 */ 0012 0013 /* In this file, we find all the functions that may depend on the operating system. */ 0014 0015 #include <stdarg.h> 0016 #include <stdio.h> 0017 #include <stdlib.h> 0018 #include <string.h> 0019 #include <synctex_parser_utils.h> 0020 0021 #include <ctype.h> 0022 #include <limits.h> 0023 #include <string.h> 0024 0025 #include <sys/stat.h> 0026 0027 #if defined(_WIN32) || defined(__WIN32__) || defined(__TOS_WIN__) || defined(__WINDOWS__) 0028 #define SYNCTEX_WINDOWS 1 0029 #endif 0030 0031 #if defined(__OS2__) 0032 #define SYNCTEX_OS2 1 0033 #endif 0034 0035 #if defined(_WIN32) 0036 #define SYNCTEX_RECENT_WINDOWS 1 0037 #endif 0038 0039 #ifdef SYNCTEX_WINDOWS 0040 #include <shlwapi.h> /* Use shlwapi.lib */ 0041 #include <windows.h> 0042 #endif 0043 0044 void *_synctex_malloc(size_t size) 0045 { 0046 void *ptr = malloc(size); 0047 if (ptr) { 0048 memset(ptr, 0, size); /* ensures null termination of strings */ 0049 } 0050 return (void *)ptr; 0051 } 0052 0053 void _synctex_free(void *ptr) 0054 { 0055 if (ptr) { 0056 free(ptr); 0057 } 0058 } 0059 0060 #if !defined(_WIN32) 0061 #include <syslog.h> 0062 #endif 0063 0064 int _synctex_log(int level, const char *prompt, const char *reason, ...) SYNCTEX_PRINTF_FORMAT(3, 4); 0065 int _synctex_log(int level, const char *prompt, const char *reason, ...) 0066 { 0067 va_list arg; 0068 int result; 0069 va_start(arg, reason); 0070 #ifdef SYNCTEX_RECENT_WINDOWS 0071 { /* This code is contributed by William Blum. 0072 As it does not work on some older computers, 0073 the _WIN32 conditional here is replaced with a SYNCTEX_RECENT_WINDOWS one. 0074 According to http://msdn.microsoft.com/en-us/library/aa363362(VS.85).aspx 0075 Minimum supported client Windows 2000 Professional 0076 Minimum supported server Windows 2000 Server 0077 People running Windows 2K standard edition will not have OutputDebugStringA. 0078 JL.*/ 0079 char *buff; 0080 size_t len; 0081 OutputDebugStringA(prompt); 0082 #ifdef _MSC_VER 0083 len = _vscprintf(reason, arg) + 1; 0084 buff = (char *)malloc(len * sizeof(char)); 0085 #else /* MinGW */ 0086 size_t buffersize = 1024; 0087 size_t max_buffersize = 1024 * buffersize; 0088 int result; 0089 buff = (char *)malloc(buffersize * sizeof(char)); 0090 result = _vsnprintf(buff, buffersize - 1, reason, arg); 0091 while (-1 == result && buffersize <= max_buffersize) { 0092 buffersize = buffersize * 2; 0093 buff = (char *)realloc(buff, buffersize * sizeof(char)); 0094 result = _vsnprintf(buff, buffersize - 1, reason, arg); 0095 } 0096 if (-1 == result) { 0097 va_end(arg); 0098 // could not make the buffer big enough or simply could not write to it 0099 free(buff); 0100 return -1; 0101 } 0102 #endif 0103 result = vsprintf(buff, reason, arg) + strlen(prompt); 0104 OutputDebugStringA(buff); 0105 OutputDebugStringA("\n"); 0106 free(buff); 0107 } 0108 #elif defined SYNCTEX_USE_SYSLOG 0109 char *buffer1 = NULL; 0110 char *buffer2 = NULL; 0111 openlog("SyncTeX", LOG_CONS | LOG_PID | LOG_PERROR | LOG_NDELAY, LOG_LOCAL0); 0112 if (vasprintf(&buffer1, reason, arg) >= 0 && asprintf(&buffer2, "%s%s", prompt, buffer1) >= 0) { 0113 syslog(level, "%s", buffer2); 0114 result = (int)strlen(buffer2); 0115 } else { 0116 syslog(level, "%s", prompt); 0117 vsyslog(level, reason, arg); 0118 result = (int)strlen(prompt); 0119 } 0120 free(buffer1); 0121 free(buffer2); 0122 closelog(); 0123 #else 0124 FILE *where = level == LOG_ERR ? stderr : stdout; 0125 result = fputs(prompt, where); 0126 result += vfprintf(where, reason, arg); 0127 result += fprintf(where, "\n"); 0128 #endif 0129 va_end(arg); 0130 return result; 0131 } 0132 0133 int _synctex_error(const char *reason, ...) 0134 { 0135 va_list arg; 0136 int result; 0137 va_start(arg, reason); 0138 #if defined(SYNCTEX_RECENT_WINDOWS) /* LOG_ERR is not used */ 0139 result = _synctex_log(0, "! SyncTeX Error : ", reason, arg); 0140 #else 0141 result = _synctex_log(LOG_ERR, "! SyncTeX Error : ", reason, arg); 0142 #endif 0143 va_end(arg); 0144 return result; 0145 } 0146 0147 int _synctex_debug(const char *reason, ...) 0148 { 0149 va_list arg; 0150 int result; 0151 va_start(arg, reason); 0152 #if defined(SYNCTEX_RECENT_WINDOWS) /* LOG_DEBUG is not used */ 0153 result = _synctex_log(0, "! SyncTeX Error : ", reason, arg); 0154 #else 0155 result = _synctex_log(LOG_DEBUG, "! SyncTeX Error : ", reason, arg); 0156 #endif 0157 va_end(arg); 0158 return result; 0159 } 0160 0161 /* strip the last extension of the given string, this string is modified! */ 0162 void _synctex_strip_last_path_extension(char *string) 0163 { 0164 if (NULL != string) { 0165 char *last_component = NULL; 0166 char *last_extension = NULL; 0167 #if defined(SYNCTEX_WINDOWS) 0168 last_component = PathFindFileNameA(string); 0169 last_extension = PathFindExtensionA(string); 0170 if (last_extension == NULL) 0171 return; 0172 if (last_component == NULL) 0173 last_component = string; 0174 if (last_extension > last_component) { /* filter out paths like "my/dir/.hidden" */ 0175 last_extension[0] = '\0'; 0176 } 0177 #else 0178 char *next = NULL; 0179 /* first we find the last path component */ 0180 if (NULL == (last_component = strstr(string, "/"))) { 0181 last_component = string; 0182 } else { 0183 ++last_component; 0184 while ((next = strstr(last_component, "/"))) { 0185 last_component = next + 1; 0186 } 0187 } 0188 #if defined(SYNCTEX_OS2) 0189 /* On OS2, the '\' is also a path separator. */ 0190 while ((next = strstr(last_component, "\\"))) { 0191 last_component = next + 1; 0192 } 0193 #endif /* SYNCTEX_OS2 */ 0194 /* then we find the last path extension */ 0195 if ((last_extension = strstr(last_component, "."))) { 0196 ++last_extension; 0197 while ((next = strstr(last_extension, "."))) { 0198 last_extension = next + 1; 0199 } 0200 --last_extension; /* back to the "." */ 0201 if (last_extension > last_component) { /* filter out paths like ....my/dir/.hidden"*/ 0202 last_extension[0] = '\0'; 0203 } 0204 } 0205 #endif /* SYNCTEX_WINDOWS */ 0206 } 0207 } 0208 0209 synctex_bool_t synctex_ignore_leading_dot_slash_in_path(const char **name_ref) 0210 { 0211 if (SYNCTEX_IS_DOT((*name_ref)[0]) && SYNCTEX_IS_PATH_SEPARATOR((*name_ref)[1])) { 0212 do { 0213 (*name_ref) += 2; 0214 while (SYNCTEX_IS_PATH_SEPARATOR((*name_ref)[0])) { 0215 ++(*name_ref); 0216 } 0217 } while (SYNCTEX_IS_DOT((*name_ref)[0]) && SYNCTEX_IS_PATH_SEPARATOR((*name_ref)[1])); 0218 return synctex_YES; 0219 } 0220 return synctex_NO; 0221 } 0222 0223 /* The base name is necessary to deal with the 2011 file naming convention... 0224 * path is a '\0' terminated string 0225 * The return value is the trailing part of the argument, 0226 * just following the first occurrence of the regexp pattern "[^|/|\].[\|/]+".*/ 0227 const char *_synctex_base_name(const char *path) 0228 { 0229 const char *ptr = path; 0230 do { 0231 if (synctex_ignore_leading_dot_slash_in_path(&ptr)) { 0232 return ptr; 0233 } 0234 do { 0235 if (!*(++ptr)) { 0236 return path; 0237 } 0238 } while (!SYNCTEX_IS_PATH_SEPARATOR(*ptr)); 0239 } while (*(++ptr)); 0240 return path; 0241 } 0242 0243 /* Compare two file names, windows is sometimes case insensitive... */ 0244 synctex_bool_t _synctex_is_equivalent_file_name(const char *lhs, const char *rhs) 0245 { 0246 /* Remove the leading regex '(\./+)*' in both rhs and lhs */ 0247 synctex_ignore_leading_dot_slash_in_path(&lhs); 0248 synctex_ignore_leading_dot_slash_in_path(&rhs); 0249 next_character: 0250 if (SYNCTEX_IS_PATH_SEPARATOR(*lhs)) { /* lhs points to a path separator */ 0251 if (!SYNCTEX_IS_PATH_SEPARATOR(*rhs)) { /* but not rhs */ 0252 return synctex_NO; 0253 } 0254 ++lhs; 0255 ++rhs; 0256 synctex_ignore_leading_dot_slash_in_path(&lhs); 0257 synctex_ignore_leading_dot_slash_in_path(&rhs); 0258 goto next_character; 0259 } else if (SYNCTEX_IS_PATH_SEPARATOR(*rhs)) { /* rhs points to a path separator but not lhs */ 0260 return synctex_NO; 0261 } else if (SYNCTEX_ARE_PATH_CHARACTERS_EQUAL(*lhs, *rhs)) { /* uppercase do not match */ 0262 return synctex_NO; 0263 } else if (!*lhs) { /* lhs is at the end of the string */ 0264 return *rhs ? synctex_NO : synctex_YES; 0265 } else if (!*rhs) { /* rhs is at the end of the string but not lhs */ 0266 return synctex_NO; 0267 } 0268 ++lhs; 0269 ++rhs; 0270 goto next_character; 0271 } 0272 0273 synctex_bool_t _synctex_path_is_absolute(const char *name) 0274 { 0275 if (!strlen(name)) { 0276 return synctex_NO; 0277 } 0278 #if defined(SYNCTEX_WINDOWS) || defined(SYNCTEX_OS2) 0279 if (strlen(name) > 2) { 0280 return (name[1] == ':' && SYNCTEX_IS_PATH_SEPARATOR(name[2])) ? synctex_YES : synctex_NO; 0281 } 0282 return synctex_NO; 0283 #else 0284 return SYNCTEX_IS_PATH_SEPARATOR(name[0]) ? synctex_YES : synctex_NO; 0285 #endif 0286 } 0287 0288 /* We do not take care of UTF-8 */ 0289 const char *_synctex_last_path_component(const char *name) 0290 { 0291 const char *c = name + strlen(name); 0292 if (c > name) { 0293 if (!SYNCTEX_IS_PATH_SEPARATOR(*c)) { 0294 do { 0295 --c; 0296 if (SYNCTEX_IS_PATH_SEPARATOR(*c)) { 0297 return c + 1; 0298 } 0299 } while (c > name); 0300 } 0301 return c; /* the last path component is the void string*/ 0302 } 0303 return c; 0304 } 0305 0306 int _synctex_copy_with_quoting_last_path_component(const char *src, char **dest_ref, size_t size) 0307 { 0308 if (src && dest_ref) { 0309 const char *lpc; 0310 #define dest (*dest_ref) 0311 dest = NULL; /* Default behavior: no change and success. */ 0312 lpc = _synctex_last_path_component(src); 0313 if (strlen(lpc)) { 0314 if (strchr(lpc, ' ') && lpc[0] != '"' && lpc[strlen(lpc) - 1] != '"') { 0315 /* We are in the situation where adding the quotes is allowed. */ 0316 /* Time to add the quotes. */ 0317 /* Consistency test: we must have dest+size>dest+strlen(dest)+2 0318 * or equivalently: strlen(dest)+2<size (see below) */ 0319 if (strlen(src) < size) { 0320 if ((dest = (char *)malloc(size + 2))) { 0321 char *dpc = dest + (lpc - src); /* dpc is the last path component of dest. */ 0322 if (dest != strncpy(dest, src, size)) { 0323 _synctex_error("! _synctex_copy_with_quoting_last_path_component: Copy problem"); 0324 free(dest); 0325 dest = NULL; /* Don't forget to reinitialize. */ 0326 return -2; 0327 } 0328 memmove(dpc + 1, dpc, strlen(dpc) + 1); /* Also move the null terminating character. */ 0329 dpc[0] = '"'; 0330 dpc[strlen(dpc) + 1] = '\0'; /* Consistency test */ 0331 dpc[strlen(dpc)] = '"'; 0332 return 0; /* Success. */ 0333 } 0334 return -1; /* Memory allocation error. */ 0335 } 0336 _synctex_error("! _synctex_copy_with_quoting_last_path_component: Internal inconsistency"); 0337 return -3; 0338 } 0339 return 0; /* Success. */ 0340 } 0341 return 0; /* No last path component. */ 0342 #undef dest 0343 } 0344 return 1; /* Bad parameter, this value is subject to changes. */ 0345 } 0346 0347 /* The client is responsible of the management of the returned string, if any. */ 0348 char *_synctex_merge_strings(const char *first, ...); 0349 0350 char *_synctex_merge_strings(const char *first, ...) 0351 { 0352 va_list arg; 0353 size_t size = 0; 0354 const char *temp; 0355 /* First retrieve the size necessary to store the merged string */ 0356 va_start(arg, first); 0357 temp = first; 0358 do { 0359 size_t len = strlen(temp); 0360 if (UINT_MAX - len < size) { 0361 va_end(arg); 0362 _synctex_error("! _synctex_merge_strings: Capacity exceeded."); 0363 return NULL; 0364 } 0365 size += len; 0366 } while ((temp = va_arg(arg, const char *)) != NULL); 0367 va_end(arg); 0368 if (size > 0) { 0369 char *result = NULL; 0370 ++size; 0371 /* Create the memory storage */ 0372 if (NULL != (result = (char *)malloc(size))) { 0373 char *dest = result; 0374 va_start(arg, first); 0375 temp = first; 0376 do { 0377 if ((size = strlen(temp)) > 0) { 0378 /* There is something to merge */ 0379 if (dest != strncpy(dest, temp, size)) { 0380 _synctex_error("! _synctex_merge_strings: Copy problem"); 0381 free(result); 0382 result = NULL; 0383 return NULL; 0384 } 0385 dest += size; 0386 } 0387 } while ((temp = va_arg(arg, const char *)) != NULL); 0388 va_end(arg); 0389 dest[0] = '\0'; /* Terminate the merged string */ 0390 return result; 0391 } 0392 _synctex_error("! _synctex_merge_strings: Memory problem"); 0393 return NULL; 0394 } 0395 return NULL; 0396 } 0397 0398 /* The purpose of _synctex_get_name is to find the name of the synctex file. 0399 * There is a list of possible filenames from which we return the most recent one and try to remove all the others. 0400 * With two runs of pdftex or xetex we are sure the synctex file is really the most appropriate. 0401 */ 0402 int _synctex_get_name(const char *output, const char *build_directory, char **synctex_name_ref, synctex_io_mode_t *io_mode_ref) 0403 { 0404 if (output && synctex_name_ref && io_mode_ref) { 0405 /* If output is already absolute, we just have to manage the quotes and the compress mode */ 0406 size_t size = 0; 0407 char *synctex_name = NULL; 0408 synctex_io_mode_t io_mode = *io_mode_ref; 0409 const char *base_name = _synctex_last_path_component(output); /* do not free, output is the owner. base name of output*/ 0410 /* Do we have a real base name ? */ 0411 if (strlen(base_name) > 0) { 0412 /* Yes, we do. */ 0413 const char *temp = NULL; 0414 char *core_name = NULL; /* base name of output without path extension. */ 0415 char *dir_name = NULL; /* dir name of output */ 0416 char *quoted_core_name = NULL; 0417 char *basic_name = NULL; 0418 char *gz_name = NULL; 0419 char *quoted_name = NULL; 0420 char *quoted_gz_name = NULL; 0421 char *build_name = NULL; 0422 char *build_gz_name = NULL; 0423 char *build_quoted_name = NULL; 0424 char *build_quoted_gz_name = NULL; 0425 struct stat buf; 0426 time_t the_time = 0; 0427 /* Create core_name: let temp point to the dot before the path extension of base_name; 0428 * We start form the \0 terminating character and scan the string upward until we find a dot. 0429 * The leading dot is not accepted. */ 0430 if ((temp = strrchr(base_name, '.')) && (size = temp - base_name) > 0) { 0431 /* There is a dot and it is not at the leading position */ 0432 if (NULL == (core_name = (char *)malloc(size + 1))) { 0433 _synctex_error("! _synctex_get_name: Memory problem 1"); 0434 return -1; 0435 } 0436 if (core_name != strncpy(core_name, base_name, size)) { 0437 _synctex_error("! _synctex_get_name: Copy problem 1"); 0438 free(core_name); 0439 dir_name = NULL; 0440 return -2; 0441 } 0442 core_name[size] = '\0'; 0443 } else { 0444 /* There is no path extension, 0445 * Just make a copy of base_name */ 0446 core_name = _synctex_merge_strings(base_name); 0447 } 0448 /* core_name is properly set up, owned by "self". */ 0449 /* creating dir_name. */ 0450 size = strlen(output) - strlen(base_name); 0451 if (size > 0) { 0452 /* output contains more than one path component */ 0453 if (NULL == (dir_name = (char *)malloc(size + 1))) { 0454 _synctex_error("! _synctex_get_name: Memory problem"); 0455 free(core_name); 0456 return -1; 0457 } 0458 if (dir_name != strncpy(dir_name, output, size)) { 0459 _synctex_error("! _synctex_get_name: Copy problem"); 0460 free(dir_name); 0461 dir_name = NULL; 0462 free(core_name); 0463 dir_name = NULL; 0464 return -2; 0465 } 0466 dir_name[size] = '\0'; 0467 } 0468 /* dir_name is properly set up. It ends with a path separator, if non void. */ 0469 /* creating quoted_core_name. */ 0470 if (strchr(core_name, ' ')) { 0471 quoted_core_name = _synctex_merge_strings("\"", core_name, "\""); 0472 } 0473 /* quoted_core_name is properly set up. */ 0474 if (dir_name && strlen(dir_name) > 0) { 0475 basic_name = _synctex_merge_strings(dir_name, core_name, synctex_suffix, NULL); 0476 if (quoted_core_name && strlen(quoted_core_name) > 0) { 0477 quoted_name = _synctex_merge_strings(dir_name, quoted_core_name, synctex_suffix, NULL); 0478 } 0479 } else { 0480 basic_name = _synctex_merge_strings(core_name, synctex_suffix, NULL); 0481 if (quoted_core_name && strlen(quoted_core_name) > 0) { 0482 quoted_name = _synctex_merge_strings(quoted_core_name, synctex_suffix, NULL); 0483 } 0484 } 0485 if (!_synctex_path_is_absolute(output) && build_directory && (size = strlen(build_directory))) { 0486 temp = build_directory + size - 1; 0487 if (_synctex_path_is_absolute(temp)) { 0488 build_name = _synctex_merge_strings(build_directory, basic_name, NULL); 0489 if (quoted_core_name && strlen(quoted_core_name) > 0) { 0490 build_quoted_name = _synctex_merge_strings(build_directory, quoted_name, NULL); 0491 } 0492 } else { 0493 build_name = _synctex_merge_strings(build_directory, "/", basic_name, NULL); 0494 if (quoted_core_name && strlen(quoted_core_name) > 0) { 0495 build_quoted_name = _synctex_merge_strings(build_directory, "/", quoted_name, NULL); 0496 } 0497 } 0498 } 0499 if (basic_name) { 0500 gz_name = _synctex_merge_strings(basic_name, synctex_suffix_gz, NULL); 0501 } 0502 if (quoted_name) { 0503 quoted_gz_name = _synctex_merge_strings(quoted_name, synctex_suffix_gz, NULL); 0504 } 0505 if (build_name) { 0506 build_gz_name = _synctex_merge_strings(build_name, synctex_suffix_gz, NULL); 0507 } 0508 if (build_quoted_name) { 0509 build_quoted_gz_name = _synctex_merge_strings(build_quoted_name, synctex_suffix_gz, NULL); 0510 } 0511 /* All the others names are properly set up... */ 0512 /* retain the most recently modified file */ 0513 #define TEST(FILENAME, COMPRESS_MODE) \ 0514 if (FILENAME) { \ 0515 if (stat(FILENAME, &buf)) { \ 0516 free(FILENAME); \ 0517 FILENAME = NULL; \ 0518 } else if (buf.st_mtime > the_time) { \ 0519 the_time = buf.st_mtime; \ 0520 synctex_name = FILENAME; \ 0521 if (COMPRESS_MODE) { \ 0522 io_mode |= synctex_io_gz_mask; \ 0523 } else { \ 0524 io_mode &= ~synctex_io_gz_mask; \ 0525 } \ 0526 } \ 0527 } 0528 TEST(basic_name, synctex_DONT_COMPRESS); 0529 TEST(gz_name, synctex_COMPRESS); 0530 TEST(quoted_name, synctex_DONT_COMPRESS); 0531 TEST(quoted_gz_name, synctex_COMPRESS); 0532 TEST(build_name, synctex_DONT_COMPRESS); 0533 TEST(build_gz_name, synctex_COMPRESS); 0534 TEST(build_quoted_name, synctex_DONT_COMPRESS); 0535 TEST(build_quoted_gz_name, synctex_COMPRESS); 0536 #undef TEST 0537 /* Free all the intermediate filenames, except the one that will be used as returned value. */ 0538 #define CLEAN_AND_REMOVE(FILENAME) \ 0539 if (FILENAME && (FILENAME != synctex_name)) { \ 0540 remove(FILENAME); \ 0541 printf("synctex tool info: %s removed\n", FILENAME); \ 0542 free(FILENAME); \ 0543 FILENAME = NULL; \ 0544 } 0545 CLEAN_AND_REMOVE(basic_name); 0546 CLEAN_AND_REMOVE(gz_name); 0547 CLEAN_AND_REMOVE(quoted_name); 0548 CLEAN_AND_REMOVE(quoted_gz_name); 0549 CLEAN_AND_REMOVE(build_name); 0550 CLEAN_AND_REMOVE(build_gz_name); 0551 CLEAN_AND_REMOVE(build_quoted_name); 0552 CLEAN_AND_REMOVE(build_quoted_gz_name); 0553 #undef CLEAN_AND_REMOVE 0554 /* set up the returned values */ 0555 *synctex_name_ref = synctex_name; 0556 /* synctex_name won't always end in .gz, even when compressed. */ 0557 FILE *F = fopen(synctex_name, "r"); 0558 if (F != NULL) { 0559 if (!feof(F) && 31 == fgetc(F) && !feof(F) && 139 == fgetc(F)) { 0560 io_mode = synctex_compress_mode_gz; 0561 } 0562 fclose(F); 0563 } 0564 *io_mode_ref = io_mode; 0565 return 0; 0566 } 0567 return -1; /* bad argument */ 0568 } 0569 return -2; 0570 } 0571 0572 const char *_synctex_get_io_mode_name(synctex_io_mode_t io_mode) 0573 { 0574 static const char *synctex_io_modes[4] = {"r", "rb", "a", "ab"}; 0575 unsigned index = ((io_mode & synctex_io_gz_mask) ? 1 : 0) + ((io_mode & synctex_io_append_mask) ? 2 : 0); /* bug pointed out by Jose Alliste */ 0576 return synctex_io_modes[index]; 0577 }