1 static const char CVSID
[] = "$Id: fileUtils.c,v 1.35 2008/08/20 14:57:35 lebert Exp $";
2 /*******************************************************************************
4 * fileUtils.c -- File utilities for Nirvana applications *
6 * Copyright (C) 1999 Mark Edel *
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. *
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 *
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 *
23 * Nirvana Text Editor *
26 * Written by Mark Edel *
28 * Modified by: DMR - Ported to VMS (1st stage for Histo-Scope) *
30 *******************************************************************************/
33 #include "../config.h"
36 #include "fileUtils.h"
43 #include <X11/Intrinsic.h>
45 #define NULL (void *) 0
51 #include <sys/types.h>
53 #include <sys/param.h>
64 #ifndef MAXSYMLINKS /* should be defined in <sys/param.h> */
65 #define MAXSYMLINKS 20
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.
92 ParseFilename(const char *fullname
, char *filename
, char *pathname
)
94 int fullLen
= strlen(fullname
);
95 int i
, pathLen
, fileLen
;
98 /* find the last ] or : */
99 for (i
=fullLen
-1; i
>=0; i
--) {
100 if (fullname
[i
] == ']' || fullname
[i
] == ':')
104 char *viewExtendPath
;
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;
112 scanStart
= fullLen
- 1;
114 /* find the last slash */
115 for (i
=scanStart
; i
>=0; i
--) {
116 if (fullname
[i
] == '/')
121 /* move chars before / (or ] or :) into pathname,& after into filename */
123 fileLen
= fullLen
- pathLen
;
125 if (pathLen
>= MAXPATHLEN
) {
128 strncpy(pathname
, fullname
, pathLen
);
129 pathname
[pathLen
] = 0;
132 if (fileLen
>= MAXPATHLEN
) {
135 strncpy(filename
, &fullname
[pathLen
], fileLen
);
136 filename
[fileLen
] = 0;
139 #ifndef VMS /* UNIX specific... Modify at a later date for VMS */
141 if (NormalizePathname(pathname
)) {
142 return 1; /* pathname too long */
144 pathLen
= strlen(pathname
);
148 if (filename
&& pathname
&& ((pathLen
+ 1 + fileLen
) >= MAXPATHLEN
)) {
149 return 1; /* pathname + filename too long */
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
];
170 if (pathname
[0] != '~')
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 ");
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 */
197 strcpy(temp
, passwdEntry
->pw_dir
);
199 len_left
= sizeof(temp
)-strlen(temp
)-1;
200 if (len_left
< strlen(nameEnd
)) {
201 /* It won't work out */
204 strcat(temp
, nameEnd
);
205 strcpy(pathname
, temp
);
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.
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
];
231 strncpy(pathResolved
, pathIn
, MAXPATHLEN
);
232 /* If there are no links at all, it's a valid "resolved" path */
235 /* !! readlink does NOT recognize loops, i.e. links like file -> ./file */
236 for (loops
=0; loops
<MAXSYMLINKS
; loops
++) {
238 rlResult
=readlink((char *)pathIn
, resolveBuf
, MAXPATHLEN
-1);
240 rlResult
=readlink(pathIn
, resolveBuf
, MAXPATHLEN
-1);
250 /* It's not a symlink - we are done */
251 strncpy(pathResolved
, pathIn
, MAXPATHLEN
);
252 pathResolved
[MAXPATHLEN
-1] = '\0';
257 } else if (rlResult
== 0) {
261 resolveBuf
[rlResult
]=0;
263 if (resolveBuf
[0]!='/') {
264 strncpy(pathBuf
, pathIn
, MAXPATHLEN
);
265 pathBuf
[MAXPATHLEN
-1] = '\0';
266 pathEnd
=strrchr(pathBuf
, '/');
270 strcpy(pathEnd
+1, resolveBuf
);
272 strcpy(pathBuf
, resolveBuf
);
274 NormalizePathname(pathBuf
);
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 */
294 /* OS/2, ...: welcome to the world of drive letters ... */
295 if (!_fnisabs(pathname
)) {
297 if (pathname
[0] != '/') {
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
);
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
;
341 /* (Added by schwarzenberg)
342 ** replace multiple slashes by a single slash
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"
349 buf
= (char*) malloc(strlen(pathname
) + 2);
351 *outPtr
++ = *inPtr
++;
357 while ('/' == *inPtr
)
365 strcpy(pathname
, buf
);
367 /* compress out . and .. */
371 copyThruSlash(&outPtr
, &inPtr
);
372 while (inPtr
!= NULL
) {
373 /* if the next component is "../", remove previous component */
374 if (compareThruSlash(inPtr
, "../")) {
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. */
382 if(outPtr
-1 == buf
|| (lstat(buf
, &statbuf
) == 0 &&
383 S_ISLNK(statbuf
.st_mode
))) {
384 copyThruSlash(&outPtr
, &inPtr
);
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
);
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",
408 strcpy(pathname
, buf
);
415 *nextSlash(char *ptr
)
417 for(; *ptr
!='/'; ptr
++) {
425 *prevSlash(char *ptr
)
427 for(ptr
-= 2; *ptr
!='/'; ptr
--);
432 compareThruSlash(const char *string1
, const char *string2
)
435 if (*string1
!= *string2
)
437 if (*string1
=='\0' || *string1
=='/')
445 copyThruSlash(char **toString
, char **fromString
)
447 char *to
= *toString
;
448 char *from
= *fromString
;
458 *fromString
= from
+ 1;
469 ** Dummy versions of the public functions for VMS.
473 ** Return 0 if everything's fine, 1 else.
475 int NormalizePathname(char *pathname
)
481 ** Return 0 if everything's fine, 1 else.
483 int CompressPathname(char *pathname
)
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
);
507 ** Return the trailing 'n' no. of path components
510 *GetTrailingPathComponents(const char* path
,
513 /* Start from the rear */
514 const char* ptr
= path
+ strlen(path
);
517 while (--ptr
> path
) {
519 if (count
++ == noOfComponents
) {
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
535 int FormatOfFile(const char *fileString
)
538 int nNewlines
= 0, nReturns
= 0;
540 for (p
=fileString
; *p
!='\0' && p
< fileString
+ FORMAT_SAMPLE_CHARS
; p
++) {
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')
551 return DOS_FILE_FORMAT
;
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
,
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')
582 break; /* Don't copy this trailing '\r' */
586 *outPtr
++ = *inPtr
++;
589 *length
= outPtr
- fileString
;
591 void ConvertFromMacFileString(char *fileString
, int length
)
593 char *inPtr
= fileString
;
594 while (inPtr
< fileString
+ length
) {
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
;
619 /* How long a string will we need? */
620 while (inPtr
< *fileString
+ inLength
) {
627 /* Allocate the new string */
628 outString
= XtMalloc(outLength
+ 1);
629 if (outString
== NULL
)
632 /* Do the conversion, free the old string */
635 while (inPtr
< *fileString
+ inLength
) {
638 *outPtr
++ = *inPtr
++;
642 *fileString
= outString
;
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
) {
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
)
672 int fileLen
, readLen
;
676 /* Read the whole file into fileString */
677 if ((fp
= fopen(fileName
, "r")) == NULL
) {
680 if (fstat(fileno(fp
), &statbuf
) != 0) {
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
);
696 fileString
[readLen
] = 0;
698 /* Convert linebreaks? */
699 format
= FormatOfFile(fileString
);
700 if (format
== DOS_FILE_FORMAT
){
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';