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