1 #if !defined(lint) && !defined(DOS)
2 static char rcsid
[] = "$Id: filesys.c 770 2007-10-24 00:23:09Z hubert@u.washington.edu $";
6 * ========================================================================
7 * Copyright 2006-2007 University of Washington
8 * Copyright 2013-2018 Eduardo Chappa
10 * Licensed under the Apache License, Version 2.0 (the "License");
11 * you may not use this file except in compliance with the License.
12 * You may obtain a copy of the License at
14 * http://www.apache.org/licenses/LICENSE-2.0
16 * ========================================================================
26 #include "../../pith/osdep/temp_nam.h"
27 #include "../../pith/charconv/filesys.h"
29 #include "../estruct.h"
34 #include "../keydefs.h"
43 # define dirent direct
45 # include <sys/ndir.h>
57 * fexist - returns TRUE if the file exists with mode passed in m,
58 * FALSE otherwise. By side effect returns length of file in l
59 * File is assumed to be a UTF-8 string.
63 char *m
, /* files mode: r,w,rw,t or x */
64 off_t
*l
) /* t means use lstat */
69 int (*stat_f
)() = (m
&& *m
== 't') ? our_lstat
: our_stat
;
74 rv
= (*stat_f
)(file
, &sbuf
);
77 case ENOENT
: /* File not found */
81 case ENAMETOOLONG
: /* Name is too long */
85 case EACCES
: /* File not found */
88 default: /* Some other error */
97 *l
= (off_t
)sbuf
.st_size
;
99 if((sbuf
.st_mode
&S_IFMT
) == S_IFDIR
)
105 * If it is a symbolic link pointing to a directory, treat
106 * it like it is a directory, not a link.
108 if((sbuf
.st_mode
&S_IFMT
) == S_IFLNK
){
109 rv
= our_stat(file
, &sbuf2
);
112 case ENOENT
: /* File not found */
116 case ENAMETOOLONG
: /* Name is too long */
120 case EACCES
: /* File not found */
123 default: /* Some other error */
131 if((sbuf2
.st_mode
&S_IFMT
) == S_IFDIR
)
135 return(((sbuf
.st_mode
&S_IFMT
) == S_IFLNK
) ? FIOSYM
: FIOSUC
);
138 if(*m
== 'r'){ /* read access? */
139 if(*(m
+1) == 'w'){ /* and write access? */
140 rv
= (can_access(file
,READ_ACCESS
)==0)
141 ? (can_access(file
,WRITE_ACCESS
)==0)
146 else if(!*(m
+1)){ /* just read access? */
147 rv
= (can_access(file
,READ_ACCESS
)==0) ? FIOSUC
: FIONRD
;
150 else if(*m
== 'w' && !*(m
+1)) /* write access? */
151 rv
= (can_access(file
,WRITE_ACCESS
)==0) ? FIOSUC
: FIONWT
;
152 else if(*m
== 'x' && !*(m
+1)) /* execute access? */
153 rv
= (can_access(file
,EXECUTE_ACCESS
)==0) ? FIOSUC
: FIONEX
;
155 rv
= FIOERR
; /* bad m arg */
164 if(our_stat(file
, &sbuf
) < 0){
165 if(errno
== ENOENT
) /* File not found */
172 *l
= (off_t
)sbuf
.st_size
;
174 if(sbuf
.st_mode
& S_IFDIR
)
176 else if(*m
== 't') /* no links, just say yes */
179 if(m
[0] == 'r') /* read access? */
180 return((S_IREAD
& sbuf
.st_mode
) ? FIOSUC
: FIONRD
);
181 else if(m
[0] == 'w') /* write access? */
182 return((S_IWRITE
& sbuf
.st_mode
) ? FIOSUC
: FIONWT
);
183 else if(m
[0] == 'x') /* execute access? */
184 return((S_IEXEC
& sbuf
.st_mode
) ? FIOSUC
: FIONEX
);
185 return(FIOERR
); /* what? */
186 #endif /* _WINDOWS */
191 * isdir - returns true if fn is a readable directory, false otherwise
192 * silent on errors (we'll let someone else notice the problem;)).
195 isdir(char *fn
, long *l
, time_t *d
)
202 if(our_stat(fn
, &sbuf
) < 0)
211 return((sbuf
.st_mode
&S_IFMT
) == S_IFDIR
);
216 * gethomedir - returns the users home directory in UTF-8 string
217 * Note: home is malloc'd for life of pico
222 static char *home
= NULL
;
223 static short hlen
= 0;
229 strncpy(buf
, "~", sizeof(buf
));
230 buf
[sizeof(buf
)-1] = '\0';
231 fixpath(buf
, sizeof(buf
)); /* let fixpath do the work! */
233 if(Pmaster
&& Pmaster
->home_dir
)
234 snprintf(buf
, sizeof(buf
), "%s", Pmaster
->home_dir
);
236 snprintf(buf
, sizeof(buf
), "%c:\\", _getdrive() + 'A' - 1);
237 #endif /* _WINDOWS */
239 if((home
= (char *)malloc((hlen
+ 1) * sizeof(char))) == NULL
){
240 emlwrite("Problem allocating space for home dir", NULL
);
244 strncpy(home
, buf
, hlen
);
256 * homeless - returns true if given file does not reside in the current
257 * user's home directory tree.
265 home
= gethomedir(&len
);
266 return(strncmp(home
, f
, len
));
271 * getfnames - return all file names in the given directory in a single
272 * malloc'd string. n contains the number of names
275 getfnames(char *dn
, char *pat
, int *n
, char *e
, size_t elen
)
277 size_t l
, avail
, alloced
, incr
= 1024;
278 char *names
, *np
, *p
;
286 struct _finddata_t dbuf
;
289 DIR *dirp
; /* opened directory */
292 #if defined(ct) || !defined(_WINDOWS)
298 if(our_stat(dn
, &sbuf
) < 0){
300 case ENOENT
: /* File not found */
302 snprintf(e
, elen
, _("File not found: \"%s\""), dn
);
306 case ENAMETOOLONG
: /* Name is too long */
308 snprintf(e
, elen
, _("File name too long: \"%s\""), dn
);
312 default: /* Some other error */
314 snprintf(e
, elen
, _("Error getting file info: \"%s\""), dn
);
322 * We'd like to use 512 * st_blocks as an initial estimate but
323 * some systems have a stat struct with no st_blocks in it.
325 avail
= alloced
= MAX(sbuf
.st_size
, incr
);
326 if((sbuf
.st_mode
&S_IFMT
) != S_IFDIR
){
328 snprintf(e
, elen
, _("Not a directory: \"%s\""), dn
);
334 if((names
=(char *)malloc(alloced
* sizeof(char))) == NULL
){
336 snprintf(e
, elen
, _("Can't malloc space for file names"));
343 if((dirp
=opendir(fname_to_locale(dn
))) == NULL
){
345 snprintf(e
, elen
, _("Can't open \"%s\": %s"), dn
, errstr(errno
));
355 while(fread(&dp
, sizeof(struct direct
), 1, dirp
) > 0) {
356 /* skip empty slots with inode of 0 */
359 (*n
)++; /* count the number of active slots */
360 (void)strncpy(fn
, dp
.d_name
, DIRSIZ
);
362 p
= fname_to_utf8(fn
);
363 while((*np
++ = *p
++) != '\0')
366 #else /* !defined(ct) */
368 strncpy(buf
, dn
, sizeof(buf
));
369 buf
[sizeof(buf
)-1] = '\0';
370 snprintf(buf
, sizeof(buf
), "%s%s%s*%s%s", dn
,
371 (dn
[strlen(dn
)-1] == '\\') ? "" : "\\",
372 (pat
&& *pat
) ? pat
: "",
373 (pat
&& *pat
&& strchr(pat
, '.')) ? "" : ".",
374 (pat
&& *pat
&& strchr(pat
, '.')) ? "" : "*");
375 if((findrv
= _findfirst(buf
, &dbuf
)) < 0){
377 sprintf(e
, "Can't find first file in \"%s\"", dn
);
379 free((char *) names
);
384 p
= fname_to_utf8(dbuf
.name
);
386 while((dp
= readdir(dirp
)) != NULL
){
387 p
= fname_to_utf8(dp
->d_name
);
388 if(!pat
|| !*pat
|| !strncmp(p
, pat
, strlen(pat
))){
398 if((names
=(char *)realloc((void *)names
, alloced
* sizeof(char)))
401 snprintf(e
, elen
, _("Can't malloc enough space for file names"));
406 np
= names
+ (np
-oldnames
);
411 while((*np
++ = *p
++) != '\0')
415 while(_findnext(findrv
, &dbuf
) == 0);
420 #endif /* !defined(ct) */
425 closedir(dirp
); /* shut down */
432 * fioperr - given the error number and file name, display error
435 fioperr(int e
, char *f
)
442 case FIOFNF
: /* File not found */
443 emlwwrite(_("File \"%s\" not found"), &eml
);
445 case FIOEOF
: /* end of file */
446 emlwwrite(_("End of file \"%s\" reached"), &eml
);
448 case FIOLNG
: /* name too long */
449 emlwwrite(_("File name \"%s\" too long"), &eml
);
451 case FIODIR
: /* file is a directory */
452 emlwwrite(_("File \"%s\" is a directory"), &eml
);
455 emlwwrite(_("Write permission denied: %s"), &eml
);
458 emlwwrite(_("Read permission denied: %s"), &eml
);
461 emlwwrite(_("Permission denied: %s"), &eml
);
464 emlwwrite(_("Execute permission denied: %s"), &eml
);
467 emlwwrite(_("File I/O error: %s"), &eml
);
474 * pfnexpand - pico's function to expand the given file name if there is
475 * a leading '~'. Converts the homedir from user's locale to UTF-8.
476 * Fn is assumed to already be UTF-8.
479 pfnexpand(char *fn
, size_t fnlen
)
481 register char *x
, *y
, *z
;
488 for(x
= fn
+1, y
= name
;
489 *x
!= '/' && *x
!= '\0' && y
-name
< sizeof(name
)-1;
494 if(x
== fn
+ 1){ /* ~/ */
495 if (!(home
= (char *) getenv("HOME")))
496 if ((pw
= getpwuid(geteuid())) != NULL
)
499 else if(*name
){ /* ~username/ */
500 if((pw
= getpwnam(name
)) != NULL
)
504 if(!home
|| (strlen(home
) + strlen(fn
) >= fnlen
))
507 char name
[_MAX_PATH
];
509 if(*fn
== '~' && *(x
= fn
+ 1) == '\\') {
510 if(!(home
= (char *) getenv("HOME"))
511 && getenv("HOMEDRIVE") && getenv("HOMEPATH"))
512 snprintf(home
= name
, sizeof(name
), "%s%s",
513 (char *) getenv("HOMEDRIVE"), (char *) getenv("HOMEPATH"));
514 #endif /* _WINDOWS */
515 home
= fname_to_utf8(home
);
517 /* make room for expanded path */
518 for(z
= x
+ strlen(x
), y
= fn
+ strlen(x
) + strlen(home
);
523 /* and insert the expanded address */
524 for(x
= fn
, y
= home
; *y
!= '\0' && x
-fn
< fnlen
; *x
++ = *y
++)
535 * fixpath - make the given pathname into an absolute path, incoming
539 fixpath(char *name
, size_t namelen
)
542 char file
[_MAX_PATH
];
548 /* return the full path of given file, so drive spec? */
549 if(name
[1] == ':' && isalpha((unsigned char) name
[0])){
550 if(name
[2] != '\\'){ /* including path? */
551 dr
= toupper((unsigned char)name
[0]) - 'A' + 1;
552 if((void *)_getdcwd(dr
, file
, _MAX_PATH
) != NULL
){
553 if(file
[strlen(file
)-1] != '\\')
554 strncat(file
, "\\", sizeof(file
)-1-strlen(file
));
557 strncat(file
, &name
[2], sizeof(file
)-1-strlen(file
));
563 return; /* fully qualified with drive and path! */
565 else if(name
[0] == '\\' && name
[1] != '\\') { /* no drive spec! */
566 sprintf(file
, "%c:%.*s", _getdrive()+'A'-1, namelen
-3, name
);
568 else if(name
[0] == '\\' && name
[1] == '\\'){ /* Windows network drive */
572 if(Pmaster
&& !(gmode
& MDCURDIR
)){
573 strncpy(file
, ((gmode
& MDTREE
) || opertree
[0])
574 ? opertree
: gethomedir(NULL
), sizeof(file
)-1);
575 file
[sizeof(file
)-1] = '\0';
577 else if(!_getcwd(file
, sizeof(file
))) /* no qualification */
580 if(*name
){ /* if name, append it */
581 if(*file
&& file
[strlen(file
)-1] != '\\')
582 strncat(file
, "\\", sizeof(file
)-1-strlen(file
));
584 strncat(file
, name
, sizeof(file
)-1-strlen(file
));
588 strncpy(name
, file
, namelen
-1); /* copy back to real buffer */
589 name
[namelen
-1] = '\0'; /* tie off just in case */
593 /* filenames relative to ~ */
594 if(!((name
[0] == '/')
596 && (name
[1] == '/' || (name
[1] == '.' && name
[2] == '/'))))){
597 if(Pmaster
&& !(gmode
&MDCURDIR
)
598 && (*name
!= '~' && strlen(name
)+2 < namelen
)){
600 if(gmode
&MDTREE
&& strlen(name
)+strlen(opertree
)+1 < namelen
){
601 int off
= strlen(opertree
);
603 for(shft
= strchr(name
, '\0'); shft
>= name
; shft
--)
606 strncpy(name
, opertree
, MIN(off
,namelen
-1));
607 name
[MIN(off
,namelen
-1)] = '/';
610 for(shft
= strchr(name
, '\0'); shft
>= name
; shft
--)
618 pfnexpand(name
, namelen
);
625 * compresspath - given a base path and an additional directory, collapse
626 * ".." and "." elements and return absolute path (appending
627 * base if necessary).
630 * 0 if there's a problem
631 * new path, by side effect, if things went OK
634 compresspath(char *base
, char *path
, size_t pathlen
)
642 #define PUSHD(X) (stack[depth++] = X)
643 #define POPD() ((depth > 0) ? stack[--depth] : "")
647 fixpath(path
, pathlen
);
648 strncpy(pathbuf
, path
, sizeof(pathbuf
));
649 pathbuf
[sizeof(pathbuf
)-1] = '\0';
651 else if(*path
!= C_FILESEP
)
652 snprintf(pathbuf
, sizeof(pathbuf
), "%s%c%s", base
, C_FILESEP
, path
);
654 strncpy(pathbuf
, path
, sizeof(pathbuf
));
655 pathbuf
[sizeof(pathbuf
)-1] = '\0';
658 strncpy(pathbuf
, path
, sizeof(pathbuf
));
659 pathbuf
[sizeof(pathbuf
)-1] = '\0';
660 fixpath(pathbuf
, sizeof(pathbuf
));
661 #endif /* _WINDOWS */
664 for(i
=0; pathbuf
[i
] != '\0'; i
++){ /* pass thru path name */
665 if(pathbuf
[i
] == C_FILESEP
){
667 PUSHD(p
); /* push dir entry */
669 p
= &pathbuf
[i
+1]; /* advance p */
670 pathbuf
[i
] = '\0'; /* cap old p off */
674 if(pathbuf
[i
] == '.'){ /* special cases! */
675 if(pathbuf
[i
+1] == '.' /* parent */
676 && (pathbuf
[i
+2] == C_FILESEP
|| pathbuf
[i
+2] == '\0')){
677 if(!strcmp(POPD(), "")) /* bad news! */
681 p
= (pathbuf
[i
] == '\0') ? "" : &pathbuf
[i
+1];
683 else if(pathbuf
[i
+1] == C_FILESEP
|| pathbuf
[i
+1] == '\0'){
685 p
= (pathbuf
[i
] == '\0') ? "" : &pathbuf
[i
+1];
691 PUSHD(p
); /* get last element */
694 for(i
= 0; i
< depth
; i
++){
695 strncat(path
, S_FILESEP
, pathlen
-strlen(path
)-1);
696 path
[pathlen
-1] = '\0';
697 strncat(path
, stack
[i
], pathlen
-strlen(path
)-1);
698 path
[pathlen
-1] = '\0';
701 return(1); /* everything's ok */
707 * tmpname - return a temporary file name in the given buffer, the filename
708 * is in the directory dir unless dir is NULL. The file did not exist at
709 * the time of the temp_nam call, but was created by temp_nam.
712 tmpname(char *dir
, char *name
)
716 if((t
= temp_nam((dir
&& *dir
) ? dir
: NULL
, "pico.")) != NULL
){
720 if(!((dir
&& *dir
) ||
721 (dir
= getenv("TMPDIR")) ||
722 (dir
= getenv("TMP")) ||
723 (dir
= getenv("TEMP"))))
724 if(!(getcwd(dir
= tmp
, _MAX_PATH
)
725 && fexist(dir
, "w", (off_t
*) NULL
) == FIOSUC
))
728 if((t
= temp_nam_ext(dir
, "ae", "txt")) != NULL
){
729 #endif /* _WINDOWS */
730 strncpy(name
, t
, NFILEN
-1);
731 name
[NFILEN
-1] = '\0';
735 emlwrite("Unable to construct temp file name", NULL
);
742 * Take a file name, and from it
743 * fabricate a buffer name. This routine knows
744 * about the syntax of file names on the target system.
745 * I suppose that this information could be put in
746 * a better place than a line of code.
749 makename(char bname
[], char fname
[])
758 while (cp1
!=&fname
[0] && cp1
[-1]!=C_FILESEP
)
762 while (cp2
!=&bname
[NBUFN
-1] && *cp1
!=0 && *cp1
!=';')
770 * copy - copy contents of file 'a' into a file named 'b'. Return error
771 * if either isn't accessible or is a directory
774 copy(char *a
, char *b
)
776 int in
, out
, n
, rv
= 0;
778 struct stat tsb
, fsb
;
782 if(our_stat(a
, &fsb
) < 0){ /* get source file info */
783 eml
.s
= errstr(errno
);
784 emlwrite("Can't Copy: %s", &eml
);
788 if(!(fsb
.st_mode
&S_IREAD
)){ /* can we read it? */
790 emlwwrite(_("Read permission denied: %s"), &eml
);
794 if((fsb
.st_mode
&S_IFMT
) == S_IFDIR
){ /* is it a directory? */
796 emlwwrite(_("Can't copy: %s is a directory"), &eml
);
800 if(our_stat(b
, &tsb
) < 0){ /* get dest file's mode */
803 break; /* these are OK */
805 eml
.s
= errstr(errno
);
806 emlwwrite(_("Can't Copy: %s"), &eml
);
811 if(!(tsb
.st_mode
&S_IWRITE
)){ /* can we write it? */
813 emlwwrite(_("Write permission denied: %s"), &eml
);
817 if((tsb
.st_mode
&S_IFMT
) == S_IFDIR
){ /* is it directory? */
819 emlwwrite(_("Can't copy: %s is a directory"), &eml
);
823 if(fsb
.st_dev
== tsb
.st_dev
&& fsb
.st_ino
== tsb
.st_ino
){
824 emlwwrite(_("Identical files. File not copied"), NULL
);
829 if((in
= our_open(a
, O_RDONLY
|O_BINARY
, 0600)) < 0){
830 eml
.s
= errstr(errno
);
831 emlwrite("Copy Failed: %s", &eml
);
835 if((out
=our_creat(b
, fsb
.st_mode
&0xfff)) < 0){
836 eml
.s
= errstr(errno
);
837 emlwrite("Can't Copy: %s", &eml
);
842 if((cb
= (char *)malloc(NLINE
*sizeof(char))) == NULL
){
843 emlwrite("Can't allocate space for copy buffer!", NULL
);
849 while(1){ /* do the copy */
850 if((n
= read(in
, cb
, NLINE
)) < 0){
851 eml
.s
= errstr(errno
);
852 emlwrite("Can't Read Copy: %s", &eml
);
854 break; /* get out now */
857 if(n
== 0) /* done! */
860 if(write(out
, cb
, n
) != n
){
861 eml
.s
= errstr(errno
);
862 emlwrite("Can't Write Copy: %s", &eml
);
876 * Open a file for writing. Return TRUE if all is well, and FALSE on error
880 ffwopen(char *fn
, int readonly
)
882 extern FIOINFO g_pico_fio
;
887 #ifndef MODE_READONLY
888 #define MODE_READONLY (0600)
892 * Call open() by hand since we don't want O_TRUNC -- it'll
893 * screw over-quota users. The strategy is to overwrite the
894 * existing file's data and call ftruncate before close to lop
898 g_pico_fio
.flags
= FIOINFO_WRITE
;
899 g_pico_fio
.name
= fn
;
901 if((fd
= our_open(fn
, O_CREAT
|O_WRONLY
, readonly
? MODE_READONLY
: 0666)) >= 0
902 && (g_pico_fio
.fp
= fdopen(fd
, "w")) != NULL
903 && fseek(g_pico_fio
.fp
, 0L, 0) == 0)
907 eml
.s
= errstr(errno
);
908 emlwrite("Cannot open file for writing: %s", &eml
);
911 if ((g_pico_fio
.fp
= our_fopen(fn
, "w")) == NULL
) {
912 emlwrite("Cannot open file for writing", NULL
);
918 our_chmod(fn
, MODE_READONLY
); /* fix access rights */
922 #endif /* _WINDOWS */
927 * Close a file. Should look at the status in all systems.
932 extern FIOINFO g_pico_fio
;
937 if((g_pico_fio
.flags
& FIOINFO_WRITE
)
938 && (fflush(g_pico_fio
.fp
) == EOF
939 || ftruncate(fileno(g_pico_fio
.fp
),
940 (off_t
) ftell(g_pico_fio
.fp
)) < 0)){
941 eml
.s
= errstr(errno
);
942 emlwwrite(_("Error preparing to close file: %s"), &eml
);
946 if (fclose(g_pico_fio
.fp
) == EOF
) {
947 eml
.s
= errstr(errno
);
948 emlwwrite(_("Error closing file: %s"), &eml
);
952 if (fclose(g_pico_fio
.fp
) != FALSE
) {
953 emlwrite("Error closing file", NULL
);
956 #endif /* _WINDOWS */
963 #define EXTEND_BLOCK 1024
967 * ffelbowroom - make sure the destination's got enough room to receive
968 * what we're about to write...
977 char s
[EXTEND_BLOCK
], *errstring
= NULL
;
979 extern FIOINFO g_pico_fio
;
981 /* Figure out how much room do we need */
982 /* first, what's total */
983 for(n
=0L, lp
=lforw(curbp
->b_linep
); lp
!= curbp
->b_linep
; lp
=lforw(lp
))
984 n
+= (llength(lp
) + 1);
986 errno
= 0; /* make sure previous error are cleared */
988 if(fstat(fileno(g_pico_fio
.fp
), &fsbuf
) == 0){
991 if(n
> 0L){ /* must be growing file, extend it */
992 memset(s
, 'U', EXTEND_BLOCK
);
993 if(fseek(g_pico_fio
.fp
, fsbuf
.st_size
, 0) == 0){
994 for( ; n
> 0L; n
-= EXTEND_BLOCK
){
995 x
= (n
< EXTEND_BLOCK
) ? (int) n
: EXTEND_BLOCK
;
996 if(fwrite(s
, x
* sizeof(char), 1, g_pico_fio
.fp
) != 1){
997 errstring
= errstr(errno
);
1003 && (fflush(g_pico_fio
.fp
) == EOF
1004 || fsync(fileno(g_pico_fio
.fp
)) < 0))
1005 errstring
= errstr(errno
);
1007 if(errstring
) /* clean up */
1008 (void) ftruncate(fileno(g_pico_fio
.fp
), (off_t
) fsbuf
.st_size
);
1009 else if(fseek(g_pico_fio
.fp
, 0L, 0) != 0)
1010 errstring
= errstr(errno
);
1013 errstring
= errstr(errno
);
1017 errstring
= errstr(errno
);
1020 snprintf(s
, sizeof(s
), "Error writing to %s: %s", g_pico_fio
.name
, errstring
);
1022 (void) fclose(g_pico_fio
.fp
);