okular/core/synctex/synctex_parser_utils.c
2021-06-10 08:11:56 +00:00

576 lines
25 KiB
C

/*
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 <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <synctex_parser_utils.h>
#include <ctype.h>
#include <limits.h>
#include <string.h>
#include <sys/stat.h>
#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 <shlwapi.h> /* Use shlwapi.lib */
#include <windows.h>
#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 <syslog.h>
#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) {
// 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<size (see below) */
if (strlen(src) < size) {
if ((dest = (char *)malloc(size + 2))) {
char *dpc = dest + (lpc - src); /* dpc is the last path component of dest. */
if (dest != strncpy(dest, src, size)) {
_synctex_error("! _synctex_copy_with_quoting_last_path_component: Copy problem");
free(dest);
dest = NULL; /* Don't forget to reinitialize. */
return -2;
}
memmove(dpc + 1, dpc, strlen(dpc) + 1); /* Also move the null terminating character. */
dpc[0] = '"';
dpc[strlen(dpc) + 1] = '\0'; /* Consistency test */
dpc[strlen(dpc)] = '"';
return 0; /* Success. */
}
return -1; /* Memory allocation error. */
}
_synctex_error("! _synctex_copy_with_quoting_last_path_component: Internal inconsistency");
return -3;
}
return 0; /* Success. */
}
return 0; /* No last path component. */
#undef dest
}
return 1; /* Bad parameter, this value is subject to changes. */
}
/* The client is responsible of the management of the returned string, if any. */
char *_synctex_merge_strings(const char *first, ...);
char *_synctex_merge_strings(const char *first, ...)
{
va_list arg;
size_t size = 0;
const char *temp;
/* First retrieve the size necessary to store the merged string */
va_start(arg, first);
temp = first;
do {
size_t len = strlen(temp);
if (UINT_MAX - len < size) {
_synctex_error("! _synctex_merge_strings: Capacity exceeded.");
return NULL;
}
size += len;
} while ((temp = va_arg(arg, const char *)) != NULL);
va_end(arg);
if (size > 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];
}