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 }