synctex_parser_utils.c (19349B)
1 /* 2 Copyright (c) 2008-2017 jerome DOT laurens AT u-bourgogne DOT fr 3 4 This file is part of the __SyncTeX__ package. 5 6 [//]: # (Latest Revision: Fri Jul 14 16:20:41 UTC 2017) 7 [//]: # (Version: 1.21) 8 9 See `synctex_parser_readme.md` for more details 10 11 ## License 12 13 Permission is hereby granted, free of charge, to any person 14 obtaining a copy of this software and associated documentation 15 files (the "Software"), to deal in the Software without 16 restriction, including without limitation the rights to use, 17 copy, modify, merge, publish, distribute, sublicense, and/or sell 18 copies of the Software, and to permit persons to whom the 19 Software is furnished to do so, subject to the following 20 conditions: 21 22 The above copyright notice and this permission notice shall be 23 included in all copies or substantial portions of the Software. 24 25 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 26 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 27 OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 28 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 29 HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 30 WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 31 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 32 OTHER DEALINGS IN THE SOFTWARE 33 34 Except as contained in this notice, the name of the copyright holder 35 shall not be used in advertising or otherwise to promote the sale, 36 use or other dealings in this Software without prior written 37 authorization from the copyright holder. 38 39 */ 40 41 /* In this file, we find all the functions that may depend on the operating system. */ 42 43 #include <synctex_parser_utils.h> 44 #include <stdlib.h> 45 #include <string.h> 46 #include <stdarg.h> 47 #include <stdio.h> 48 49 #include <limits.h> 50 #include <ctype.h> 51 #include <string.h> 52 53 #include <sys/stat.h> 54 55 #if defined(_WIN32) || defined(__WIN32__) || defined(__TOS_WIN__) || defined(__WINDOWS__) 56 #define SYNCTEX_WINDOWS 1 57 #endif 58 59 #if defined(__OS2__) 60 #define SYNCTEX_OS2 1 61 #endif 62 63 #if defined(_WIN32) 64 #define SYNCTEX_RECENT_WINDOWS 1 65 #endif 66 67 #ifdef SYNCTEX_WINDOWS 68 #include <windows.h> 69 #include <shlwapi.h> /* Use shlwapi.lib */ 70 #endif 71 72 void *_synctex_malloc(size_t size) { 73 void * ptr = malloc(size); 74 if(ptr) { 75 memset(ptr,0, size);/* ensures null termination of strings */ 76 } 77 return (void *)ptr; 78 } 79 80 void _synctex_free(void * ptr) { 81 if (ptr) { 82 free(ptr); 83 } 84 } 85 86 #if !defined(_WIN32) 87 # include <syslog.h> 88 #endif 89 90 int _synctex_log(int level, const char * prompt, const char * reason,va_list arg) { 91 int result; 92 # ifdef SYNCTEX_RECENT_WINDOWS 93 {/* This code is contributed by William Blum. 94 As it does not work on some older computers, 95 the _WIN32 conditional here is replaced with a SYNCTEX_RECENT_WINDOWS one. 96 According to http://msdn.microsoft.com/en-us/library/aa363362(VS.85).aspx 97 Minimum supported client Windows 2000 Professional 98 Minimum supported server Windows 2000 Server 99 People running Windows 2K standard edition will not have OutputDebugStringA. 100 JL.*/ 101 char *buff; 102 size_t len; 103 OutputDebugStringA(prompt); 104 len = _vscprintf(reason, arg) + 1; 105 buff = (char*)malloc( len * sizeof(char) ); 106 result = vsprintf(buff, reason, arg) +strlen(prompt); 107 OutputDebugStringA(buff); 108 OutputDebugStringA("\n"); 109 free(buff); 110 } 111 # elif SYNCTEX_USE_SYSLOG 112 char * buffer1 = NULL; 113 char * buffer2 = NULL; 114 openlog ("SyncTeX", LOG_CONS | LOG_PID | LOG_PERROR | LOG_NDELAY, LOG_LOCAL0); 115 if (vasprintf(&buffer1,reason,arg)>=0 116 && asprintf(&buffer2,"%s%s",prompt, buffer1)>=0) { 117 syslog (level, "%s", buffer2); 118 result = (int)strlen(buffer2); 119 } else { 120 syslog (level, "%s",prompt); 121 vsyslog(level,reason,arg); 122 result = (int)strlen(prompt); 123 } 124 free(buffer1); 125 free(buffer2); 126 closelog(); 127 # else 128 FILE * where = level == LOG_ERR? stderr: stdout; 129 result = fputs(prompt,where); 130 result += vfprintf(where, reason, arg); 131 result += fprintf(where,"\n"); 132 # endif 133 return result; 134 } 135 136 int _synctex_error(const char * reason,...) { 137 va_list arg; 138 int result; 139 va_start (arg, reason); 140 #if defined(SYNCTEX_RECENT_WINDOWS) /* LOG_ERR is not used */ 141 result = _synctex_log(0, "! SyncTeX Error : ", reason, arg); 142 #else 143 result = _synctex_log(LOG_ERR, "! SyncTeX Error : ", reason, arg); 144 #endif 145 va_end (arg); 146 return result; 147 } 148 149 int _synctex_debug(const char * reason,...) { 150 va_list arg; 151 int result; 152 va_start (arg, reason); 153 #if defined(SYNCTEX_RECENT_WINDOWS) /* LOG_DEBUG is not used */ 154 result = _synctex_log(0, "! SyncTeX Error : ", reason, arg); 155 #else 156 result = _synctex_log(LOG_DEBUG, "! SyncTeX Error : ", reason, arg); 157 #endif 158 va_end (arg); 159 return result; 160 } 161 162 /* strip the last extension of the given string, this string is modified! */ 163 void _synctex_strip_last_path_extension(char * string) { 164 if(NULL != string){ 165 char * last_component = NULL; 166 char * last_extension = NULL; 167 # if defined(SYNCTEX_WINDOWS) 168 last_component = PathFindFileName(string); 169 last_extension = PathFindExtension(string); 170 if(last_extension == NULL)return; 171 if(last_component == NULL)last_component = string; 172 if(last_extension>last_component){/* filter out paths like "my/dir/.hidden" */ 173 last_extension[0] = '\0'; 174 } 175 # else 176 char * next = NULL; 177 /* first we find the last path component */ 178 if(NULL == (last_component = strstr(string,"/"))){ 179 last_component = string; 180 } else { 181 ++last_component; 182 while((next = strstr(last_component,"/"))){ 183 last_component = next+1; 184 } 185 } 186 # if defined(SYNCTEX_OS2) 187 /* On OS2, the '\' is also a path separator. */ 188 while((next = strstr(last_component,"\\"))){ 189 last_component = next+1; 190 } 191 # endif /* SYNCTEX_OS2 */ 192 /* then we find the last path extension */ 193 if((last_extension = strstr(last_component,"."))){ 194 ++last_extension; 195 while((next = strstr(last_extension,"."))){ 196 last_extension = next+1; 197 } 198 --last_extension;/* back to the "." */ 199 if(last_extension>last_component){/* filter out paths like ....my/dir/.hidden"*/ 200 last_extension[0] = '\0'; 201 } 202 } 203 # endif /* SYNCTEX_WINDOWS */ 204 } 205 } 206 207 synctex_bool_t synctex_ignore_leading_dot_slash_in_path(const char ** name_ref) 208 { 209 if (SYNCTEX_IS_DOT((*name_ref)[0]) && SYNCTEX_IS_PATH_SEPARATOR((*name_ref)[1])) { 210 do { 211 (*name_ref) += 2; 212 while (SYNCTEX_IS_PATH_SEPARATOR((*name_ref)[0])) { 213 ++(*name_ref); 214 } 215 } while(SYNCTEX_IS_DOT((*name_ref)[0]) && SYNCTEX_IS_PATH_SEPARATOR((*name_ref)[1])); 216 return synctex_YES; 217 } 218 return synctex_NO; 219 } 220 221 /* The base name is necessary to deal with the 2011 file naming convention... 222 * path is a '\0' terminated string 223 * The return value is the trailing part of the argument, 224 * just following the first occurrence of the regexp pattern "[^|/|\].[\|/]+".*/ 225 const char * _synctex_base_name(const char *path) { 226 const char * ptr = path; 227 do { 228 if (synctex_ignore_leading_dot_slash_in_path(&ptr)) { 229 return ptr; 230 } 231 do { 232 if (!*(++ptr)) { 233 return path; 234 } 235 } while (!SYNCTEX_IS_PATH_SEPARATOR(*ptr)); 236 } while (*(++ptr)); 237 return path; 238 } 239 240 /* Compare two file names, windows is sometimes case insensitive... */ 241 synctex_bool_t _synctex_is_equivalent_file_name(const char *lhs, const char *rhs) { 242 /* Remove the leading regex '(\./+)*' in both rhs and lhs */ 243 synctex_ignore_leading_dot_slash_in_path(&lhs); 244 synctex_ignore_leading_dot_slash_in_path(&rhs); 245 next_character: 246 if (SYNCTEX_IS_PATH_SEPARATOR(*lhs)) {/* lhs points to a path separator */ 247 if (!SYNCTEX_IS_PATH_SEPARATOR(*rhs)) {/* but not rhs */ 248 return synctex_NO; 249 } 250 ++lhs; 251 ++rhs; 252 synctex_ignore_leading_dot_slash_in_path(&lhs); 253 synctex_ignore_leading_dot_slash_in_path(&rhs); 254 goto next_character; 255 } else if (SYNCTEX_IS_PATH_SEPARATOR(*rhs)) {/* rhs points to a path separator but not lhs */ 256 return synctex_NO; 257 } else if (SYNCTEX_ARE_PATH_CHARACTERS_EQUAL(*lhs,*rhs)){/* uppercase do not match */ 258 return synctex_NO; 259 } else if (!*lhs) {/* lhs is at the end of the string */ 260 return *rhs ? synctex_NO : synctex_YES; 261 } else if(!*rhs) {/* rhs is at the end of the string but not lhs */ 262 return synctex_NO; 263 } 264 ++lhs; 265 ++rhs; 266 goto next_character; 267 } 268 269 synctex_bool_t _synctex_path_is_absolute(const char * name) { 270 if(!strlen(name)) { 271 return synctex_NO; 272 } 273 # if defined(SYNCTEX_WINDOWS) || defined(SYNCTEX_OS2) 274 if(strlen(name)>2) { 275 return (name[1]==':' && SYNCTEX_IS_PATH_SEPARATOR(name[2]))?synctex_YES:synctex_NO; 276 } 277 return synctex_NO; 278 # else 279 return SYNCTEX_IS_PATH_SEPARATOR(name[0])?synctex_YES:synctex_NO; 280 # endif 281 } 282 283 /* We do not take care of UTF-8 */ 284 const char * _synctex_last_path_component(const char * name) { 285 const char * c = name+strlen(name); 286 if(c>name) { 287 if(!SYNCTEX_IS_PATH_SEPARATOR(*c)) { 288 do { 289 --c; 290 if(SYNCTEX_IS_PATH_SEPARATOR(*c)) { 291 return c+1; 292 } 293 } while(c>name); 294 } 295 return c;/* the last path component is the void string*/ 296 } 297 return c; 298 } 299 300 int _synctex_copy_with_quoting_last_path_component(const char * src, char ** dest_ref, size_t size) { 301 if(src && dest_ref) { 302 const char * lpc; 303 # define dest (*dest_ref) 304 dest = NULL; /* Default behavior: no change and success. */ 305 lpc = _synctex_last_path_component(src); 306 if(strlen(lpc)) { 307 if(strchr(lpc,' ') && lpc[0]!='"' && lpc[strlen(lpc)-1]!='"') { 308 /* We are in the situation where adding the quotes is allowed. */ 309 /* Time to add the quotes. */ 310 /* Consistency test: we must have dest+size>dest+strlen(dest)+2 311 * or equivalently: strlen(dest)+2<size (see below) */ 312 if(strlen(src)<size) { 313 if((dest = (char *)malloc(size+2))) { 314 char * dpc = dest + (lpc-src); /* dpc is the last path component of dest. */ 315 if(dest != strncpy(dest,src,size)) { 316 _synctex_error("! _synctex_copy_with_quoting_last_path_component: Copy problem"); 317 free(dest); 318 dest = NULL;/* Don't forget to reinitialize. */ 319 return -2; 320 } 321 memmove(dpc+1,dpc,strlen(dpc)+1); /* Also move the null terminating character. */ 322 dpc[0]='"'; 323 dpc[strlen(dpc)+1]='\0';/* Consistency test */ 324 dpc[strlen(dpc)]='"'; 325 return 0; /* Success. */ 326 } 327 return -1; /* Memory allocation error. */ 328 } 329 _synctex_error("! _synctex_copy_with_quoting_last_path_component: Internal inconsistency"); 330 return -3; 331 } 332 return 0; /* Success. */ 333 } 334 return 0; /* No last path component. */ 335 # undef dest 336 } 337 return 1; /* Bad parameter, this value is subject to changes. */ 338 } 339 340 /* The client is responsible of the management of the returned string, if any. */ 341 char * _synctex_merge_strings(const char * first,...); 342 343 char * _synctex_merge_strings(const char * first,...) { 344 va_list arg; 345 size_t size = 0; 346 const char * temp; 347 /* First retrieve the size necessary to store the merged string */ 348 va_start (arg, first); 349 temp = first; 350 do { 351 size_t len = strlen(temp); 352 if(UINT_MAX-len<size) { 353 _synctex_error("! _synctex_merge_strings: Capacity exceeded."); 354 return NULL; 355 } 356 size+=len; 357 } while( (temp = va_arg(arg, const char *)) != NULL); 358 va_end(arg); 359 if(size>0) { 360 char * result = NULL; 361 ++size; 362 /* Create the memory storage */ 363 if(NULL!=(result = (char *)malloc(size))) { 364 char * dest = result; 365 va_start (arg, first); 366 temp = first; 367 do { 368 if((size = strlen(temp))>0) { 369 /* There is something to merge */ 370 if(dest != strncpy(dest,temp,size)) { 371 _synctex_error("! _synctex_merge_strings: Copy problem"); 372 free(result); 373 result = NULL; 374 return NULL; 375 } 376 dest += size; 377 } 378 } while( (temp = va_arg(arg, const char *)) != NULL); 379 va_end(arg); 380 dest[0]='\0';/* Terminate the merged string */ 381 return result; 382 } 383 _synctex_error("! _synctex_merge_strings: Memory problem"); 384 return NULL; 385 } 386 return NULL; 387 } 388 389 /* The purpose of _synctex_get_name is to find the name of the synctex file. 390 * There is a list of possible filenames from which we return the most recent one and try to remove all the others. 391 * With two runs of pdftex or xetex we are sure the the synctex file is really the most appropriate. 392 */ 393 int _synctex_get_name(const char * output, const char * build_directory, char ** synctex_name_ref, synctex_io_mode_t * io_mode_ref) 394 { 395 if(output && synctex_name_ref && io_mode_ref) { 396 /* If output is already absolute, we just have to manage the quotes and the compress mode */ 397 size_t size = 0; 398 char * synctex_name = NULL; 399 synctex_io_mode_t io_mode = *io_mode_ref; 400 const char * base_name = _synctex_last_path_component(output); /* do not free, output is the owner. base name of output*/ 401 /* Do we have a real base name ? */ 402 if(strlen(base_name)>0) { 403 /* Yes, we do. */ 404 const char * temp = NULL; 405 char * core_name = NULL; /* base name of output without path extension. */ 406 char * dir_name = NULL; /* dir name of output */ 407 char * quoted_core_name = NULL; 408 char * basic_name = NULL; 409 char * gz_name = NULL; 410 char * quoted_name = NULL; 411 char * quoted_gz_name = NULL; 412 char * build_name = NULL; 413 char * build_gz_name = NULL; 414 char * build_quoted_name = NULL; 415 char * build_quoted_gz_name = NULL; 416 struct stat buf; 417 time_t the_time = 0; 418 /* Create core_name: let temp point to the dot before the path extension of base_name; 419 * We start form the \0 terminating character and scan the string upward until we find a dot. 420 * The leading dot is not accepted. */ 421 if((temp = strrchr(base_name,'.')) && (size = temp - base_name)>0) { 422 /* There is a dot and it is not at the leading position */ 423 if(NULL == (core_name = (char *)malloc(size+1))) { 424 _synctex_error("! _synctex_get_name: Memory problem 1"); 425 return -1; 426 } 427 if(core_name != strncpy(core_name,base_name,size)) { 428 _synctex_error("! _synctex_get_name: Copy problem 1"); 429 free(core_name); 430 dir_name = NULL; 431 return -2; 432 } 433 core_name[size] = '\0'; 434 } else { 435 /* There is no path extension, 436 * Just make a copy of base_name */ 437 core_name = _synctex_merge_strings(base_name); 438 } 439 /* core_name is properly set up, owned by "self". */ 440 /* creating dir_name. */ 441 size = strlen(output)-strlen(base_name); 442 if(size>0) { 443 /* output contains more than one path component */ 444 if(NULL == (dir_name = (char *)malloc(size+1))) { 445 _synctex_error("! _synctex_get_name: Memory problem"); 446 free(core_name); 447 return -1; 448 } 449 if(dir_name != strncpy(dir_name,output,size)) { 450 _synctex_error("! _synctex_get_name: Copy problem"); 451 free(dir_name); 452 dir_name = NULL; 453 free(core_name); 454 dir_name = NULL; 455 return -2; 456 } 457 dir_name[size] = '\0'; 458 } 459 /* dir_name is properly set up. It ends with a path separator, if non void. */ 460 /* creating quoted_core_name. */ 461 if(strchr(core_name,' ')) { 462 quoted_core_name = _synctex_merge_strings("\"",core_name,"\""); 463 } 464 /* quoted_core_name is properly set up. */ 465 if(dir_name &&strlen(dir_name)>0) { 466 basic_name = _synctex_merge_strings(dir_name,core_name,synctex_suffix,NULL); 467 if(quoted_core_name && strlen(quoted_core_name)>0) { 468 quoted_name = _synctex_merge_strings(dir_name,quoted_core_name,synctex_suffix,NULL); 469 } 470 } else { 471 basic_name = _synctex_merge_strings(core_name,synctex_suffix,NULL); 472 if(quoted_core_name && strlen(quoted_core_name)>0) { 473 quoted_name = _synctex_merge_strings(quoted_core_name,synctex_suffix,NULL); 474 } 475 } 476 if(!_synctex_path_is_absolute(output) && build_directory && (size = strlen(build_directory))) { 477 temp = build_directory + size - 1; 478 if(_synctex_path_is_absolute(temp)) { 479 build_name = _synctex_merge_strings(build_directory,basic_name,NULL); 480 if(quoted_core_name && strlen(quoted_core_name)>0) { 481 build_quoted_name = _synctex_merge_strings(build_directory,quoted_name,NULL); 482 } 483 } else { 484 build_name = _synctex_merge_strings(build_directory,"/",basic_name,NULL); 485 if(quoted_core_name && strlen(quoted_core_name)>0) { 486 build_quoted_name = _synctex_merge_strings(build_directory,"/",quoted_name,NULL); 487 } 488 } 489 } 490 if(basic_name) { 491 gz_name = _synctex_merge_strings(basic_name,synctex_suffix_gz,NULL); 492 } 493 if(quoted_name) { 494 quoted_gz_name = _synctex_merge_strings(quoted_name,synctex_suffix_gz,NULL); 495 } 496 if(build_name) { 497 build_gz_name = _synctex_merge_strings(build_name,synctex_suffix_gz,NULL); 498 } 499 if(build_quoted_name) { 500 build_quoted_gz_name = _synctex_merge_strings(build_quoted_name,synctex_suffix_gz,NULL); 501 } 502 /* All the others names are properly set up... */ 503 /* retain the most recently modified file */ 504 # define TEST(FILENAME,COMPRESS_MODE) \ 505 if(FILENAME) {\ 506 if (stat(FILENAME, &buf)) { \ 507 free(FILENAME);\ 508 FILENAME = NULL;\ 509 } else if (buf.st_mtime>the_time) { \ 510 the_time=buf.st_mtime; \ 511 synctex_name = FILENAME; \ 512 if (COMPRESS_MODE) { \ 513 io_mode |= synctex_io_gz_mask; \ 514 } else { \ 515 io_mode &= ~synctex_io_gz_mask; \ 516 } \ 517 } \ 518 } 519 TEST(basic_name,synctex_DONT_COMPRESS); 520 TEST(gz_name,synctex_COMPRESS); 521 TEST(quoted_name,synctex_DONT_COMPRESS); 522 TEST(quoted_gz_name,synctex_COMPRESS); 523 TEST(build_name,synctex_DONT_COMPRESS); 524 TEST(build_gz_name,synctex_COMPRESS); 525 TEST(build_quoted_name,synctex_DONT_COMPRESS); 526 TEST(build_quoted_gz_name,synctex_COMPRESS); 527 # undef TEST 528 /* Free all the intermediate filenames, except the one that will be used as returned value. */ 529 # define CLEAN_AND_REMOVE(FILENAME) \ 530 if(FILENAME && (FILENAME!=synctex_name)) {\ 531 remove(FILENAME);\ 532 printf("synctex tool info: %s removed\n",FILENAME);\ 533 free(FILENAME);\ 534 FILENAME = NULL;\ 535 } 536 CLEAN_AND_REMOVE(basic_name); 537 CLEAN_AND_REMOVE(gz_name); 538 CLEAN_AND_REMOVE(quoted_name); 539 CLEAN_AND_REMOVE(quoted_gz_name); 540 CLEAN_AND_REMOVE(build_name); 541 CLEAN_AND_REMOVE(build_gz_name); 542 CLEAN_AND_REMOVE(build_quoted_name); 543 CLEAN_AND_REMOVE(build_quoted_gz_name); 544 # undef CLEAN_AND_REMOVE 545 /* set up the returned values */ 546 * synctex_name_ref = synctex_name; 547 /* synctex_name won't always end in .gz, even when compressed. */ 548 FILE * F = fopen(synctex_name, "r"); 549 if (F != NULL) { 550 if (!feof(F) 551 && 31 == fgetc(F) 552 && !feof(F) 553 && 139 == fgetc(F)) { 554 io_mode = synctex_compress_mode_gz; 555 } 556 fclose(F); 557 } 558 * io_mode_ref = io_mode; 559 return 0; 560 } 561 return -1;/* bad argument */ 562 } 563 return -2; 564 } 565 566 const char * _synctex_get_io_mode_name(synctex_io_mode_t io_mode) { 567 static const char * synctex_io_modes[4] = {"r","rb","a","ab"}; 568 unsigned index = ((io_mode & synctex_io_gz_mask)?1:0) + ((io_mode & synctex_io_append_mask)?2:0);// bug pointed out by Jose Alliste 569 return synctex_io_modes[index]; 570 }