Xft support under OpenMotif 2.3.3 - I've been using this for quite a while on
[nedit.git] / util / fileUtils.c
blob4a4be43bee7618e6d82ab3de0cd1f7348af3c9f8
1 static const char CVSID[] = "$Id: fileUtils.c,v 1.35 2008/08/20 14:57:35 lebert 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. In addition, you may distribute versions of this program linked to *
12 * Motif or Open Motif. See README for details. *
13 * *
14 * This software is distributed in the hope that it will be useful, but WITHOUT *
15 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
16 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License *
17 * for more details. *
18 * *
19 * You should have received a copy of the GNU General Public License along with *
20 * software; if not, write to the Free Software Foundation, Inc., 59 Temple *
21 * Place, Suite 330, Boston, MA 02111-1307 USA *
22 * *
23 * Nirvana Text Editor *
24 * July 28, 1992 *
25 * *
26 * Written by Mark Edel *
27 * *
28 * Modified by: DMR - Ported to VMS (1st stage for Histo-Scope) *
29 * *
30 *******************************************************************************/
32 #ifdef HAVE_CONFIG_H
33 #include "../config.h"
34 #endif
36 #include "fileUtils.h"
37 #include "utils.h"
39 #include <stdlib.h>
40 #include <stdio.h>
41 #include <string.h>
42 #include <errno.h>
43 #include <X11/Intrinsic.h>
44 #ifdef VAXC
45 #define NULL (void *) 0
46 #endif /*VAXC*/
47 #ifdef VMS
48 #include "vmsparam.h"
49 #include <stat.h>
50 #else
51 #include <sys/types.h>
52 #ifndef __MVS__
53 #include <sys/param.h>
54 #endif
55 #include <sys/stat.h>
56 #include <unistd.h>
57 #include <pwd.h>
58 #endif /*VMS*/
60 #ifdef HAVE_DEBUG_H
61 #include "../debug.h"
62 #endif
64 #ifndef MAXSYMLINKS /* should be defined in <sys/param.h> */
65 #define MAXSYMLINKS 20
66 #endif
68 #define TRUE 1
69 #define FALSE 0
71 /* Parameters to algorithm used to auto-detect DOS format files. NEdit will
72 scan up to the lesser of FORMAT_SAMPLE_LINES lines and FORMAT_SAMPLE_CHARS
73 characters of the beginning of the file, checking that all newlines are
74 paired with carriage returns. If even a single counterexample exists,
75 the file is judged to be in Unix format. */
76 #define FORMAT_SAMPLE_LINES 5
77 #define FORMAT_SAMPLE_CHARS 2000
79 static char *nextSlash(char *ptr);
80 static char *prevSlash(char *ptr);
81 static int compareThruSlash(const char *string1, const char *string2);
82 static void copyThruSlash(char **toString, char **fromString);
85 ** Decompose a Unix file name into a file name and a path.
86 ** Return non-zero value if it fails, zero else.
87 ** For now we assume that filename and pathname are at
88 ** least MAXPATHLEN chars long.
89 ** To skip setting filename or pathname pass NULL for that argument.
91 int
92 ParseFilename(const char *fullname, char *filename, char *pathname)
94 int fullLen = strlen(fullname);
95 int i, pathLen, fileLen;
97 #ifdef VMS
98 /* find the last ] or : */
99 for (i=fullLen-1; i>=0; i--) {
100 if (fullname[i] == ']' || fullname[i] == ':')
101 break;
103 #else /* UNIX */
104 char *viewExtendPath;
105 int scanStart;
107 /* For clearcase version extended paths, slash characters after the "@@/"
108 should be considered part of the file name, rather than the path */
109 if ((viewExtendPath = strstr(fullname, "@@/")) != NULL)
110 scanStart = viewExtendPath - fullname - 1;
111 else
112 scanStart = fullLen - 1;
114 /* find the last slash */
115 for (i=scanStart; i>=0; i--) {
116 if (fullname[i] == '/')
117 break;
119 #endif
121 /* move chars before / (or ] or :) into pathname,& after into filename */
122 pathLen = i + 1;
123 fileLen = fullLen - pathLen;
124 if (pathname) {
125 if (pathLen >= MAXPATHLEN) {
126 return 1;
128 strncpy(pathname, fullname, pathLen);
129 pathname[pathLen] = 0;
131 if (filename) {
132 if (fileLen >= MAXPATHLEN) {
133 return 2;
135 strncpy(filename, &fullname[pathLen], fileLen);
136 filename[fileLen] = 0;
139 #ifndef VMS /* UNIX specific... Modify at a later date for VMS */
140 if(pathname) {
141 if (NormalizePathname(pathname)) {
142 return 1; /* pathname too long */
144 pathLen = strlen(pathname);
146 #endif
148 if (filename && pathname && ((pathLen + 1 + fileLen) >= MAXPATHLEN)) {
149 return 1; /* pathname + filename too long */
151 return 0;
154 #ifndef VMS
158 ** Expand tilde characters which begin file names as done by the shell
159 ** If it doesn't work just out leave pathname unmodified.
160 ** This implementation is neither fast, nor elegant, nor ...
163 ExpandTilde(char *pathname)
165 struct passwd *passwdEntry;
166 char username[MAXPATHLEN], temp[MAXPATHLEN];
167 char *nameEnd;
168 unsigned len_left;
170 if (pathname[0] != '~')
171 return TRUE;
172 nameEnd = strchr(&pathname[1], '/');
173 if (nameEnd == NULL) {
174 nameEnd = pathname + strlen(pathname);
176 strncpy(username, &pathname[1], nameEnd - &pathname[1]);
177 username[nameEnd - &pathname[1]] = '\0';
178 /* We might consider to re-use the GetHomeDir() function,
179 but to keep the code more similar for both cases ... */
180 if (username[0] == '\0') {
181 passwdEntry = getpwuid(getuid());
182 if ((passwdEntry == NULL) || (*(passwdEntry->pw_dir)== '\0')) {
183 /* This is really serious, so just exit. */
184 perror("NEdit/nc: getpwuid() failed ");
185 exit(EXIT_FAILURE);
188 else {
189 passwdEntry = getpwnam(username);
190 if ((passwdEntry == NULL) || (*(passwdEntry->pw_dir)== '\0')) {
191 /* username was just an input by the user, this is no
192 indication for some (serious) problems */
193 return FALSE;
197 strcpy(temp, passwdEntry->pw_dir);
198 strcat(temp, "/");
199 len_left= sizeof(temp)-strlen(temp)-1;
200 if (len_left < strlen(nameEnd)) {
201 /* It won't work out */
202 return FALSE;
204 strcat(temp, nameEnd);
205 strcpy(pathname, temp);
206 return TRUE;
210 * Resolve symbolic links (if any) for the absolute path given in pathIn
211 * and place the resolved absolute path in pathResolved.
212 * - pathIn must contain an absolute path spec.
213 * - pathResolved must point to a buffer of minimum size MAXPATHLEN.
215 * Returns:
216 * TRUE if pathResolved contains a valid resolved path
217 * OR pathIn is not a symlink (pathResolved will have the same
218 * contents like pathIn)
220 * FALSE an error occured while trying to resolve the symlink, i.e.
221 * pathIn was no absolute path or the link is a loop.
224 ResolvePath(const char * pathIn, char * pathResolved)
226 char resolveBuf[MAXPATHLEN], pathBuf[MAXPATHLEN];
227 char *pathEnd;
228 int rlResult, loops;
230 #ifdef NO_READLINK
231 strncpy(pathResolved, pathIn, MAXPATHLEN);
232 /* If there are no links at all, it's a valid "resolved" path */
233 return TRUE;
234 #else
235 /* !! readlink does NOT recognize loops, i.e. links like file -> ./file */
236 for (loops=0; loops<MAXSYMLINKS; loops++) {
237 #ifdef UNICOS
238 rlResult=readlink((char *)pathIn, resolveBuf, MAXPATHLEN-1);
239 #else
240 rlResult=readlink(pathIn, resolveBuf, MAXPATHLEN-1);
241 #endif
242 if (rlResult<0) {
244 #ifndef __Lynx__
245 if (errno == EINVAL)
246 #else
247 if (errno == ENXIO)
248 #endif
250 /* It's not a symlink - we are done */
251 strncpy(pathResolved, pathIn, MAXPATHLEN);
252 pathResolved[MAXPATHLEN-1] = '\0';
253 return TRUE;
254 } 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;
270 strcpy(pathEnd+1, resolveBuf);
271 } else {
272 strcpy(pathBuf, resolveBuf);
274 NormalizePathname(pathBuf);
275 pathIn=pathBuf;
278 return FALSE;
279 #endif /* NO_READLINK */
284 ** Return 0 if everything's fine. In fact it always return 0... (No it doesn't)
285 ** Capable to handle arbitrary path length (>MAXPATHLEN)!
287 ** FIXME: Documentation
288 ** FIXME: Change return value to False and True.
290 int NormalizePathname(char *pathname)
292 /* if this is a relative pathname, prepend current directory */
293 #ifdef __EMX__
294 /* OS/2, ...: welcome to the world of drive letters ... */
295 if (!_fnisabs(pathname)) {
296 #else
297 if (pathname[0] != '/') {
298 #endif
299 char *oldPathname;
300 size_t len;
302 /* make a copy of pathname to work from */
303 oldPathname=(char *)malloc(strlen(pathname)+1);
304 strcpy(oldPathname, pathname);
305 /* get the working directory and prepend to the path */
306 strcpy(pathname, GetCurrentDir());
308 /* check for trailing slash, or pathname being root dir "/":
309 don't add a second '/' character as this may break things
310 on non-un*x systems */
311 len = strlen(pathname); /* GetCurrentDir() returns non-NULL value */
313 /* Apart from the fact that people putting conditional expressions in
314 ifs should be caned: How should len ever become 0 if GetCurrentDir()
315 always returns a useful value?
316 FIXME: Check and document GetCurrentDir() return behaviour. */
317 if (0 == len ? 1 : pathname[len-1] != '/')
319 strcat(pathname, "/");
321 strcat(pathname, oldPathname);
322 free(oldPathname);
325 /* compress out .. and . */
326 return CompressPathname(pathname);
331 ** Return 0 if everything's fine, 1 else.
333 ** FIXME: Documentation
334 ** FIXME: Change return value to False and True.
336 int CompressPathname(char *pathname)
338 char *buf, *inPtr, *outPtr;
339 struct stat statbuf;
341 /* (Added by schwarzenberg)
342 ** replace multiple slashes by a single slash
343 ** (added by yooden)
344 ** Except for the first slash. From the Single UNIX Spec: "A pathname
345 ** that begins with two successive slashes may be interpreted in an
346 ** implementation-dependent manner"
348 inPtr = pathname;
349 buf = (char*) malloc(strlen(pathname) + 2);
350 outPtr = buf;
351 *outPtr++ = *inPtr++;
352 while (*inPtr)
354 *outPtr = *inPtr++;
355 if ('/' == *outPtr)
357 while ('/' == *inPtr)
359 inPtr++;
362 outPtr++;
364 *outPtr=0;
365 strcpy(pathname, buf);
367 /* compress out . and .. */
368 inPtr = pathname;
369 outPtr = buf;
370 /* copy initial / */
371 copyThruSlash(&outPtr, &inPtr);
372 while (inPtr != NULL) {
373 /* if the next component is "../", remove previous component */
374 if (compareThruSlash(inPtr, "../")) {
375 *outPtr = 0;
376 /* If the ../ is at the beginning, or if the previous component
377 is a symbolic link, preserve the ../. It is not valid to
378 compress ../ when the previous component is a symbolic link
379 because ../ is relative to where the link points. If there's
380 no S_ISLNK macro, assume system does not do symbolic links. */
381 #ifdef S_ISLNK
382 if(outPtr-1 == buf || (lstat(buf, &statbuf) == 0 &&
383 S_ISLNK(statbuf.st_mode))) {
384 copyThruSlash(&outPtr, &inPtr);
385 } else
386 #endif
388 /* back up outPtr to remove last path name component */
389 outPtr = prevSlash(outPtr);
390 inPtr = nextSlash(inPtr);
392 } else if (compareThruSlash(inPtr, "./")) {
393 /* don't copy the component if it's the redundant "./" */
394 inPtr = nextSlash(inPtr);
395 } else {
396 /* copy the component to outPtr */
397 copyThruSlash(&outPtr, &inPtr);
400 /* updated pathname with the new value */
401 if (strlen(buf)>MAXPATHLEN) {
402 fprintf(stderr, "nedit: CompressPathname(): file name too long %s\n",
403 pathname);
404 free(buf);
405 return 1;
407 else {
408 strcpy(pathname, buf);
409 free(buf);
410 return 0;
414 static char
415 *nextSlash(char *ptr)
417 for(; *ptr!='/'; ptr++) {
418 if (*ptr == '\0')
419 return NULL;
421 return ptr + 1;
424 static char
425 *prevSlash(char *ptr)
427 for(ptr -= 2; *ptr!='/'; ptr--);
428 return ptr + 1;
431 static int
432 compareThruSlash(const char *string1, const char *string2)
434 while (TRUE) {
435 if (*string1 != *string2)
436 return FALSE;
437 if (*string1 =='\0' || *string1=='/')
438 return TRUE;
439 string1++;
440 string2++;
444 static void
445 copyThruSlash(char **toString, char **fromString)
447 char *to = *toString;
448 char *from = *fromString;
450 while (TRUE) {
451 *to = *from;
452 if (*from =='\0') {
453 *fromString = NULL;
454 return;
456 if (*from=='/') {
457 *toString = to + 1;
458 *fromString = from + 1;
459 return;
461 from++;
462 to++;
466 #else /* VMS */
469 ** Dummy versions of the public functions for VMS.
473 ** Return 0 if everything's fine, 1 else.
475 int NormalizePathname(char *pathname)
477 return 0;
481 ** Return 0 if everything's fine, 1 else.
483 int CompressPathname(char *pathname)
485 return 0;
489 * Returns:
490 * TRUE if no error occured
492 * FALSE if an error occured.
494 int ResolvePath(const char * pathIn, char * pathResolved)
496 if (strlen(pathIn) < MAXPATHLEN) {
497 strcpy(pathResolved, pathIn);
498 return TRUE;
499 } else {
500 return FALSE;
504 #endif /* VMS */
507 ** Return the trailing 'n' no. of path components
509 const char
510 *GetTrailingPathComponents(const char* path,
511 int noOfComponents)
513 /* Start from the rear */
514 const char* ptr = path + strlen(path);
515 int count = 0;
517 while (--ptr > path) {
518 if (*ptr == '/') {
519 if (count++ == noOfComponents) {
520 break;
524 return(ptr);
528 ** Samples up to a maximum of FORMAT_SAMPLE_LINES lines and FORMAT_SAMPLE_CHARS
529 ** characters, to determine whether fileString represents a MS DOS or Macintosh
530 ** format file. If there's ANY ambiguity (a newline in the sample not paired
531 ** with a return in an otherwise DOS looking file, or a newline appearing in
532 ** the sampled portion of a Macintosh looking file), the file is judged to be
533 ** Unix format.
535 int FormatOfFile(const char *fileString)
537 const char *p;
538 int nNewlines = 0, nReturns = 0;
540 for (p=fileString; *p!='\0' && p < fileString + FORMAT_SAMPLE_CHARS; p++) {
541 if (*p == '\n') {
542 nNewlines++;
543 if (p == fileString || *(p-1) != '\r')
544 return UNIX_FILE_FORMAT;
545 if (nNewlines >= FORMAT_SAMPLE_LINES)
546 return DOS_FILE_FORMAT;
547 } else if (*p == '\r')
548 nReturns++;
550 if (nNewlines > 0)
551 return DOS_FILE_FORMAT;
552 if (nReturns > 0)
553 return MAC_FILE_FORMAT;
554 return UNIX_FILE_FORMAT;
558 ** Converts a string (which may represent the entire contents of the file)
559 ** from DOS or Macintosh format to Unix format. Conversion is done in-place.
560 ** In the DOS case, the length will be shorter, and passed length will be
561 ** modified to reflect the new length. The routine has support for blockwise
562 ** file to string conversion: if the fileString has a trailing '\r' and
563 ** 'pendingCR' is not zero, the '\r' is deposited in there and is not
564 ** converted. If there is no trailing '\r', a 0 is deposited in 'pendingCR'
565 ** It's the caller's responsability to make sure that the pending character,
566 ** if present, is inserted at the beginning of the next block to convert.
568 void ConvertFromDosFileString(char *fileString, int *length,
569 char* pendingCR)
571 char *outPtr = fileString;
572 char *inPtr = fileString;
573 if (pendingCR) *pendingCR = 0;
574 while (inPtr < fileString + *length) {
575 if (*inPtr == '\r') {
576 if (inPtr < fileString + *length - 1) {
577 if (*(inPtr + 1) == '\n')
578 inPtr++;
579 } else {
580 if (pendingCR) {
581 *pendingCR = *inPtr;
582 break; /* Don't copy this trailing '\r' */
586 *outPtr++ = *inPtr++;
588 *outPtr = '\0';
589 *length = outPtr - fileString;
591 void ConvertFromMacFileString(char *fileString, int length)
593 char *inPtr = fileString;
594 while (inPtr < fileString + length) {
595 if (*inPtr == '\r' )
596 *inPtr = '\n';
597 inPtr++;
602 ** Converts a string (which may represent the entire contents of the file) from
603 ** Unix to DOS format. String is re-allocated (with malloc), and length is
604 ** modified. If allocation fails, which it may, because this can potentially
605 ** be a huge hunk of memory, returns FALSE and no conversion is done.
607 ** This could be done more efficiently by asking doSave to allocate some
608 ** extra memory for this, and only re-allocating if it wasn't enough. If
609 ** anyone cares about the performance or the potential for running out of
610 ** memory on a save, it should probably be redone.
612 int ConvertToDosFileString(char **fileString, int *length)
614 char *outPtr, *outString;
615 char *inPtr = *fileString;
616 int inLength = *length;
617 int outLength = 0;
619 /* How long a string will we need? */
620 while (inPtr < *fileString + inLength) {
621 if (*inPtr == '\n')
622 outLength++;
623 inPtr++;
624 outLength++;
627 /* Allocate the new string */
628 outString = XtMalloc(outLength + 1);
629 if (outString == NULL)
630 return FALSE;
632 /* Do the conversion, free the old string */
633 inPtr = *fileString;
634 outPtr = outString;
635 while (inPtr < *fileString + inLength) {
636 if (*inPtr == '\n')
637 *outPtr++ = '\r';
638 *outPtr++ = *inPtr++;
640 *outPtr = '\0';
641 XtFree(*fileString);
642 *fileString = outString;
643 *length = outLength;
644 return TRUE;
648 ** Converts a string (which may represent the entire contents of the file)
649 ** from Unix to Macintosh format.
651 void ConvertToMacFileString(char *fileString, int length)
653 char *inPtr = fileString;
655 while (inPtr < fileString + length) {
656 if (*inPtr == '\n' )
657 *inPtr = '\r';
658 inPtr++;
663 ** Reads a text file into a string buffer, converting line breaks to
664 ** unix-style if appropriate.
666 ** Force a terminating \n, if this is requested
668 char *ReadAnyTextFile(const char *fileName, int forceNL)
670 struct stat statbuf;
671 FILE *fp;
672 int fileLen, readLen;
673 char *fileString;
674 int format;
676 /* Read the whole file into fileString */
677 if ((fp = fopen(fileName, "r")) == NULL) {
678 return NULL;
680 if (fstat(fileno(fp), &statbuf) != 0) {
681 fclose(fp);
682 return NULL;
684 fileLen = statbuf.st_size;
685 /* +1 = space for null
686 ** +1 = possible additional \n
688 fileString = XtMalloc(fileLen + 2);
689 readLen = fread(fileString, sizeof(char), fileLen, fp);
690 if (ferror(fp)) {
691 XtFree(fileString);
692 fclose(fp);
693 return NULL;
695 fclose(fp);
696 fileString[readLen] = 0;
698 /* Convert linebreaks? */
699 format = FormatOfFile(fileString);
700 if (format == DOS_FILE_FORMAT){
701 char pendingCR;
702 ConvertFromDosFileString(fileString, &readLen, &pendingCR);
703 } else if (format == MAC_FILE_FORMAT){
704 ConvertFromMacFileString(fileString, readLen);
707 /* now, that the fileString is in Unix format, check for terminating \n */
708 if (forceNL && fileString[readLen - 1] != '\n') {
709 fileString[readLen] = '\n';
710 fileString[readLen + 1] = '\0';
713 return fileString;