Added Lesstif-specific suppression of harmless but annoying warnings
[nedit.git] / util / fileUtils.c
blobda8bc19e613fd706eda370697c650b0561b535c5
1 static const char CVSID[] = "$Id: fileUtils.c,v 1.31 2003/11/18 12:16:12 edg Exp $";
2 /*******************************************************************************
3 * *
4 * fileUtils.c -- File utilities for Nirvana applications *
5 * *
6 * Copyright (C) 1999 Mark Edel *
7 * *
8 * This is free software; you can redistribute it and/or modify it under the *
9 * terms of the GNU General Public License as published by the Free Software *
10 * Foundation; either version 2 of the License, or (at your option) any later *
11 * version. *
12 * *
13 * This software is distributed in the hope that it will be useful, but WITHOUT *
14 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
15 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License *
16 * for more details. *
17 * *
18 * You should have received a copy of the GNU General Public License along with *
19 * software; if not, write to the Free Software Foundation, Inc., 59 Temple *
20 * Place, Suite 330, Boston, MA 02111-1307 USA *
21 * *
22 * Nirvana Text Editor *
23 * July 28, 1992 *
24 * *
25 * Written by Mark Edel *
26 * *
27 * Modified by: DMR - Ported to VMS (1st stage for Histo-Scope) *
28 * *
29 *******************************************************************************/
31 #ifdef HAVE_CONFIG_H
32 #include "../config.h"
33 #endif
35 #include "fileUtils.h"
36 #include "utils.h"
38 #include <stdlib.h>
39 #include <stdio.h>
40 #include <string.h>
41 #include <errno.h>
42 #include <X11/Intrinsic.h>
43 #ifdef VAXC
44 #define NULL (void *) 0
45 #endif /*VAXC*/
46 #ifdef VMS
47 #include "vmsparam.h"
48 #include <stat.h>
49 #else
50 #include <sys/types.h>
51 #ifndef __MVS__
52 #include <sys/param.h>
53 #endif
54 #include <sys/stat.h>
55 #include <unistd.h>
56 #include <pwd.h>
57 #endif /*VMS*/
59 #ifdef HAVE_DEBUG_H
60 #include "../debug.h"
61 #endif
63 #ifndef MAXSYMLINKS /* should be defined in <sys/param.h> */
64 #define MAXSYMLINKS 20
65 #endif
67 #define TRUE 1
68 #define FALSE 0
70 /* Parameters to algorithm used to auto-detect DOS format files. NEdit will
71 scan up to the lesser of FORMAT_SAMPLE_LINES lines and FORMAT_SAMPLE_CHARS
72 characters of the beginning of the file, checking that all newlines are
73 paired with carriage returns. If even a single counterexample exists,
74 the file is judged to be in Unix format. */
75 #define FORMAT_SAMPLE_LINES 5
76 #define FORMAT_SAMPLE_CHARS 2000
78 static char *nextSlash(char *ptr);
79 static char *prevSlash(char *ptr);
80 static int compareThruSlash(const char *string1, const char *string2);
81 static void copyThruSlash(char **toString, char **fromString);
84 ** Decompose a Unix file name into a file name and a path.
85 ** Return non-zero value if it fails, zero else.
86 ** For now we assume that filename and pathname are at
87 ** least MAXPATHLEN chars long.
88 ** To skip setting filename or pathname pass NULL for that argument.
90 int
91 ParseFilename(const char *fullname, char *filename, char *pathname)
93 int fullLen = strlen(fullname);
94 int i, pathLen, fileLen;
96 #ifdef VMS
97 /* find the last ] or : */
98 for (i=fullLen-1; i>=0; i--) {
99 if (fullname[i] == ']' || fullname[i] == ':')
100 break;
102 #else /* UNIX */
103 char *viewExtendPath;
104 int scanStart;
106 /* For clearcase version extended paths, slash characters after the "@@/"
107 should be considered part of the file name, rather than the path */
108 if ((viewExtendPath = strstr(fullname, "@@/")) != NULL)
109 scanStart = viewExtendPath - fullname - 1;
110 else
111 scanStart = fullLen - 1;
113 /* find the last slash */
114 for (i=scanStart; i>=0; i--) {
115 if (fullname[i] == '/')
116 break;
118 #endif
120 /* move chars before / (or ] or :) into pathname,& after into filename */
121 pathLen = i + 1;
122 fileLen = fullLen - pathLen;
123 if (pathname) {
124 if (pathLen >= MAXPATHLEN) {
125 return 1;
127 strncpy(pathname, fullname, pathLen);
128 pathname[pathLen] = 0;
130 if (filename) {
131 if (fileLen >= MAXPATHLEN) {
132 return 2;
134 strncpy(filename, &fullname[pathLen], fileLen);
135 filename[fileLen] = 0;
138 #ifndef VMS /* UNIX specific... Modify at a later date for VMS */
139 if(pathname) {
140 if (NormalizePathname(pathname)) {
141 return 1; /* pathname too long */
143 pathLen = strlen(pathname);
145 #endif
147 if (filename && pathname && (pathLen + 1 + fileLen) >= MAXPATHLEN) {
148 return 1; /* pathname + filename too long */
150 return 0;
153 #ifndef VMS
157 ** Expand tilde characters which begin file names as done by the shell
158 ** If it doesn't work just out leave pathname unmodified.
159 ** This implementation is neither fast, nor elegant, nor ...
162 ExpandTilde(char *pathname)
164 struct passwd *passwdEntry;
165 char username[MAXPATHLEN], temp[MAXPATHLEN];
166 char *nameEnd;
167 unsigned len_left;
169 if (pathname[0] != '~')
170 return TRUE;
171 nameEnd = strchr(&pathname[1], '/');
172 if (nameEnd == NULL) {
173 nameEnd = pathname + strlen(pathname);
175 strncpy(username, &pathname[1], nameEnd - &pathname[1]);
176 username[nameEnd - &pathname[1]] = '\0';
177 /* We might consider to re-use the GetHomeDir() function,
178 but to keep the code more similar for both cases ... */
179 if (username[0] == '\0') {
180 passwdEntry = getpwuid(getuid());
181 if ((passwdEntry == NULL) || (*(passwdEntry->pw_dir)== '\0')) {
182 /* This is really serious, so just exit. */
183 perror("NEdit/nc: getpwuid() failed ");
184 exit(EXIT_FAILURE);
187 else {
188 passwdEntry = getpwnam(username);
189 if ((passwdEntry == NULL) || (*(passwdEntry->pw_dir)== '\0')) {
190 /* username was just an input by the user, this is no
191 indication for some (serious) problems */
192 return FALSE;
196 strcpy(temp, passwdEntry->pw_dir);
197 strcat(temp, "/");
198 len_left= sizeof(temp)-strlen(temp)-1;
199 if (len_left < strlen(nameEnd)) {
200 /* It won't work out */
201 return FALSE;
203 strcat(temp, nameEnd);
204 strcpy(pathname, temp);
205 return TRUE;
209 * Resolve symbolic links (if any) for the absolute path given in pathIn
210 * and place the resolved absolute path in pathResolved.
211 * - pathIn must contain an absolute path spec.
212 * - pathResolved must point to a buffer of minimum size MAXPATHLEN.
214 * Returns:
215 * TRUE if pathResolved contains a valid resolved path
216 * OR pathIn is not a symlink (pathResolved will have the same
217 * contents like pathIn)
219 * FALSE an error occured while trying to resolve the symlink, i.e.
220 * pathIn was no absolute path or the link is a loop.
223 ResolvePath(const char * pathIn, char * pathResolved)
225 char resolveBuf[MAXPATHLEN], pathBuf[MAXPATHLEN];
226 char *pathEnd;
227 int rlResult, loops;
229 #ifdef NO_READLINK
230 strncpy(pathResolved, pathIn, MAXPATHLEN);
231 /* If there are no links at all, it's a valid "resolved" path */
232 return TRUE;
233 #else
234 /* !! readlink does NOT recognize loops, i.e. links like file -> ./file */
235 for(loops=0; loops<MAXSYMLINKS; loops++) {
236 #ifdef UNICOS
237 rlResult=readlink((char *)pathIn, resolveBuf, MAXPATHLEN-1);
238 #else
239 rlResult=readlink(pathIn, resolveBuf, MAXPATHLEN-1);
240 #endif
241 if(rlResult<0) {
243 #ifndef __Lynx__
244 if (errno == EINVAL)
245 #else
246 if (errno == ENXIO)
247 #endif
249 /* It's not a symlink - we are done */
250 strncpy(pathResolved, pathIn, MAXPATHLEN);
251 pathResolved[MAXPATHLEN-1] = '\0';
252 return TRUE;
253 } else
255 return FALSE;
257 } else if (rlResult==0) {
258 return FALSE;
261 resolveBuf[rlResult]=0;
263 if(resolveBuf[0]!='/') {
264 strncpy(pathBuf, pathIn, MAXPATHLEN);
265 pathBuf[MAXPATHLEN-1] = '\0';
266 pathEnd=strrchr(pathBuf, '/');
267 if(!pathEnd)
268 return FALSE;
269 strcpy(pathEnd+1, resolveBuf);
270 } else {
271 strcpy(pathBuf, resolveBuf);
273 NormalizePathname(pathBuf);
274 pathIn=pathBuf;
276 return FALSE;
277 #endif /* NO_READLINK */
282 ** Return 0 if everything's fine. In fact it always return 0 ...
283 ** Capable to handle arbitrary path length (>MAXPATHLEN)!
286 NormalizePathname(char *pathname)
288 /* if this is a relative pathname, prepend current directory */
289 #ifdef __EMX__
290 /* OS/2, ...: welcome to the world of drive letters ... */
291 if (!_fnisabs(pathname)) {
292 #else
293 if (pathname[0] != '/') {
294 #endif
295 char *oldPathname;
296 size_t len;
298 /* make a copy of pathname to work from */
299 oldPathname=(char *)malloc(strlen(pathname)+1);
300 strcpy(oldPathname, pathname);
301 /* get the working directory and prepend to the path */
302 strcpy(pathname, GetCurrentDir());
303 /* check for trailing slash, or pathname being root dir "/":
304 don't add a second '/' character as this may break things
305 on non-un*x systems */
306 len=strlen(pathname); /* GetCurrentDir() returns non-NULL value */
307 if ( len==0 ? 1 : pathname[len-1] != '/' ) {
308 strcat(pathname, "/");
310 strcat(pathname, oldPathname);
311 free(oldPathname);
314 /* compress out .. and . */
315 return CompressPathname(pathname);
320 ** Return 0 if everything's fine, 1 else.
323 CompressPathname(char *pathname)
325 char *buf, *inPtr, *outPtr;
326 struct stat statbuf;
328 /* (Added by schwarzenberg)
329 ** replace multiple slashes by a single slash
331 inPtr=pathname;
332 buf=(char *)malloc(strlen(pathname)+2);
333 outPtr=buf;
334 while (*inPtr) {
335 *outPtr=*inPtr++;
336 if(*outPtr=='/') {
337 while(*inPtr=='/')
338 inPtr++;
340 outPtr++;
342 *outPtr=0;
343 strcpy(pathname, buf);
345 /* compress out . and .. */
346 inPtr = pathname;
347 outPtr = buf;
348 /* copy initial / */
349 copyThruSlash(&outPtr, &inPtr);
350 while (inPtr != NULL) {
351 /* if the next component is "../", remove previous component */
352 if (compareThruSlash(inPtr, "../")) {
353 *outPtr = 0;
354 /* If the ../ is at the beginning, or if the previous component
355 is a symbolic link, preserve the ../. It is not valid to
356 compress ../ when the previous component is a symbolic link
357 because ../ is relative to where the link points. If there's
358 no S_ISLNK macro, assume system does not do symbolic links. */
359 #ifdef S_ISLNK
360 if(outPtr-1 == buf || (lstat(buf, &statbuf) == 0 &&
361 S_ISLNK(statbuf.st_mode))) {
362 copyThruSlash(&outPtr, &inPtr);
363 } else
364 #endif
366 /* back up outPtr to remove last path name component */
367 outPtr = prevSlash(outPtr);
368 inPtr = nextSlash(inPtr);
370 } else if (compareThruSlash(inPtr, "./")) {
371 /* don't copy the component if it's the redundant "./" */
372 inPtr = nextSlash(inPtr);
373 } else {
374 /* copy the component to outPtr */
375 copyThruSlash(&outPtr, &inPtr);
378 /* updated pathname with the new value */
379 if (strlen(buf)>MAXPATHLEN) {
380 fprintf(stderr, "NEdit: CompressPathname(): file name too long %s\n",
381 pathname);
382 free(buf);
383 return 1;
385 else {
386 strcpy(pathname, buf);
387 free(buf);
388 return 0;
392 static char
393 *nextSlash(char *ptr)
395 for(; *ptr!='/'; ptr++) {
396 if (*ptr == '\0')
397 return NULL;
399 return ptr + 1;
402 static char
403 *prevSlash(char *ptr)
405 for(ptr -= 2; *ptr!='/'; ptr--);
406 return ptr + 1;
409 static int
410 compareThruSlash(const char *string1, const char *string2)
412 while (TRUE) {
413 if (*string1 != *string2)
414 return FALSE;
415 if (*string1 =='\0' || *string1=='/')
416 return TRUE;
417 string1++;
418 string2++;
422 static void
423 copyThruSlash(char **toString, char **fromString)
425 char *to = *toString;
426 char *from = *fromString;
428 while (TRUE) {
429 *to = *from;
430 if (*from =='\0') {
431 *fromString = NULL;
432 return;
434 if (*from=='/') {
435 *toString = to + 1;
436 *fromString = from + 1;
437 return;
439 from++;
440 to++;
444 #else /* VMS */
447 ** Dummy versions of the public functions for VMS.
451 ** Return 0 if everything's fine, 1 else.
453 int NormalizePathname(char *pathname)
455 return 0;
459 ** Return 0 if everything's fine, 1 else.
461 int CompressPathname(char *pathname)
463 return 0;
467 * Returns:
468 * TRUE if no error occured
470 * FALSE if an error occured.
472 int ResolvePath(const char * pathIn, char * pathResolved)
474 if (strlen(pathIn) < MAXPATHLEN) {
475 strcpy(pathResolved, pathIn);
476 return TRUE;
477 } else {
478 return FALSE;
482 #endif /* VMS */
485 ** Return the trailing 'n' no. of path components
487 const char
488 *GetTrailingPathComponents(const char* path,
489 int noOfComponents)
491 /* Start from the rear */
492 const char* ptr = path + strlen(path);
493 int count = 0;
495 while (--ptr > path) {
496 if (*ptr == '/') {
497 if (count++ == noOfComponents) {
498 break;
502 return(ptr);
506 ** Samples up to a maximum of FORMAT_SAMPLE_LINES lines and FORMAT_SAMPLE_CHARS
507 ** characters, to determine whether fileString represents a MS DOS or Macintosh
508 ** format file. If there's ANY ambiguity (a newline in the sample not paired
509 ** with a return in an otherwise DOS looking file, or a newline appearing in
510 ** the sampled portion of a Macintosh looking file), the file is judged to be
511 ** Unix format.
513 int FormatOfFile(const char *fileString)
515 const char *p;
516 int nNewlines = 0, nReturns = 0;
518 for (p=fileString; *p!='\0' && p < fileString + FORMAT_SAMPLE_CHARS; p++) {
519 if (*p == '\n') {
520 nNewlines++;
521 if (p == fileString || *(p-1) != '\r')
522 return UNIX_FILE_FORMAT;
523 if (nNewlines >= FORMAT_SAMPLE_LINES)
524 return DOS_FILE_FORMAT;
525 } else if (*p == '\r')
526 nReturns++;
528 if (nNewlines > 0)
529 return DOS_FILE_FORMAT;
530 if (nReturns > 0)
531 return MAC_FILE_FORMAT;
532 return UNIX_FILE_FORMAT;
536 ** Converts a string (which may represent the entire contents of the file)
537 ** from DOS or Macintosh format to Unix format. Conversion is done in-place.
538 ** In the DOS case, the length will be shorter, and passed length will be
539 ** modified to reflect the new length. The routine has support for blockwise
540 ** file to string conversion: if the fileString has a trailing '\r' and
541 ** 'pendingCR' is not zero, the '\r' is deposited in there and is not
542 ** converted. If there is no trailing '\r', a 0 is deposited in 'pendingCR'
543 ** It's the caller's responsability to make sure that the pending character,
544 ** if present, is inserted at the beginning of the next block to convert.
546 void ConvertFromDosFileString(char *fileString, int *length,
547 char* pendingCR)
549 char *outPtr = fileString;
550 char *inPtr = fileString;
551 if (pendingCR) *pendingCR = 0;
552 while (inPtr < fileString + *length) {
553 if (*inPtr == '\r') {
554 if (inPtr < fileString + *length - 1) {
555 if (*(inPtr + 1) == '\n')
556 inPtr++;
557 } else {
558 if (pendingCR) {
559 *pendingCR = *inPtr;
560 break; /* Don't copy this trailing '\r' */
564 *outPtr++ = *inPtr++;
566 *outPtr = '\0';
567 *length = outPtr - fileString;
569 void ConvertFromMacFileString(char *fileString, int length)
571 char *inPtr = fileString;
572 while (inPtr < fileString + length) {
573 if (*inPtr == '\r' )
574 *inPtr = '\n';
575 inPtr++;
580 ** Converts a string (which may represent the entire contents of the file) from
581 ** Unix to DOS format. String is re-allocated (with malloc), and length is
582 ** modified. If allocation fails, which it may, because this can potentially
583 ** be a huge hunk of memory, returns FALSE and no conversion is done.
585 ** This could be done more efficiently by asking doSave to allocate some
586 ** extra memory for this, and only re-allocating if it wasn't enough. If
587 ** anyone cares about the performance or the potential for running out of
588 ** memory on a save, it should probably be redone.
590 int ConvertToDosFileString(char **fileString, int *length)
592 char *outPtr, *outString;
593 char *inPtr = *fileString;
594 int inLength = *length;
595 int outLength = 0;
597 /* How long a string will we need? */
598 while (inPtr < *fileString + inLength) {
599 if (*inPtr == '\n')
600 outLength++;
601 inPtr++;
602 outLength++;
605 /* Allocate the new string */
606 outString = XtMalloc(outLength + 1);
607 if (outString == NULL)
608 return FALSE;
610 /* Do the conversion, free the old string */
611 inPtr = *fileString;
612 outPtr = outString;
613 while (inPtr < *fileString + inLength) {
614 if (*inPtr == '\n')
615 *outPtr++ = '\r';
616 *outPtr++ = *inPtr++;
618 *outPtr = '\0';
619 XtFree(*fileString);
620 *fileString = outString;
621 *length = outLength;
622 return TRUE;
626 ** Converts a string (which may represent the entire contents of the file)
627 ** from Unix to Macintosh format.
629 void ConvertToMacFileString(char *fileString, int length)
631 char *inPtr = fileString;
633 while (inPtr < fileString + length) {
634 if (*inPtr == '\n' )
635 *inPtr = '\r';
636 inPtr++;
641 ** Reads a text file into a string buffer, converting line breaks to
642 ** unix-style if appropriate.
644 char *ReadAnyTextFile(const char *fileName)
646 struct stat statbuf;
647 FILE *fp;
648 int fileLen, readLen;
649 char *fileString;
650 int format;
652 /* Read the whole file into fileString */
653 if ((fp = fopen(fileName, "r")) == NULL) {
654 return NULL;
656 if (fstat(fileno(fp), &statbuf) != 0) {
657 fclose(fp);
658 return NULL;
660 fileLen = statbuf.st_size;
661 fileString = XtMalloc(fileLen+1); /* +1 = space for null */
662 readLen = fread(fileString, sizeof(char), fileLen, fp);
663 if (ferror(fp)) {
664 XtFree(fileString);
665 fclose(fp);
666 return NULL;
668 fclose(fp);
669 fileString[readLen] = 0;
671 /* Convert linebreaks? */
672 format = FormatOfFile(fileString);
673 if (format == DOS_FILE_FORMAT){
674 char pendingCR;
675 ConvertFromDosFileString(fileString, &readLen, &pendingCR);
676 } else if (format == MAC_FILE_FORMAT){
677 ConvertFromMacFileString(fileString, readLen);
680 return fileString;