root/trunk/src/z-file.c

Revision 894, 15.3 kB (checked in by takkaria, 5 days ago)

Make OS X always give us the correct home directory. Fixes #384, with thanks to Rowan Beentje, roustk, and Kenneth Boyd.

Line 
1 /*
2  * File: z-file.c
3  * Purpose: Low-level file (and directory) handling
4  *
5  * Copyright (c) 1997-2007 Ben Harrison, pelpel, Andrew Sidwell, Matthew Jones
6  *
7  * This work is free software; you can redistribute it and/or modify it
8  * under the terms of either:
9  *
10  * a) the GNU General Public License as published by the Free Software
11  *    Foundation, version 2, or
12  *
13  * b) the "Angband licence":
14  *    This software may be copied and distributed for educational, research,
15  *    and not for profit purposes provided that this copyright and statement
16  *    are included in all such copies.  Other copyrights may also apply.
17  */
18 #include "z-file.h"
19 #include "z-virt.h"
20 #include "z-util.h"
21 #include "z-form.h"
22
23 #ifndef RISCOS
24 # include <sys/types.h>
25 #endif
26
27 #ifdef WINDOWS
28 # include <windows.h>
29 # include <io.h>
30 #endif
31
32 #ifdef MACH_O_CARBON
33 # include <SystemConfiguration/SCDynamicStoreCopySpecific.h>
34 #endif
35
36 #ifdef HAVE_FCNTL_H
37 # include <fcntl.h>
38 #endif
39
40 #ifdef HAVE_DIRENT_H
41 # include <sys/types.h>
42 # include <dirent.h>
43 #endif
44
45 #ifdef HAVE_STAT
46 # include <sys/stat.h>
47 #endif
48
49 /*
50  * Player info
51  */
52 int player_uid;
53 int player_egid;
54
55
56
57
58 /*
59  * Drop permissions
60  */
61 void safe_setuid_drop(void)
62 {
63 #ifdef SET_UID
64 # if defined(HAVE_SETRESGID)
65
66         if (setresgid(-1, getgid(), -1) != 0)
67                 quit("setegid(): cannot drop permissions correctly!");
68
69 # else
70
71         if (setegid(getgid()) != 0)
72                 quit("setegid(): cannot drop permissions correctly!");
73
74 # endif
75 #endif /* SET_UID */
76 }
77
78
79 /*
80  * Grab permissions
81  */
82 void safe_setuid_grab(void)
83 {
84 #ifdef SET_UID
85 # if defined(HAVE_SETRESGID)
86
87         if (setresgid(-1, player_egid, -1) != 0)
88                 quit("setegid(): cannot grab permissions correctly!");
89
90 # elif defined(HAVE_SETEGID)
91
92         if (setegid(player_egid) != 0)
93                 quit("setegid(): cannot grab permissions correctly!");
94
95 # endif
96 #endif /* SET_UID */
97 }
98
99
100
101
102 /*
103  * Apply special system-specific processing before dealing with a filename.
104  */
105 static void path_parse(char *buf, size_t max, cptr file)
106 {
107 #ifndef RISCOS
108
109         /* Accept the filename */
110         my_strcpy(buf, file, max);
111
112 #else /* RISCOS */
113
114         /* Defined in main-ros.c */
115         char *riscosify_name(const char *path);
116         my_strcpy(buf, riscosify_name(path), max);
117
118 #endif /* !RISCOS */
119 }
120
121
122
123 static void path_process(char *buf, size_t len, size_t *cur_len, const char *path)
124 {
125 #if defined(SET_UID) || defined(USE_PRIVATE_PATHS)
126
127         /* Home directory on Unixes */
128         if (path[0] == '~')
129         {
130                 const char *s;
131                 const char *username = path + 1;
132
133                 struct passwd *pw;
134                 char user[128];
135
136                 /* Look for non-user portion of the file */
137                 s = strstr(username, PATH_SEP);
138                 if (s)
139                 {
140                         int i;
141
142                         /* Keep username a decent length */
143                         if (s >= username + sizeof(user)) return;
144
145                         for (i = 0; username < s; ++i) user[i] = *username++;
146                         user[i] = '\0';
147                         username = user;
148                 }
149
150                 /* Fallback -- try the "current" user */
151                 if (username[0] == '\0')
152                         username = getlogin();
153
154                 /* Look up a user (or "current" user) */
155 #ifndef MACH_O_CARBON
156                 if (username) pw = getpwnam(username);
157                 else          pw = getpwuid(getuid());
158
159 #else /* MACH_O_CARBON */
160
161                 /* On Macs look up the console user to avoid problems with invalid root detection */
162                 uid_t uid;
163                 SCDynamicStoreCopyConsoleUser(NULL, &uid, NULL);
164                 pw = getpwuid(uid);
165 #endif /* !MACH_O_CARBON */
166                
167                 if (!pw) return;
168
169                 /* Copy across */
170                 strnfcat(buf, len, cur_len, "%s%s", pw->pw_dir, PATH_SEP);
171                 if (s) strnfcat(buf, len, cur_len, "%s", s);
172         }
173         else
174
175 #endif
176
177         {
178                 strnfcat(buf, len, cur_len, "%s", path);
179         }
180 }
181
182
183
184
185 /*
186  * Create a new path string by appending a 'leaf' to 'base'.
187  *
188  * On Unixes, we convert a tidle at the beginning of a basename to mean the
189  * directory, complicating things a little, but better now than later.
190  *
191  * Remember to free the return value.
192  */
193 size_t path_build(char *buf, size_t len, const char *base, const char *leaf)
194 {
195         size_t cur_len = 0;
196         buf[0] = '\0';
197
198         if (!leaf || !leaf[0])
199         {
200                 if (base && base[0])
201                         path_process(buf, len, &cur_len, base);
202
203                 return cur_len;
204         }
205
206
207         /*
208          * If the leafname starts with the seperator,
209          *   or with the tilde (on Unix),
210          *   or there's no base path,
211          * We use the leafname only.
212          */
213 #if defined(SET_UID) || defined(USE_PRIVATE_PATHS)
214         if ((!base || !base[0]) || prefix(leaf, PATH_SEP) || leaf[0] == '~')
215 #else
216         if ((!base || !base[0]) || prefix(leaf, PATH_SEP))
217 #endif
218         {
219                 path_process(buf, len, &cur_len, leaf);
220                 return cur_len;
221         }
222
223
224         /* There is both a relative leafname and a base path from which it is relative */
225         path_process(buf, len, &cur_len, base);
226         strnfcat(buf, len, &cur_len, "%s", PATH_SEP);
227         path_process(buf, len, &cur_len, leaf);
228
229         return cur_len;
230 }
231
232
233
234 /*** Support for byte-swapping for endian-indepedent files ***/
235
236 static bool is_bigendian()
237 {
238         int i = 1;
239         char *p = (char *)&i;
240
241         if (p[0] != 1)
242                 return TRUE;
243
244         return FALSE;
245 }
246
247 u16b flip_u16b(u16b arg)
248 {
249         u16b ret;
250         char *in = (char *)&arg;
251         char *out = (char *)&ret;
252
253         if (is_bigendian())
254                 return arg;
255
256         out[0] = in[1];
257         out[1] = in[0];
258
259         return ret;
260 }
261
262 u32b flip_u32b(u32b arg)
263 {
264         u32b ret;
265         char *in = (char *)&arg;
266         char *out = (char *)&ret;
267
268         if (is_bigendian())
269                 return arg;
270
271         out[0] = in[3];
272         out[1] = in[2];
273         out[2] = in[1];
274         out[3] = in[0];
275
276         return ret;
277 }
278
279
280
281 /*** File-handling API ***/
282
283 /* On Windows, fwrite() and fread() are broken. */
284 #if defined(WINDOWS) || defined(SET_UID)
285 # define HAVE_WRITE
286 # define HAVE_READ
287 #endif
288
289
290 /* Private structure to hold file pointers and useful info. */
291 struct ang_file
292 {
293         FILE *fh;
294         char *fname;
295         file_mode mode;
296 };
297
298
299
300 /** Utility functions **/
301
302 /*
303  * Delete file 'fname'.
304  */
305 bool file_delete(const char *fname)
306 {
307         char buf[1024];
308
309         /* Get the system-specific paths */
310         path_parse(buf, sizeof(buf), fname);
311
312         return (remove(buf) == 0);
313 }
314
315 /*
316  * Delete file 'fname' to 'newname'.
317  */
318 bool file_move(const char *fname, const char *newname)
319 {
320         char buf[1024];
321         char aux[1024];
322
323         /* Get the system-specific paths */
324         path_parse(buf, sizeof(buf), fname);
325         path_parse(aux, sizeof(aux), newname);
326
327         return (rename(buf, aux) == 0);
328 }
329
330
331 /*
332  * Decide whether a file exists or not.
333  */
334 bool file_exists(const char *fname);
335
336 #if defined(HAVE_STAT)
337
338 bool file_exists(const char *fname)
339 {
340         struct stat st;
341         return (stat(fname, &st) == 0);
342 }
343
344 #elif defined(WINDOWS)
345
346 bool file_exists(const char *fname)
347 {
348         char path[MAX_PATH];
349         DWORD attrib;
350
351         /* API says we mustn't pass anything larger than MAX_PATH */
352         my_strcpy(path, s, sizeof(path));
353
354         attrib = GetFileAttributes(path);
355         if (attrib == INVALID_FILE_NAME) return FALSE;
356         if (attrib & FILE_ATTRIBUTE_DIRECTORY) return FALSE;
357
358         return TRUE;
359 }
360
361 #else
362
363 bool file_exists(const char *fname)
364 {
365         ang_file *f = file_open(fname, MODE_READ, 0);
366
367         if (f) file_close(f);
368         return (f ? TRUE : FALSE);
369 }
370
371 #endif
372
373
374 #ifndef RISCOS
375 #ifdef HAVE_STAT
376
377 /*
378  * Return TRUE if first is newer than second, FALSE otherwise.
379  */
380 bool file_newer(const char *first, const char *second)
381 {
382         struct stat first_stat, second_stat;
383
384         bool second_exists = stat(second, &second_stat) ? FALSE : TRUE;
385         bool first_exists = stat(first, &first_stat) ? FALSE : TRUE;
386
387         /*
388          * If the first doesn't exist, the first is not newer;
389          * If the second doesn't exist, the first is always newer.
390          */
391         if (!first_exists)  return FALSE;
392         if (!second_exists) return TRUE;
393
394         if (first_stat.st_mtime >= second_stat.st_mtime)
395                 return TRUE;
396
397         return FALSE;
398 }
399
400 #else /* HAVE_STAT */
401
402 bool file_newer(const char *first, const char *second)
403 {
404         /* Assume newer */
405         return FALSE;
406 }
407
408 #endif /* HAVE_STAT */
409 #endif /* RISCOS */
410
411
412
413
414 /** File-handle functions **/
415
416 /*
417  * Open file 'fname', in mode 'mode', with filetype 'ftype'.
418  * Returns file handle or NULL.
419  */
420 ang_file *file_open(const char *fname, file_mode mode, file_type ftype)
421 {
422         ang_file *f = ZNEW(ang_file);
423         char modestr[3] = "__";
424         char buf[1024];
425
426         /* Get the system-specific path */
427         path_parse(buf, sizeof(buf), fname);
428
429         switch (mode)
430         {
431                 case MODE_WRITE:
432                         modestr[0] = 'w';
433                         modestr[1] = 'b';
434                         break;
435                 case MODE_READ:
436                         modestr[0] = 'r';
437                         modestr[1] = 'b';
438                         break;
439                 case MODE_APPEND:
440                         modestr[0] = 'w';
441                         modestr[1] = 'a';
442                         break;
443                 default:
444                         break;
445         }
446
447         f->fh = fopen(buf, modestr);
448
449         if (f->fh == NULL)
450         {
451                 FREE(f);
452                 return NULL;
453         }
454
455         f->fname = string_make(buf);
456         f->mode = mode;
457
458 #ifdef MACH_O_CARBON
459         extern void fsetfileinfo(cptr path, u32b fcreator, u32b ftype);
460
461         /* OS X uses its own kind of filetypes */
462         if (mode != MODE_READ)
463         {
464                 u32b mac_type = 'TEXT';
465
466                 if (ftype == FTYPE_RAW) mac_type = 'DATA';
467                 else if (ftype == FTYPE_SAVE) mac_type = 'SAVE';
468
469                 fsetfileinfo(buf, 'A271', mac_type);
470         }
471 #endif
472
473 #if defined(RISCOS) && 0
474         /* do something for RISC OS here? */
475         if (mode != MODE_READ)
476                 File_SetType(n, ftype);
477 #endif
478
479         return f;
480 }
481
482 /*
483  * Close file handle 'f'.
484  */
485 bool file_close(ang_file *f)
486 {
487         if (fclose(f->fh) != 0)
488                 return FALSE;
489
490         FREE(f->fname);
491         FREE(f);
492
493         return TRUE;
494 }
495
496
497
498 /** Locking functions **/
499
500 /*
501  * Lock a file using POSIX locks, on platforms where this is supported.
502  */
503 void file_lock(ang_file *f)
504 {
505 #if defined(HAVE_FCNTL_H) && defined(SET_UID)
506         struct flock lock;
507         lock.l_type = (f->mode == MODE_READ ? F_RDLCK : F_WRLCK);
508         lock.l_whence = SEEK_SET;
509         lock.l_start = 0;
510         lock.l_len = 0;
511         lock.l_pid = 0;
512         fcntl(fileno(f->fh), F_SETLKW, &lock);
513 #endif /* HAVE_FCNTL_H && SET_UID */
514 }
515
516 /*
517  * Unlock a file locked using file_lock().
518  */
519 void file_unlock(ang_file *f)
520 {
521 #if defined(HAVE_FCNTL_H) && defined(SET_UID)
522         struct flock lock;
523         lock.l_type = F_UNLCK;
524         lock.l_whence = SEEK_SET;
525         lock.l_start = 0;
526         lock.l_len = 0;
527         lock.l_pid = 0;
528         fcntl(fileno(f->fh), F_SETLK, &lock);
529 #endif /* HAVE_FCNTL_H && SET_UID */
530 }
531
532
533 /** Byte-based IO and functions **/
534
535 /*
536  * Seek to location 'pos' in file 'f'.
537  */
538 bool file_seek(ang_file *f, u32b pos)
539 {
540         return (fseek(f->fh, pos, SEEK_SET) == 0);
541 }
542
543 /*
544  * Read a single, 8-bit character from file 'f'.
545  */
546 bool file_readc(ang_file *f, byte *b)
547 {
548         int i = fgetc(f->fh);
549
550         if (i == EOF)
551                 return FALSE;
552
553         *b = (byte)i;
554         return TRUE;
555 }
556
557 /*
558  * Write a single, 8-bit character 'b' to file 'f'.
559  */
560 bool file_writec(ang_file *f, byte b)
561 {
562         return file_write(f, (const char *)&b, 1);
563 }
564
565 /*
566  * Read 'n' bytes from file 'f' into array 'buf'.
567  */
568 size_t file_read(ang_file *f, char *buf, size_t n);
569
570 #ifdef HAVE_READ
571
572 #ifndef SET_UID
573 # define READ_BUF_SIZE 16384
574 #endif
575
576 size_t file_read(ang_file *f, char *buf, size_t n)
577 {
578         int fd = fileno(f->fh);
579
580 #ifndef SET_UID
581
582         while (n >= READ_BUF_SIZE)
583         {
584                 if (read(fd, buf, READ_BUF_SIZE) != READ_BUF_SIZE)
585                         return FALSE;
586
587                 buf += READ_BUF_SIZE;
588                 n -= READ_BUF_SIZE;
589         }
590
591 #endif /* !SET_UID */
592
593         if (read(fd, buf, n) != (int)n)
594                 return FALSE;
595
596         return TRUE;
597 }
598
599 #else
600
601 size_t file_read(ang_file *f, char *buf, size_t n)
602 {
603         return fread(buf, 1, n, f->fh);
604 }
605
606 #endif
607
608
609 /*
610  * Append 'n' bytes of array 'buf' to file 'f'.
611  */
612 bool file_write(ang_file *f, const char *buf, size_t n);
613
614 #ifdef HAVE_WRITE
615
616 #ifndef SET_UID
617 # define WRITE_BUF_SIZE 16384
618 #endif
619
620 bool file_write(ang_file *f, const char *buf, size_t n)
621 {
622         int fd = fileno(f->fh);
623
624 #ifndef SET_UID
625
626         while (n >= WRITE_BUF_SIZE)
627         {
628                 if (write(fd, buf, WRITE_BUF_SIZE) != WRITE_BUF_SIZE)
629                         return FALSE;
630
631                 buf += WRITE_BUF_SIZE;
632                 n -= WRITE_BUF_SIZE;
633         }
634
635 #endif /* !SET_UID */
636
637         if (write(fd, buf, n) != (int)n)
638                 return FALSE;
639
640         return TRUE;
641 }
642
643 #else
644
645 bool file_write(ang_file *f, const char *buf, size_t n)
646 {
647         return (fwrite(buf, 1, n, f->fh) == n);
648 }
649
650 #endif
651
652
653 /** Line-based IO **/
654
655 /*
656  * Read a line of text from file 'f' into buffer 'buf' of size 'n' bytes.
657  *
658  * Support both \r\n and \n as line endings, but not the outdated \r that used
659  * to be used on Macs.  Replace non-printables with '?', and \ts with ' '.
660  */
661 #define TAB_COLUMNS 4
662
663 bool file_getl(ang_file *f, char *buf, size_t len)
664 {
665         bool seen_cr = FALSE;
666         byte b;
667         size_t i = 0;
668
669         /* Leave a byte for the terminating 0 */
670         size_t max_len = len - 1;
671
672         while (i < max_len)
673         {
674                 char c;
675
676                 if (!file_readc(f, &b))
677                 {
678                         buf[i] = '\0';
679                         return (i == 0) ? FALSE : TRUE;
680                 }
681
682                 c = (char) b;
683
684                 if (c == '\r')
685                 {
686                         seen_cr = TRUE;
687                         continue;
688                 }
689
690                 if (seen_cr && c != '\n')
691                 {
692                         fseek(f->fh, -1, SEEK_CUR);
693                         buf[i] = '\0';
694                         return TRUE;
695                 }
696
697                 if (c == '\n')
698                 {
699                         buf[i] = '\0';
700                         return TRUE;
701                 }
702
703                 /* Expand tabs */
704                 if (c == '\t')
705                 {
706                         /* Next tab stop */
707                         size_t tabstop = ((i + TAB_COLUMNS) / TAB_COLUMNS) * TAB_COLUMNS;
708                         if (tabstop >= len) break;
709
710                         /* Convert to spaces */
711                         while (i < tabstop)
712                                 buf[i++] = ' ';
713
714                         continue;
715                 }
716
717                 /* Ignore non-printables */
718                 if (!isprint((unsigned char) c))
719                 {
720                         buf[i++] = '?';
721                         continue;
722                 }
723
724                 buf[i++] = c;
725         }
726
727         return TRUE;
728 }
729
730 /*
731  * Append a line of text 'buf' to the end of file 'f', using system-dependent
732  * line ending.
733  */
734 bool file_put(ang_file *f, const char *buf)
735 {
736         return file_write(f, buf, strlen(buf));
737 }
738
739 /*
740  * Append a formatted line of text to the end of file 'f'.
741  */
742 bool file_putf(ang_file *f, const char *fmt, ...)
743 {
744         char buf[1024];
745         va_list vp;
746
747         va_start(vp, fmt);
748         (void)vstrnfmt(buf, sizeof(buf), fmt, vp);
749         va_end(vp);
750
751         return file_put(f, buf);
752 }
753
754
755
756 /*** Directory scanning API ***/
757
758 /*
759  * For information on what these are meant to do, please read the header file.
760  */
761
762 #ifdef WINDOWS
763
764
765 /* System-specific struct */
766 struct ang_dir
767 {
768         HANDLE h;
769         char *first_file;
770 };
771
772 ang_dir *my_dopen(const char *dirname)
773 {
774         WIN32_FIND_DATA fd;
775         HANDLE h;
776         ang_dir *dir;
777        
778         /* Try to open it */
779         h = FindFirstFile(format("%s\\*", dirname), &fd);
780
781         /* Abort */
782         if (h == INVALID_HANDLE_VALUE)
783                 return NULL;
784
785         /* Set up the handle */
786         dir = ZNEW(ang_dir);
787         dir->h = h;
788         dir->first_file = string_make(fd.cFileName);
789
790         /* Success */
791         return dir;
792 }
793
794 bool my_dread(ang_dir *dir, char *fname, size_t len)
795 {
796         WIN32_FIND_DATA fd;
797         BOOL ok;
798
799         /* Try the first file */
800         if (dir->first_file)
801         {
802                 /* Copy the string across, then free it */
803                 my_strcpy(fname, dir->first_file, len);
804                 FREE(dir->first_file);
805
806                 /* Wild success */
807                 return TRUE;
808         }
809
810         /* Try the next file */
811         while (1)
812         {
813                 ok = FindNextFile(dir->h, &fd);
814                 if (!ok) return FALSE;
815
816                 /* Skip directories */
817                 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ||
818                     strcmp(fd.cFileName, ".") == 0 ||
819                     strcmp(fd.cFileName, "..") == 0)
820                         continue;
821
822                 /* Take this one */
823                 break;
824         }
825
826         /* Copy name */
827         my_strcpy(fname, fd.cFileName, len);
828
829         return TRUE;
830 }
831
832 void my_dclose(ang_dir *dir)
833 {
834         /* Close directory */
835         if (dir->h)
836                 FindClose(dir->h);
837
838         /* Free memory */
839         FREE(dir->first_file);
840         FREE(dir);
841 }
842
843 #endif /* WINDOWS */
844
845
846 #ifdef HAVE_DIRENT_H
847
848 /* Define our ang_dir type */
849 struct ang_dir
850 {
851         DIR *d;
852         char *dirname;
853 };
854
855 ang_dir *my_dopen(const char *dirname)
856 {
857         ang_dir *dir;
858         DIR *d;
859
860         /* Try to open the directory */
861         d = opendir(dirname);
862         if (!d) return NULL;
863
864         /* Allocate memory for the handle */
865         dir = ZNEW(ang_dir);
866         if (!dir)
867         {
868                 closedir(d);
869                 return NULL;
870         }
871
872         /* Set up the handle */
873         dir->d = d;
874         dir->dirname = string_make(dirname);
875
876         /* Success */
877         return dir;
878 }
879
880 bool my_dread(ang_dir *dir, char *fname, size_t len)
881 {
882         struct dirent *entry;
883         struct stat filedata;
884         char path[1024];
885
886         assert(dir != NULL);
887
888         /* Try reading another entry */
889         while (1)
890         {
891                 entry = readdir(dir->d);
892                 if (!entry) return FALSE;
893
894                 path_build(path, sizeof path, dir->dirname, entry->d_name);
895            
896                 /* Check to see if it exists */
897                 if (stat(path, &filedata) != 0)
898                         continue;
899
900                 /* Check to see if it's a directory */
901                 if (S_ISDIR(filedata.st_mode))
902                         continue;
903
904                 /* We've found something worth returning */
905                 break;
906         }
907
908         /* Copy the filename */
909         my_strcpy(fname, entry->d_name, len);
910
911         return TRUE;
912 }
913
914 void my_dclose(ang_dir *dir)
915 {
916         /* Close directory */
917         if (dir->d)
918                 closedir(dir->d);
919
920         /* Free memory */
921         FREE(dir->dirname);
922         FREE(dir);
923 }
924
925 #endif /* HAVE_DIRENT_H */
926
Note: See TracBrowser for help on using the browser.