2 * ========================================================================
3 * Copyright 2006-2007 University of Washington
4 * Copyright 2013-2021 Eduardo Chappa
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * ========================================================================
22 #include "../../pith/osdep/temp_nam.h"
23 #include "../../pith/charconv/filesys.h"
25 #include "../estruct.h"
30 #include "../keydefs.h"
39 # define dirent direct
41 # include <sys/ndir.h>
53 * fexist - returns TRUE if the file exists with mode passed in m,
54 * FALSE otherwise. By side effect returns length of file in l
55 * File is assumed to be a UTF-8 string.
59 char *m
, /* files mode: r,w,rw,t or x */
60 off_t
*l
) /* t means use lstat */
65 int (*stat_f
)() = (m
&& *m
== 't') ? our_lstat
: our_stat
;
70 rv
= (*stat_f
)(file
, &sbuf
);
73 case ENOENT
: /* File not found */
77 case ENAMETOOLONG
: /* Name is too long */
81 case EACCES
: /* File not found */
84 default: /* Some other error */
93 *l
= (off_t
)sbuf
.st_size
;
95 if((sbuf
.st_mode
&S_IFMT
) == S_IFDIR
)
101 * If it is a symbolic link pointing to a directory, treat
102 * it like it is a directory, not a link.
104 if((sbuf
.st_mode
&S_IFMT
) == S_IFLNK
){
105 rv
= our_stat(file
, &sbuf2
);
108 case ENOENT
: /* File not found */
112 case ENAMETOOLONG
: /* Name is too long */
116 case EACCES
: /* File not found */
119 default: /* Some other error */
127 if((sbuf2
.st_mode
&S_IFMT
) == S_IFDIR
)
131 return(((sbuf
.st_mode
&S_IFMT
) == S_IFLNK
) ? FIOSYM
: FIOSUC
);
134 if(*m
== 'r'){ /* read access? */
135 if(*(m
+1) == 'w'){ /* and write access? */
136 rv
= (can_access(file
,READ_ACCESS
)==0)
137 ? (can_access(file
,WRITE_ACCESS
)==0)
142 else if(!*(m
+1)){ /* just read access? */
143 rv
= (can_access(file
,READ_ACCESS
)==0) ? FIOSUC
: FIONRD
;
146 else if(*m
== 'w' && !*(m
+1)) /* write access? */
147 rv
= (can_access(file
,WRITE_ACCESS
)==0) ? FIOSUC
: FIONWT
;
148 else if(*m
== 'x' && !*(m
+1)) /* execute access? */
149 rv
= (can_access(file
,EXECUTE_ACCESS
)==0) ? FIOSUC
: FIONEX
;
151 rv
= FIOERR
; /* bad m arg */
160 if(our_stat(file
, &sbuf
) < 0){
161 if(errno
== ENOENT
) /* File not found */
168 *l
= (off_t
)sbuf
.st_size
;
170 if(sbuf
.st_mode
& S_IFDIR
)
172 else if(*m
== 't') /* no links, just say yes */
175 if(m
[0] == 'r') /* read access? */
176 return((S_IREAD
& sbuf
.st_mode
) ? FIOSUC
: FIONRD
);
177 else if(m
[0] == 'w') /* write access? */
178 return((S_IWRITE
& sbuf
.st_mode
) ? FIOSUC
: FIONWT
);
179 else if(m
[0] == 'x') /* execute access? */
180 return((S_IEXEC
& sbuf
.st_mode
) ? FIOSUC
: FIONEX
);
181 return(FIOERR
); /* what? */
182 #endif /* _WINDOWS */
187 * isdir - returns true if fn is a readable directory, false otherwise
188 * silent on errors (we'll let someone else notice the problem;)).
191 isdir(char *fn
, long *l
, time_t *d
)
198 if(our_stat(fn
, &sbuf
) < 0)
207 return((sbuf
.st_mode
&S_IFMT
) == S_IFDIR
);
212 * gethomedir - returns the users home directory in UTF-8 string
213 * Note: home is malloc'd for life of pico
218 static char *home
= NULL
;
219 static short hlen
= 0;
225 strncpy(buf
, "~", sizeof(buf
));
226 buf
[sizeof(buf
)-1] = '\0';
227 fixpath(buf
, sizeof(buf
)); /* let fixpath do the work! */
229 if(Pmaster
&& Pmaster
->home_dir
)
230 snprintf(buf
, sizeof(buf
), "%s", Pmaster
->home_dir
);
232 snprintf(buf
, sizeof(buf
), "%c:\\", _getdrive() + 'A' - 1);
233 #endif /* _WINDOWS */
235 if((home
= (char *)malloc((hlen
+ 1) * sizeof(char))) == NULL
){
236 emlwrite("Problem allocating space for home dir", NULL
);
240 strncpy(home
, buf
, hlen
);
252 * homeless - returns true if given file does not reside in the current
253 * user's home directory tree.
261 home
= gethomedir(&len
);
262 return(strncmp(home
, f
, len
));
267 * getfnames - return all file names in the given directory in a single
268 * malloc'd string. n contains the number of names
271 getfnames(char *dn
, char *pat
, int *n
, char *e
, size_t elen
)
273 size_t l
, avail
, alloced
, incr
= 1024;
274 char *names
, *np
, *p
;
282 struct _finddata_t dbuf
;
285 DIR *dirp
; /* opened directory */
288 #if defined(ct) || !defined(_WINDOWS)
294 if(our_stat(dn
, &sbuf
) < 0){
296 case ENOENT
: /* File not found */
298 snprintf(e
, elen
, _("File not found: \"%s\""), dn
);
302 case ENAMETOOLONG
: /* Name is too long */
304 snprintf(e
, elen
, _("File name too long: \"%s\""), dn
);
308 default: /* Some other error */
310 snprintf(e
, elen
, _("Error getting file info: \"%s\""), dn
);
318 * We'd like to use 512 * st_blocks as an initial estimate but
319 * some systems have a stat struct with no st_blocks in it.
321 * In some systems the size of a directory is the sum of the
322 * sizes of all files contained in it, so sbuf.st_size might
323 * be too large and the malloc() might fail with error ENOMEM.
324 * Since this is just the list of files, and not the contents
325 * of the files, let us start with a small amount and resize
326 * when necessary. Reported by Sebasting Knust. Of course the
327 * correct solution is to read the directory twice, but keeping
328 * realloc()ing as needed also solves the problem in a less
331 avail
= alloced
= 4096;
332 if((sbuf
.st_mode
&S_IFMT
) != S_IFDIR
){
334 snprintf(e
, elen
, _("Not a directory: \"%s\""), dn
);
340 if((names
=(char *)malloc(alloced
* sizeof(char))) == NULL
){
342 snprintf(e
, elen
, _("Can't malloc space for file names"));
349 if((dirp
=opendir(fname_to_locale(dn
))) == NULL
){
351 snprintf(e
, elen
, _("Can't open \"%s\": %s"), dn
, errstr(errno
));
361 while(fread(&dp
, sizeof(struct direct
), 1, dirp
) > 0) {
362 /* skip empty slots with inode of 0 */
365 (*n
)++; /* count the number of active slots */
366 (void)strncpy(fn
, dp
.d_name
, DIRSIZ
);
368 p
= fname_to_utf8(fn
);
369 while((*np
++ = *p
++) != '\0')
372 #else /* !defined(ct) */
374 strncpy(buf
, dn
, sizeof(buf
));
375 buf
[sizeof(buf
)-1] = '\0';
376 snprintf(buf
, sizeof(buf
), "%s%s%s*%s%s", dn
,
377 (dn
[strlen(dn
)-1] == '\\') ? "" : "\\",
378 (pat
&& *pat
) ? pat
: "",
379 (pat
&& *pat
&& strchr(pat
, '.')) ? "" : ".",
380 (pat
&& *pat
&& strchr(pat
, '.')) ? "" : "*");
381 if((findrv
= _findfirst(buf
, &dbuf
)) < 0){
383 sprintf(e
, "Can't find first file in \"%s\"", dn
);
385 free((char *) names
);
390 p
= fname_to_utf8(dbuf
.name
);
392 while((dp
= readdir(dirp
)) != NULL
){
393 p
= fname_to_utf8(dp
->d_name
);
394 if(!pat
|| !*pat
|| !strncmp(p
, pat
, strlen(pat
))){
404 if((names
=(char *)realloc((void *)names
, alloced
* sizeof(char)))
407 snprintf(e
, elen
, _("Can't malloc enough space for file names"));
412 np
= names
+ (np
-oldnames
);
417 while((*np
++ = *p
++) != '\0')
421 while(_findnext(findrv
, &dbuf
) == 0);
426 #endif /* !defined(ct) */
431 closedir(dirp
); /* shut down */
438 * fioperr - given the error number and file name, display error
441 fioperr(int e
, char *f
)
448 case FIOFNF
: /* File not found */
449 emlwwrite(_("File \"%s\" not found"), &eml
);
451 case FIOEOF
: /* end of file */
452 emlwwrite(_("End of file \"%s\" reached"), &eml
);
454 case FIOLNG
: /* name too long */
455 emlwwrite(_("File name \"%s\" too long"), &eml
);
457 case FIODIR
: /* file is a directory */
458 emlwwrite(_("File \"%s\" is a directory"), &eml
);
461 emlwwrite(_("Write permission denied: %s"), &eml
);
464 emlwwrite(_("Read permission denied: %s"), &eml
);
467 emlwwrite(_("Permission denied: %s"), &eml
);
470 emlwwrite(_("Execute permission denied: %s"), &eml
);
473 emlwwrite(_("File I/O error: %s"), &eml
);
480 * pfnexpand - pico's function to expand the given file name if there is
481 * a leading '~'. Converts the homedir from user's locale to UTF-8.
482 * Fn is assumed to already be UTF-8.
485 pfnexpand(char *fn
, size_t fnlen
)
487 register char *x
, *y
, *z
;
494 for(x
= fn
+1, y
= name
;
495 *x
!= '/' && *x
!= '\0' && y
-name
< sizeof(name
)-1;
500 if(x
== fn
+ 1){ /* ~/ */
501 if (!(home
= (char *) getenv("HOME")))
502 if ((pw
= getpwuid(geteuid())) != NULL
)
505 else if(*name
){ /* ~username/ */
506 if((pw
= getpwnam(name
)) != NULL
)
510 if(!home
|| (strlen(home
) + strlen(fn
) >= fnlen
))
513 char name
[_MAX_PATH
];
515 if(*fn
== '~' && *(x
= fn
+ 1) == '\\') {
516 if(!(home
= (char *) getenv("HOME"))
517 && getenv("HOMEDRIVE") && getenv("HOMEPATH"))
518 snprintf(home
= name
, sizeof(name
), "%s%s",
519 (char *) getenv("HOMEDRIVE"), (char *) getenv("HOMEPATH"));
520 #endif /* _WINDOWS */
521 home
= fname_to_utf8(home
);
523 /* make room for expanded path */
524 for(z
= x
+ strlen(x
), y
= fn
+ strlen(x
) + strlen(home
);
529 /* and insert the expanded address */
530 for(x
= fn
, y
= home
; *y
!= '\0' && x
-fn
< fnlen
; *x
++ = *y
++)
541 * fixpath - make the given pathname into an absolute path, incoming
545 fixpath(char *name
, size_t namelen
)
548 char file
[_MAX_PATH
];
554 /* return the full path of given file, so drive spec? */
555 if(name
[1] == ':' && isalpha((unsigned char) name
[0])){
556 if(name
[2] != '\\'){ /* including path? */
557 dr
= toupper((unsigned char)name
[0]) - 'A' + 1;
558 if((void *)_getdcwd(dr
, file
, _MAX_PATH
) != NULL
){
559 if(file
[strlen(file
)-1] != '\\')
560 strncat(file
, "\\", sizeof(file
)-1-strlen(file
));
563 strncat(file
, &name
[2], sizeof(file
)-1-strlen(file
));
569 return; /* fully qualified with drive and path! */
571 else if(name
[0] == '\\' && name
[1] != '\\') { /* no drive spec! */
572 sprintf(file
, "%c:%.*s", _getdrive()+'A'-1, namelen
-3, name
);
574 else if(name
[0] == '\\' && name
[1] == '\\'){ /* Windows network drive */
578 if(Pmaster
&& !(gmode
& MDCURDIR
)){
579 strncpy(file
, ((gmode
& MDTREE
) || opertree
[0])
580 ? opertree
: gethomedir(NULL
), sizeof(file
)-1);
581 file
[sizeof(file
)-1] = '\0';
583 else if(!_getcwd(file
, sizeof(file
))) /* no qualification */
586 if(*name
){ /* if name, append it */
587 if(*file
&& file
[strlen(file
)-1] != '\\')
588 strncat(file
, "\\", sizeof(file
)-1-strlen(file
));
590 strncat(file
, name
, sizeof(file
)-1-strlen(file
));
594 strncpy(name
, file
, namelen
-1); /* copy back to real buffer */
595 name
[namelen
-1] = '\0'; /* tie off just in case */
599 /* filenames relative to ~ */
600 if(!((name
[0] == '/')
602 && (name
[1] == '/' || (name
[1] == '.' && name
[2] == '/'))))){
603 if(Pmaster
&& !(gmode
&MDCURDIR
)
604 && (*name
!= '~' && strlen(name
)+2 < namelen
)){
606 if(gmode
&MDTREE
&& strlen(name
)+strlen(opertree
)+1 < namelen
){
607 int off
= strlen(opertree
);
609 for(shft
= strchr(name
, '\0'); shft
>= name
; shft
--)
612 strncpy(name
, opertree
, MIN(off
,namelen
-1));
613 name
[MIN(off
,namelen
-1)] = '/';
616 for(shft
= strchr(name
, '\0'); shft
>= name
; shft
--)
624 pfnexpand(name
, namelen
);
631 * compresspath - given a base path and an additional directory, collapse
632 * ".." and "." elements and return absolute path (appending
633 * base if necessary).
636 * 0 if there's a problem
637 * new path, by side effect, if things went OK
640 compresspath(char *base
, char *path
, size_t pathlen
)
648 #define PUSHD(X) (stack[depth++] = X)
649 #define POPD() ((depth > 0) ? stack[--depth] : "")
653 fixpath(path
, pathlen
);
654 strncpy(pathbuf
, path
, sizeof(pathbuf
));
655 pathbuf
[sizeof(pathbuf
)-1] = '\0';
657 else if(*path
!= C_FILESEP
)
658 snprintf(pathbuf
, sizeof(pathbuf
), "%s%c%s", base
, C_FILESEP
, path
);
660 strncpy(pathbuf
, path
, sizeof(pathbuf
));
661 pathbuf
[sizeof(pathbuf
)-1] = '\0';
664 strncpy(pathbuf
, path
, sizeof(pathbuf
));
665 pathbuf
[sizeof(pathbuf
)-1] = '\0';
666 fixpath(pathbuf
, sizeof(pathbuf
));
667 #endif /* _WINDOWS */
670 for(i
=0; pathbuf
[i
] != '\0'; i
++){ /* pass thru path name */
671 if(pathbuf
[i
] == C_FILESEP
){
673 PUSHD(p
); /* push dir entry */
675 p
= &pathbuf
[i
+1]; /* advance p */
676 pathbuf
[i
] = '\0'; /* cap old p off */
680 if(pathbuf
[i
] == '.'){ /* special cases! */
681 if(pathbuf
[i
+1] == '.' /* parent */
682 && (pathbuf
[i
+2] == C_FILESEP
|| pathbuf
[i
+2] == '\0')){
683 if(!strcmp(POPD(), "")) /* bad news! */
687 p
= (pathbuf
[i
] == '\0') ? "" : &pathbuf
[i
+1];
689 else if(pathbuf
[i
+1] == C_FILESEP
|| pathbuf
[i
+1] == '\0'){
691 p
= (pathbuf
[i
] == '\0') ? "" : &pathbuf
[i
+1];
697 PUSHD(p
); /* get last element */
700 for(i
= 0; i
< depth
; i
++){
701 strncat(path
, S_FILESEP
, pathlen
-strlen(path
)-1);
702 path
[pathlen
-1] = '\0';
703 strncat(path
, stack
[i
], pathlen
-strlen(path
)-1);
704 path
[pathlen
-1] = '\0';
707 return(1); /* everything's ok */
713 * tmpname - return a temporary file name in the given buffer, the filename
714 * is in the directory dir unless dir is NULL. The file did not exist at
715 * the time of the temp_nam call, but was created by temp_nam.
718 tmpname(char *dir
, char *name
)
722 if((t
= temp_nam((dir
&& *dir
) ? dir
: NULL
, "pico.")) != NULL
){
726 if(!((dir
&& *dir
) ||
727 (dir
= getenv("TMPDIR")) ||
728 (dir
= getenv("TMP")) ||
729 (dir
= getenv("TEMP"))))
730 if(!(getcwd(dir
= tmp
, _MAX_PATH
)
731 && fexist(dir
, "w", (off_t
*) NULL
) == FIOSUC
))
734 if((t
= temp_nam_ext(dir
, "ae", "txt")) != NULL
){
735 #endif /* _WINDOWS */
736 strncpy(name
, t
, NFILEN
-1);
737 name
[NFILEN
-1] = '\0';
741 emlwrite("Unable to construct temp file name", NULL
);
748 * Take a file name, and from it
749 * fabricate a buffer name. This routine knows
750 * about the syntax of file names on the target system.
751 * I suppose that this information could be put in
752 * a better place than a line of code.
755 makename(char bname
[], char fname
[])
764 while (cp1
!=&fname
[0] && cp1
[-1]!=C_FILESEP
)
768 while (cp2
!=&bname
[NBUFN
-1] && *cp1
!=0 && *cp1
!=';')
776 * copy - copy contents of file 'a' into a file named 'b'. Return error
777 * if either isn't accessible or is a directory
780 copy(char *a
, char *b
)
782 int in
, out
, n
, rv
= 0;
784 struct stat tsb
, fsb
;
788 if(our_stat(a
, &fsb
) < 0){ /* get source file info */
789 eml
.s
= errstr(errno
);
790 emlwrite("Can't Copy: %s", &eml
);
794 if(!(fsb
.st_mode
&S_IREAD
)){ /* can we read it? */
796 emlwwrite(_("Read permission denied: %s"), &eml
);
800 if((fsb
.st_mode
&S_IFMT
) == S_IFDIR
){ /* is it a directory? */
802 emlwwrite(_("Can't copy: %s is a directory"), &eml
);
806 if(our_stat(b
, &tsb
) < 0){ /* get dest file's mode */
809 break; /* these are OK */
811 eml
.s
= errstr(errno
);
812 emlwwrite(_("Can't Copy: %s"), &eml
);
817 if(!(tsb
.st_mode
&S_IWRITE
)){ /* can we write it? */
819 emlwwrite(_("Write permission denied: %s"), &eml
);
823 if((tsb
.st_mode
&S_IFMT
) == S_IFDIR
){ /* is it directory? */
825 emlwwrite(_("Can't copy: %s is a directory"), &eml
);
829 if(fsb
.st_dev
== tsb
.st_dev
&& fsb
.st_ino
== tsb
.st_ino
){
830 emlwwrite(_("Identical files. File not copied"), NULL
);
835 if((in
= our_open(a
, O_RDONLY
|O_BINARY
, 0600)) < 0){
836 eml
.s
= errstr(errno
);
837 emlwrite("Copy Failed: %s", &eml
);
841 if((out
=our_creat(b
, fsb
.st_mode
&0xfff)) < 0){
842 eml
.s
= errstr(errno
);
843 emlwrite("Can't Copy: %s", &eml
);
848 if((cb
= (char *)malloc(NLINE
*sizeof(char))) == NULL
){
849 emlwrite("Can't allocate space for copy buffer!", NULL
);
855 while(1){ /* do the copy */
856 if((n
= read(in
, cb
, NLINE
)) < 0){
857 eml
.s
= errstr(errno
);
858 emlwrite("Can't Read Copy: %s", &eml
);
860 break; /* get out now */
863 if(n
== 0) /* done! */
866 if(write(out
, cb
, n
) != n
){
867 eml
.s
= errstr(errno
);
868 emlwrite("Can't Write Copy: %s", &eml
);
882 * Open a file for writing. Return TRUE if all is well, and FALSE on error
886 ffwopen(char *fn
, int readonly
)
888 extern FIOINFO g_pico_fio
;
893 #ifndef MODE_READONLY
894 #define MODE_READONLY (0600)
898 * Call open() by hand since we don't want O_TRUNC -- it'll
899 * screw over-quota users. The strategy is to overwrite the
900 * existing file's data and call ftruncate before close to lop
904 g_pico_fio
.flags
= FIOINFO_WRITE
;
905 g_pico_fio
.name
= fn
;
907 if((fd
= our_open(fn
, O_CREAT
|O_WRONLY
, readonly
? MODE_READONLY
: 0666)) >= 0
908 && (g_pico_fio
.fp
= fdopen(fd
, "w")) != NULL
909 && fseek(g_pico_fio
.fp
, 0L, 0) == 0)
913 eml
.s
= errstr(errno
);
914 emlwrite("Cannot open file for writing: %s", &eml
);
917 if ((g_pico_fio
.fp
= our_fopen(fn
, "w")) == NULL
) {
918 emlwrite("Cannot open file for writing", NULL
);
924 our_chmod(fn
, MODE_READONLY
); /* fix access rights */
928 #endif /* _WINDOWS */
933 * Close a file. Should look at the status in all systems.
938 extern FIOINFO g_pico_fio
;
943 if((g_pico_fio
.flags
& FIOINFO_WRITE
)
944 && (fflush(g_pico_fio
.fp
) == EOF
945 || ftruncate(fileno(g_pico_fio
.fp
),
946 (off_t
) ftell(g_pico_fio
.fp
)) < 0)){
947 eml
.s
= errstr(errno
);
948 emlwwrite(_("Error preparing to close file: %s"), &eml
);
952 if (fclose(g_pico_fio
.fp
) == EOF
) {
953 eml
.s
= errstr(errno
);
954 emlwwrite(_("Error closing file: %s"), &eml
);
958 if (fclose(g_pico_fio
.fp
) != FALSE
) {
959 emlwrite("Error closing file", NULL
);
962 #endif /* _WINDOWS */
969 #define EXTEND_BLOCK 1024
973 * ffelbowroom - make sure the destination's got enough room to receive
974 * what we're about to write...
983 char s
[EXTEND_BLOCK
], *errstring
= NULL
;
985 extern FIOINFO g_pico_fio
;
987 /* Figure out how much room do we need */
988 /* first, what's total */
989 for(n
=0L, lp
=lforw(curbp
->b_linep
); lp
!= curbp
->b_linep
; lp
=lforw(lp
))
990 n
+= (llength(lp
) + 1);
992 errno
= 0; /* make sure previous error are cleared */
994 if(fstat(fileno(g_pico_fio
.fp
), &fsbuf
) == 0){
997 if(n
> 0L){ /* must be growing file, extend it */
998 memset(s
, 'U', EXTEND_BLOCK
);
999 if(fseek(g_pico_fio
.fp
, fsbuf
.st_size
, 0) == 0){
1000 for( ; n
> 0L; n
-= EXTEND_BLOCK
){
1001 x
= (n
< EXTEND_BLOCK
) ? (int) n
: EXTEND_BLOCK
;
1002 if(fwrite(s
, x
* sizeof(char), 1, g_pico_fio
.fp
) != 1){
1003 errstring
= errstr(errno
);
1009 && (fflush(g_pico_fio
.fp
) == EOF
1010 || fsync(fileno(g_pico_fio
.fp
)) < 0))
1011 errstring
= errstr(errno
);
1013 if(errstring
) /* clean up */
1014 (void) ftruncate(fileno(g_pico_fio
.fp
), (off_t
) fsbuf
.st_size
);
1015 else if(fseek(g_pico_fio
.fp
, 0L, 0) != 0)
1016 errstring
= errstr(errno
);
1019 errstring
= errstr(errno
);
1023 errstring
= errstr(errno
);
1026 snprintf(s
, sizeof(s
), "Error writing to %s: %s", g_pico_fio
.name
, errstring
);
1028 (void) fclose(g_pico_fio
.fp
);