xref: /Universal-ctags/main/routines.c (revision d53559843c3c07240e7ea2eb5dce190aa0ab69d7)
1 /*
2 *   Copyright (c) 2002-2003, Darren Hiebert
3 *
4 *   This source code is released for free distribution under the terms of the
5 *   GNU General Public License version 2 or (at your option) any later version.
6 *
7 *   This module contains a lose assortment of shared functions.
8 */
9 
10 /*
11 *   INCLUDE FILES
12 */
13 #include "general.h"  /* must always come first */
14 
15 #include <stdlib.h>  /* to declare malloc (), realloc (), mbcs() */
16 #include <ctype.h>
17 #include <string.h>
18 #include <stdio.h>  /* to declare tempnam(), and SEEK_SET (hopefully) */
19 
20 #ifdef HAVE_FCNTL_H
21 # include <fcntl.h>  /* to declare O_RDWR, O_CREAT, O_EXCL */
22 #endif
23 #ifdef HAVE_UNISTD_H
24 # include <unistd.h>  /* to declare mkstemp () */
25 #endif
26 
27 #include <limits.h>  /* to declare MB_LEN_MAX */
28 #ifndef MB_LEN_MAX
29 # define MB_LEN_MAX 6
30 #endif
31 
32 /*  To declare "struct stat" and stat ().
33  */
34 #if defined (HAVE_SYS_TYPES_H)
35 # include <sys/types.h>
36 #else
37 # if defined (HAVE_TYPES_H)
38 #  include <types.h>
39 # endif
40 #endif
41 #ifdef HAVE_SYS_STAT_H
42 # include <sys/stat.h>
43 #else
44 # ifdef HAVE_STAT_H
45 #  include <stat.h>
46 # endif
47 #endif
48 
49 #ifdef HAVE_DIRECT_H
50 # include <direct.h>  /* to _getcwd */
51 #endif
52 #ifdef HAVE_IO_H
53 # include <io.h>  /* to declare open() */
54 #endif
55 #include "debug.h"
56 #include "routines.h"
57 #include "routines_p.h"
58 #include <errno.h>
59 
60 #include "vstring.h"
61 
62 /*
63 *   MACROS
64 */
65 #ifndef TMPDIR
66 # define TMPDIR "/tmp"
67 #endif
68 
69 /*  File type tests.
70  */
71 #ifndef S_ISREG
72 # if defined (S_IFREG)
73 #  define S_ISREG(mode)		((mode) & S_IFREG)
74 # endif
75 #endif
76 
77 #ifndef S_ISLNK
78 # ifdef S_IFLNK
79 #  define S_ISLNK(mode)		(((mode) & S_IFMT) == S_IFLNK)
80 # else
81 #  define S_ISLNK(mode)		false  /* assume no soft links */
82 # endif
83 #endif
84 
85 #ifndef S_ISDIR
86 # ifdef S_IFDIR
87 #  define S_ISDIR(mode)		(((mode) & S_IFMT) == S_IFDIR)
88 # else
89 #  define S_ISDIR(mode)		false  /* assume no soft links */
90 # endif
91 #endif
92 
93 #ifndef S_IFMT
94 # define S_IFMT 0
95 #endif
96 
97 #ifndef S_IXUSR
98 # define S_IXUSR 0
99 #endif
100 #ifndef S_IXGRP
101 # define S_IXGRP 0
102 #endif
103 #ifndef S_IXOTH
104 # define S_IXOTH 0
105 #endif
106 
107 #ifndef S_IRUSR
108 # define S_IRUSR 0400
109 #endif
110 #ifndef S_IWUSR
111 # define S_IWUSR 0200
112 #endif
113 
114 #ifndef S_ISUID
115 # define S_ISUID 0
116 #endif
117 
118 #ifndef S_ISGID
119 # define S_ISGID 0
120 #endif
121 
122 /*  Hack for ridiculous practice of Microsoft Visual C++.
123  */
124 #if defined (WIN32)
125 # if defined (_MSC_VER) || defined (__MINGW32__)
126 #  ifndef stat
127 #   define stat    _stat
128 #  endif
129 #  define getcwd  _getcwd
130 #  define currentdrive() (_getdrive() + 'A' - 1)
131 # else
132 #  define currentdrive() 'C'
133 # endif
134 #endif
135 
136 #ifndef PATH_MAX
137 # define PATH_MAX 256
138 #endif
139 
140 /*
141  *  Miscellaneous macros
142  */
143 
144 
145 /*
146 *   DATA DEFINITIONS
147 */
148 #if defined (MSDOS_STYLE_PATH)
149 const char *const PathDelimiters = ":/\\";
150 #endif
151 
152 char *CurrentDirectory;
153 
154 static const char *ExecutableProgram;
155 static const char *ExecutableName;
156 
157 /*
158 *   FUNCTION PROTOTYPES
159 */
160 #ifdef NEED_PROTO_STAT
161 extern int stat (const char *, struct stat *);
162 #endif
163 #ifdef NEED_PROTO_LSTAT
164 extern int lstat (const char *, struct stat *);
165 #endif
166 #if defined (WIN32)
167 # define lstat(fn,buf) stat(fn,buf)
168 #endif
169 
170 static bool isPathSeparator (const int c);
171 static char *strSeparator (const char *s);
172 static char *strRSeparator (const char *s);
173 static void canonicalizePath (char *const path);
174 
175 
176 /*
177 *   FUNCTION DEFINITIONS
178 */
179 
freeRoutineResources(void)180 extern void freeRoutineResources (void)
181 {
182 	if (CurrentDirectory != NULL)
183 		eFree (CurrentDirectory);
184 }
185 
setExecutableName(const char * const path)186 extern void setExecutableName (const char *const path)
187 {
188 	ExecutableProgram = path;
189 	ExecutableName = baseFilename (path);
190 }
191 
getExecutableName(void)192 extern const char *getExecutableName (void)
193 {
194 	return ExecutableName;
195 }
196 
getExecutablePath(void)197 extern const char *getExecutablePath (void)
198 {
199 	return ExecutableProgram;
200 }
201 
202 /*
203  *  compare file/dirname characters with platform-correct case sensitivity
204  */
fnmChEq(int c1,int c2)205 static bool fnmChEq (int c1, int c2)
206 {
207 #ifdef WIN32
208 	return tolower( c1 ) == tolower( c2 );  /* case-insensitive */
209 #else
210 	return          c1   ==          c2  ;  /* case-  sensitive */
211 #endif
212 }
213 
214 /*
215  *  Memory allocation functions
216  */
217 
eMalloc(const size_t size)218 extern void *eMalloc (const size_t size)
219 {
220 	void *buffer = malloc (size);
221 
222 	if (buffer == NULL && size != 0)
223 		error (FATAL, "out of memory");
224 
225 	return buffer;
226 }
227 
eCalloc(const size_t count,const size_t size)228 extern void *eCalloc (const size_t count, const size_t size)
229 {
230 	void *buffer = calloc (count, size);
231 
232 	if (buffer == NULL && count != 0 && size != 0)
233 		error (FATAL, "out of memory");
234 
235 	return buffer;
236 }
237 
eRealloc(void * const ptr,const size_t size)238 extern void *eRealloc (void *const ptr, const size_t size)
239 {
240 	void *buffer;
241 	if (ptr == NULL)
242 		buffer = eMalloc (size);
243 	else
244 	{
245 		buffer = realloc (ptr, size);
246 		if (buffer == NULL && size != 0)
247 			error (FATAL, "out of memory");
248 	}
249 	return buffer;
250 }
251 
eFree(void * const ptr)252 extern void eFree (void *const ptr)
253 {
254 	Assert (ptr != NULL);
255 	free (ptr);
256 }
257 
eFreeNoNullCheck(void * const ptr)258 extern void eFreeNoNullCheck (void *const ptr)
259 {
260 	free (ptr);
261 }
262 
eFreeIndirect(void ** ptr)263 extern void eFreeIndirect(void **ptr)
264 {
265 	if (ptr && *ptr)
266 	{
267 		eFree (*ptr);
268 		*ptr = NULL;
269 	}
270 }
271 
272 /*
273  *  String manipulation functions
274  */
275 
276 /*
277  * Compare two strings, ignoring case.
278  * Return 0 for match, < 0 for smaller, > 0 for bigger
279  * Make sure case is folded to uppercase in comparison (like for 'sort -f')
280  * This makes a difference when one of the chars lies between upper and lower
281  * ie. one of the chars [ \ ] ^ _ ` for ascii. (The '_' in particular !)
282  */
struppercmp(const char * s1,const char * s2)283 extern int struppercmp (const char *s1, const char *s2)
284 {
285 	int result;
286 	do
287 	{
288 		result = toupper ((int) *s1) - toupper ((int) *s2);
289 	} while (result == 0  &&  *s1++ != '\0'  &&  *s2++ != '\0');
290 	return result;
291 }
292 
strnuppercmp(const char * s1,const char * s2,size_t n)293 extern int strnuppercmp (const char *s1, const char *s2, size_t n)
294 {
295 	int result;
296 	do
297 	{
298 		result = toupper ((int) *s1) - toupper ((int) *s2);
299 	} while (result == 0  &&  --n > 0  &&  *s1++ != '\0'  &&  *s2++ != '\0');
300 	return result;
301 }
302 
303 #ifndef HAVE_STRSTR
strstr(const char * str,const char * substr)304 extern char* strstr (const char *str, const char *substr)
305 {
306 	const size_t length = strlen (substr);
307 	const char *p;
308 
309 	for (p = str  ;  *p != '\0'  ;  ++p)
310 		if (strncmp (p, substr, length) == 0)
311 			return (char*) p;
312 	return NULL;
313 }
314 #endif
315 
strrstr(const char * str,const char * substr)316 extern char* strrstr (const char *str, const char *substr)
317 {
318 	const size_t length = strlen (substr);
319 	const char *p;
320 
321 	for (p = str + strlen(str) - length  ;  p >= str  ;  --p)
322 		if (strncmp (p, substr, length) == 0)
323 			return (char*) p;
324 	return NULL;
325 }
326 
eStrdup(const char * str)327 extern char* eStrdup (const char* str)
328 {
329 	char* result = xMalloc (strlen (str) + 1, char);
330 	strcpy (result, str);
331 	return result;
332 }
333 
eStrndup(const char * str,size_t len)334 extern char* eStrndup (const char* str, size_t len)
335 {
336 	char* result = xMalloc (len + 1, char);
337 	strncpy (result, str, len);
338 	result [len] = '\0';
339 	return result;
340 }
341 
toLowerString(char * str)342 extern void toLowerString (char* str)
343 {
344 	while (*str != '\0')
345 	{
346 		*str = tolower ((int) *str);
347 		++str;
348 	}
349 }
350 
toUpperString(char * str)351 extern void toUpperString (char* str)
352 {
353 	while (*str != '\0')
354 	{
355 		*str = toupper ((int) *str);
356 		++str;
357 	}
358 }
359 
360 /*  Newly allocated string containing lower case conversion of a string.
361  */
newLowerString(const char * str)362 extern char* newLowerString (const char* str)
363 {
364 	char* const result = xMalloc (strlen (str) + 1, char);
365 	int i = 0;
366 	do
367 		result [i] = tolower ((int) str [i]);
368 	while (str [i++] != '\0');
369 	return result;
370 }
371 
372 /*  Newly allocated string containing upper case conversion of a string.
373  */
newUpperString(const char * str)374 extern char* newUpperString (const char* str)
375 {
376 	char* const result = xMalloc (strlen (str) + 1, char);
377 	int i = 0;
378 	do
379 		result [i] = toupper ((int) str [i]);
380 	while (str [i++] != '\0');
381 	return result;
382 }
383 
384 /* Safe wrapper for strtoul
385  *
386  * The conversion result is placed in value and must only be used if the
387  * function returned true.
388  */
strToULong(const char * const str,int base,unsigned long * value)389 extern bool strToULong(const char *const str, int base, unsigned long *value)
390 {
391 	char *endptr;
392 
393 	errno = 0;
394 	*value = strtoul (str, &endptr, base);
395 	return *endptr == '\0' && str != endptr && errno == 0;
396 }
397 
398 /* Safe wrapper for strtol/atol
399  *
400  * The conversion result is placed in value and must only be used if the
401  * function returned true.
402  */
strToLong(const char * const str,int base,long * value)403 extern bool strToLong(const char *const str, int base, long *value)
404 {
405 	char *endptr;
406 
407 	errno = 0;
408 	*value = strtol (str, &endptr, base);
409 	return *endptr == '\0' && str != endptr && errno == 0;
410 }
411 
strToUInt(const char * const str,int base,unsigned int * value)412 extern bool strToUInt(const char *const str, int base, unsigned int *value)
413 {
414 	unsigned long ulong_value;
415 
416 	if(!strToULong(str, base, &ulong_value) || ulong_value > UINT_MAX)
417 		return false;
418 
419 	*value = (unsigned int) ulong_value;
420 	return true;
421 }
422 
strToInt(const char * const str,int base,int * value)423 extern bool strToInt(const char *const str, int base, int *value)
424 {
425 	long long_value;
426 
427 	if(!strToLong(str, base, &long_value) || long_value > INT_MAX || long_value < INT_MIN)
428 		return false;
429 
430 	*value = (int) long_value;
431 	return true;
432 }
433 
434 /*
435  * File system functions
436  */
437 
setCurrentDirectory(void)438 extern void setCurrentDirectory (void) /* TODO */
439 {
440 	char* buf;
441 	if (CurrentDirectory == NULL)
442 		CurrentDirectory = xMalloc ((size_t) (PATH_MAX + 1), char);
443 	buf = getcwd (CurrentDirectory, PATH_MAX);
444 	if (buf == NULL)
445 		perror ("");
446 	if (! isPathSeparator (CurrentDirectory [strlen (CurrentDirectory) - (size_t) 1]))
447 	{
448 		sprintf (CurrentDirectory + strlen (CurrentDirectory), "%c",
449 				OUTPUT_PATH_SEPARATOR);
450 	}
451 	canonicalizePath (CurrentDirectory);
452 }
453 
454 /* For caching of stat() calls */
eStat(const char * const fileName)455 extern fileStatus *eStat (const char *const fileName)
456 {
457 	struct stat status;
458 	static fileStatus file;
459 	if (file.name == NULL  ||  strcmp (fileName, file.name) != 0)
460 	{
461 		eStatFree (&file);
462 		file.name = eStrdup (fileName);
463 		if (lstat (file.name, &status) != 0)
464 			file.exists = false;
465 		else
466 		{
467 			file.isSymbolicLink = (bool) S_ISLNK (status.st_mode);
468 			if (file.isSymbolicLink  &&  stat (file.name, &status) != 0)
469 				file.exists = false;
470 			else
471 			{
472 				file.exists = true;
473 				file.isDirectory = (bool) S_ISDIR (status.st_mode);
474 				file.isNormalFile = (bool) (S_ISREG (status.st_mode));
475 				file.isExecutable = (bool) ((status.st_mode &
476 					(S_IXUSR | S_IXGRP | S_IXOTH)) != 0);
477 				file.isSetuid = (bool) ((status.st_mode & S_ISUID) != 0);
478 				file.isSetgid = (bool) ((status.st_mode & S_ISGID) != 0);
479 				file.size = status.st_size;
480 				file.mtime = status.st_mtime;
481 			}
482 		}
483 	}
484 	return &file;
485 }
486 
eStatFree(fileStatus * status)487 extern void eStatFree (fileStatus *status)
488 {
489 	if (status->name != NULL)
490 	{
491 		eFree (status->name);
492 		status->name = NULL;
493 	}
494 }
495 
doesFileExist(const char * const fileName)496 extern bool doesFileExist (const char *const fileName)
497 {
498 	fileStatus *status = eStat (fileName);
499 	return status->exists;
500 }
501 
doesExecutableExist(const char * const fileName)502 extern bool doesExecutableExist (const char *const fileName)
503 {
504 	fileStatus *status = eStat (fileName);
505 	return status->exists && status->isExecutable;
506 }
507 
isRecursiveLink(const char * const dirName)508 extern bool isRecursiveLink (const char* const dirName)
509 {
510 	bool result = false;
511 	fileStatus *status = eStat (dirName);
512 	if (status->isSymbolicLink)
513 	{
514 		char* const path = absoluteFilename (dirName);
515 		while (isPathSeparator (path [strlen (path) - 1]))
516 			path [strlen (path) - 1] = '\0';
517 		while (! result  &&  strlen (path) > (size_t) 1)
518 		{
519 			char *const separator = strRSeparator (path);
520 			if (separator == NULL)
521 				break;
522 			else if (separator == path)  /* backed up to root directory */
523 				*(separator + 1) = '\0';
524 			else
525 				*separator = '\0';
526 			result = isSameFile (path, dirName);
527 		}
528 		eFree (path);
529 	}
530 	return result;
531 }
532 
533 /*
534  *  Pathname manipulation (O/S dependent!!!)
535  */
536 
isPathSeparator(const int c)537 static bool isPathSeparator (const int c)
538 {
539 	bool result;
540 #if defined (MSDOS_STYLE_PATH)
541 	result = (bool) (strchr (PathDelimiters, c) != NULL);
542 #else
543 	result = (bool) (c == PATH_SEPARATOR);
544 #endif
545 	return result;
546 }
547 
strSeparator(const char * s)548 static char *strSeparator (const char *s)
549 {
550 #if defined (MSDOS_STYLE_PATH)
551 	return strpbrk (s, PathDelimiters);
552 #else
553 	return strchr (s, PATH_SEPARATOR);
554 #endif
555 }
556 
strRSeparator(const char * s)557 static char *strRSeparator (const char *s)
558 {
559 #if defined (MSDOS_STYLE_PATH)
560 	const char *last = NULL;
561 
562 	while (( s = strSeparator (s) ))
563 	{
564 		last = s;
565 		s++;
566 	}
567 	return (char*) last;
568 #else
569 	return strrchr (s, PATH_SEPARATOR);
570 #endif
571 }
572 
canonicalizePath(char * const path CTAGS_ATTR_UNUSED)573 static void canonicalizePath (char *const path CTAGS_ATTR_UNUSED)
574 {
575 # if defined (MSDOS_STYLE_PATH)
576 	char *p;
577 	for (p = path  ;  *p != '\0'  ;  ++p)
578 		if (isPathSeparator (*p)  &&  *p != ':')
579 			*p = OUTPUT_PATH_SEPARATOR;
580 # endif
581 }
582 
isSameFile(const char * const name1,const char * const name2)583 extern bool isSameFile (const char *const name1, const char *const name2)
584 {
585 	bool result = false;
586 #if defined (HAVE_STAT_ST_INO)
587 	struct stat stat1, stat2;
588 
589 	if (stat (name1, &stat1) == 0  &&  stat (name2, &stat2) == 0)
590 		result = (bool) (stat1.st_ino == stat2.st_ino);
591 #else
592 	{
593 		char *const n1 = absoluteFilename (name1);
594 		char *const n2 = absoluteFilename (name2);
595 		canonicalizePath (n1);
596 		canonicalizePath (n2);
597 # if defined (CASE_INSENSITIVE_FILENAMES)
598 		result = (bool) (strcasecmp (n1, n2) == 0);
599 # else
600 		result = (bool) (strcmp (n1, n2) == 0);
601 # endif
602 		eFree (n1);
603 		eFree (n2);
604 	}
605 #endif
606 	return result;
607 }
608 
baseFilename(const char * const filePath)609 extern const char *baseFilename (const char *const filePath)
610 {
611 #if defined (MSDOS_STYLE_PATH)
612 	const char *tail = NULL;
613 	unsigned int i;
614 
615 	/*  Find whichever of the path delimiters is last.
616 	 */
617 	for (i = 0  ;  i < strlen (PathDelimiters)  ;  ++i)
618 	{
619 # ifdef HAVE_MBLEN
620 		const char *p;
621 		int ml;
622 
623 		/* Some DBCS has letter contains 0x5C in trailing byte.
624 		 * So skip to the trailing byte. */
625 		for (p = filePath  ;  *p != '\0'  ;  ++p)
626 		{
627 			ml = mblen(p, MB_LEN_MAX);
628 			if (ml > 1)
629 				p += ml - 1;
630 			else if (*p == PathDelimiters [i] && p > tail)
631 				tail = p;
632 		}
633 # else
634 		const char *sep = strrchr (filePath, PathDelimiters [i]);
635 
636 		if (sep > tail)
637 			tail = sep;
638 # endif
639 	}
640 #else
641 	const char *tail = strRSeparator (filePath);
642 #endif
643 	if (tail == NULL)
644 		tail = filePath;
645 	else
646 		++tail;  /* step past last delimiter */
647 
648 	return tail;
649 }
650 
fileExtension(const char * const fileName)651 extern const char *fileExtension (const char *const fileName)
652 {
653 	const char *extension;
654 	const char *pDelimiter;
655 	const char *const base = baseFilename (fileName);
656 
657 	pDelimiter = strrchr (base, '.');
658 
659 	if (pDelimiter == NULL)
660 		extension = "";
661 	else
662 		extension = pDelimiter + 1;  /* skip to first char of extension */
663 
664 	return extension;
665 }
666 
baseFilenameSansExtensionNew(const char * const fileName,const char * const templateExt)667 extern char* baseFilenameSansExtensionNew (const char *const fileName,
668 					   const char *const templateExt)
669 {
670 	const char *pDelimiter;
671 	const char *const base = baseFilename (fileName);
672 	char* shorten_base;
673 
674 	pDelimiter = strrchr (base, templateExt[0]);
675 
676 	if (pDelimiter && (strcmp (pDelimiter, templateExt) == 0))
677 	{
678 		shorten_base = eStrndup (base, pDelimiter - base);
679 		return shorten_base;
680 	}
681 	else
682 		return NULL;
683 }
684 
isAbsolutePath(const char * const path)685 extern bool isAbsolutePath (const char *const path)
686 {
687 	bool result;
688 #if defined (MSDOS_STYLE_PATH)
689 	if (isPathSeparator (path [0]))
690 		result = true;
691 	else if (isalpha (path [0])  &&  path [1] == ':')
692 	{
693 		if (isPathSeparator (path [2]))
694 			result = true;
695 		else
696 		{
697 			result = false;
698 			/*  We don't support non-absolute file names with a drive
699 			 *  letter, like `d:NAME' (it's too much hassle).
700 			 */
701 			error (FATAL,
702 				"%s: relative file names with drive letters not supported",
703 				path);
704 		}
705 	}
706 	else
707 		result = false;
708 #else
709 	result = isPathSeparator (path [0]);
710 #endif
711 	return result;
712 }
713 
combinePathAndFile(const char * const path,const char * const file)714 extern char *combinePathAndFile (
715 	const char *const path, const char *const file)
716 {
717 	vString *const filePath = vStringNew ();
718 	size_t len = strlen (path);
719 
720 	if (len)
721 	{
722 		const int lastChar = path [len - 1];
723 		bool terminated = isPathSeparator (lastChar);
724 		vStringCopyS (filePath, path);
725 		if (! terminated)
726 			vStringPut (filePath, OUTPUT_PATH_SEPARATOR);
727 	}
728 
729 	vStringCatS (filePath, file);
730 	return vStringDeleteUnwrap (filePath);
731 }
732 
733 /* Return a newly-allocated string whose contents concatenate those of
734  * s1, s2, s3.
735  * Routine adapted from Gnu etags.
736  */
concat(const char * s1,const char * s2,const char * s3)737 static char* concat (const char *s1, const char *s2, const char *s3)
738 {
739 	int len1 = strlen (s1), len2 = strlen (s2), len3 = strlen (s3);
740 	char *result = xMalloc (len1 + len2 + len3 + 1, char);
741 
742 	strcpy (result, s1);
743 	strcpy (result + len1, s2);
744 	strcpy (result + len1 + len2, s3);
745 	result [len1 + len2 + len3] = '\0';
746 
747 	return result;
748 }
749 
750 /* Return a newly allocated string containing the absolute file name of FILE
751  * given CWD (which should end with a slash).
752  * Routine adapted from Gnu etags.
753  */
absoluteFilename(const char * file)754 extern char* absoluteFilename (const char *file)
755 {
756 	char *slashp, *cp;
757 	char *res = NULL;
758 	if (isAbsolutePath (file))
759 	{
760 #ifdef MSDOS_STYLE_PATH
761 		if (file [1] == ':')
762 			res = eStrdup (file);
763 		else
764 		{
765 			char drive [3];
766 			sprintf (drive, "%c:", currentdrive ());
767 			res = concat (drive, file, "");
768 		}
769 #else
770 		res = eStrdup (file);
771 #endif
772 	}
773 	else
774 		res = concat (CurrentDirectory, file, "");
775 
776 	/* Delete the "/dirname/.." and "/." substrings. */
777 	slashp = strSeparator (res);
778 	while (slashp != NULL  &&  slashp [0] != '\0')
779 	{
780 		if (slashp[1] == '.')
781 		{
782 			if (slashp [2] == '.' &&
783 				(isPathSeparator (slashp [3]) || slashp [3] == '\0'))
784 			{
785 				cp = slashp;
786 				do
787 					cp--;
788 				while (cp >= res  &&  ! isAbsolutePath (cp));
789 				if (cp < res)
790 					cp = slashp;/* the absolute name begins with "/.." */
791 #ifdef MSDOS_STYLE_PATH
792 				/* Under MSDOS and NT we get `d:/NAME' as absolute file name,
793 				 * so the user could say `d:/../NAME'. We silently treat this
794 				 * as `d:/NAME'.
795 				 */
796 				else if (!isPathSeparator (cp [0]))
797 					cp = slashp;
798 #endif
799 				memmove (cp, slashp + 3, strlen (slashp + 3) + 1);
800 				slashp = cp;
801 				continue;
802 			}
803 			else if (isPathSeparator (slashp [2])  ||  slashp [2] == '\0')
804 			{
805 				memmove (slashp, slashp + 2, strlen (slashp + 2) + 1);
806 				continue;
807 			}
808 		}
809 		slashp = strSeparator (slashp + 1);
810 	}
811 
812 	if (res [0] == '\0')
813 	{
814 		const char root [] = {OUTPUT_PATH_SEPARATOR, '\0'};
815 		eFree (res);
816 		res = eStrdup (root);
817 	}
818 	else
819 	{
820 #ifdef MSDOS_STYLE_PATH
821 		/* Canonicalize drive letter case. */
822 		if (res [1] == ':'  &&  islower (res [0]))
823 			res [0] = toupper (res [0]);
824 #endif
825 	}
826 	canonicalizePath (res);
827 	return res;
828 }
829 
830 /* Return a newly allocated string containing the absolute file name of dir
831  * where `file' resides given `CurrentDirectory'.
832  * Routine adapted from Gnu etags.
833  */
absoluteDirname(char * file)834 extern char* absoluteDirname (char *file)
835 {
836 	char *slashp, *res;
837 	char save;
838 	slashp = strRSeparator (file);
839 	if (slashp == NULL)
840 		res = eStrdup (CurrentDirectory);
841 	else
842 	{
843 		save = slashp [1];
844 		slashp [1] = '\0';
845 		res = absoluteFilename (file);
846 		slashp [1] = save;
847 	}
848 	return res;
849 }
850 
851 /* Return a newly allocated string containing the file name of FILE relative
852  * to the absolute directory DIR (which should end with a slash).
853  * Routine adapted from Gnu etags.
854  */
relativeFilename(const char * file,const char * dir)855 extern char* relativeFilename (const char *file, const char *dir)
856 {
857 	const char *fp, *dp;
858 	char *absdir, *res;
859 	int i;
860 
861 	/* Find the common root of file and dir (with a trailing slash). */
862 	absdir = absoluteFilename (file);
863 	fp = absdir;
864 	dp = dir;
865 	while (fnmChEq (*fp++, *dp++))
866 		continue;
867 	fp--;
868 	dp--;  /* back to the first differing char */
869 	do
870 	{  /* look at the equal chars until path sep */
871 		if (fp == absdir)
872 			return absdir;  /* first char differs, give up */
873 		fp--;
874 		dp--;
875 	} while (!isPathSeparator (*fp));
876 
877 	/* Build a sequence of "../" strings for the resulting relative file name.
878 	 */
879 	i = 0;
880 	while ((dp = strSeparator (dp + 1)) != NULL)
881 		i += 1;
882 	res = xMalloc (3 * i + strlen (fp + 1) + 1, char);
883 	res [0] = '\0';
884 	while (i-- > 0)
885 	{
886 		const char parent [] = {'.', '.', OUTPUT_PATH_SEPARATOR, '\0'};
887 		strcat (res, parent);
888 	}
889 
890 	/* Add the file name relative to the common root of file and dir. */
891 	strcat (res, fp + 1);
892 	eFree (absdir);
893 
894 	return res;
895 }
896 
tempFileFP(const char * const mode,char ** const pName)897 extern FILE *tempFileFP (const char *const mode, char **const pName)
898 {
899 	char *name;
900 	FILE *fp;
901 	int fd;
902 #if defined(HAVE_MKSTEMP)
903 	const char *const pattern = "tags.XXXXXX";
904 	const char *tmpdir = NULL;
905 	fileStatus *file = eStat (ExecutableProgram);
906 # ifdef WIN32
907 	tmpdir = getenv ("TMP");
908 # else
909 	if (! file->isSetuid)
910 		tmpdir = getenv ("TMPDIR");
911 # endif
912 	if (tmpdir == NULL)
913 		tmpdir = TMPDIR;
914 	name = xMalloc (strlen (tmpdir) + 1 + strlen (pattern) + 1, char);
915 	sprintf (name, "%s%c%s", tmpdir, OUTPUT_PATH_SEPARATOR, pattern);
916 	fd = mkstemp (name);
917 # ifdef WIN32
918 	if (fd == -1)
919 	{
920 		/* mkstemp() sometimes fails with unknown reasons.
921 		 * Retry a few times. */
922 		int i;
923 		for (i = 0; i < 5 && fd == -1; i++)
924 		{
925 			sprintf (name, "%s%c%s", tmpdir, OUTPUT_PATH_SEPARATOR, pattern);
926 			fd = mkstemp (name);
927 		}
928 	}
929 # endif
930 	eStatFree (file);
931 #elif defined(HAVE_TEMPNAM)
932 	const char *tmpdir = NULL;
933 # ifdef WIN32
934 	tmpdir = getenv ("TMP");
935 # endif
936 	if (tmpdir == NULL)
937 		tmpdir = TMPDIR;
938 	name = tempnam (tmpdir, "tags");
939 	if (name == NULL)
940 		error (FATAL | PERROR, "cannot allocate temporary file name");
941 	fd = open (name, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
942 #else
943 	name = xMalloc (L_tmpnam, char);
944 	if (tmpnam (name) != name)
945 		error (FATAL | PERROR, "cannot assign temporary file name");
946 	fd = open (name, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
947 #endif
948 	if (fd == -1)
949 		error (FATAL | PERROR, "cannot open temporary file: %s", name);
950 	fp = fdopen (fd, mode);
951 	if (fp == NULL)
952 		error (FATAL | PERROR, "cannot open temporary file");
953 	Assert (*pName == NULL);
954 	*pName = name;
955 	return fp;
956 }
957 
tempFile(const char * const mode,char ** const pName)958 extern MIO *tempFile (const char *const mode, char **const pName)
959 {
960 	FILE *fp = tempFileFP (mode, pName);
961 	return mio_new_fp (fp, fclose);
962 }
963