Polish translation was updated.
[elinks.git] / src / util / file.c
blob8015b6cc8675ceea2e1c889a7e521adb0b98f375
1 /* File utilities */
3 #ifdef HAVE_CONFIG_H
4 #include "config.h"
5 #endif
7 #include <ctype.h>
8 #include <errno.h>
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include <sys/types.h>
13 #include <sys/stat.h> /* OS/2 needs this after sys/types.h */
14 #ifdef HAVE_DIRENT_H
15 #include <dirent.h> /* OS/2 needs this after sys/types.h */
16 #endif
17 #ifdef HAVE_FCNTL_H
18 #include <fcntl.h> /* OS/2 needs this after sys/types.h */
19 #endif
20 #ifdef HAVE_PWD_H
21 #include <pwd.h>
22 #endif
23 #ifdef HAVE_UNISTD_H
24 #include <unistd.h>
25 #endif
27 #ifdef TIME_WITH_SYS_TIME
28 #ifdef HAVE_SYS_TIME_H
29 #include <sys/time.h>
30 #endif
31 #ifdef HAVE_TIME_H
32 #include <time.h>
33 #endif
34 #else
35 #if defined(TM_IN_SYS_TIME) && defined(HAVE_SYS_TIME_H)
36 #include <sys/time.h>
37 #elif defined(HAVE_TIME_H)
38 #include <time.h>
39 #endif
40 #endif
42 #include "elinks.h"
44 #include "osdep/osdep.h"
45 #include "util/conv.h"
46 #include "util/error.h"
47 #include "util/file.h"
48 #include "util/memory.h"
49 #include "util/string.h"
52 /* Not that these two would be so useful for portability (they are ANSI C) but
53 * they encapsulate the lowlevel stuff (need for <unistd.h>) nicely. */
55 int
56 file_exists(const unsigned char *filename)
58 #ifdef HAVE_ACCESS
59 return access(filename, F_OK) >= 0;
60 #else
61 struct stat buf;
63 return stat(filename, &buf) >= 0;
64 #endif
67 int
68 file_can_read(const unsigned char *filename)
70 #ifdef HAVE_ACCESS
71 return access(filename, R_OK) >= 0;
72 #else
73 FILE *f = fopen(filename, "rb");
74 int ok = !!f;
76 if (f) fclose(f);
77 return ok;
78 #endif
81 int
82 file_is_dir(const unsigned char *filename)
84 struct stat st;
86 if (stat(filename, &st))
87 return 0;
89 return S_ISDIR(st.st_mode);
92 unsigned char *
93 get_filename_position(unsigned char *filename)
95 unsigned char *pos;
97 assert(filename);
98 if_assert_failed return NULL;
100 for (pos = filename; *pos; pos++)
101 if (dir_sep(*pos)) filename = pos + 1;
103 return filename;
106 unsigned char *
107 expand_tilde(unsigned char *filename)
109 struct string file;
111 if (!init_string(&file)) return NULL;
113 if (filename[0] == '~') {
114 if (!filename[1] || dir_sep(filename[1])) {
115 unsigned char *home = getenv("HOME");
117 if (home) {
118 add_to_string(&file, home);
119 filename++;
121 #ifdef HAVE_GETPWNAM
122 } else {
123 struct passwd *passwd = NULL;
124 unsigned char *user = filename + 1;
125 int userlen = 0;
127 while (user[userlen] && !dir_sep(user[userlen]))
128 userlen++;
130 user = memacpy(user, userlen);
131 if (user) {
132 passwd = getpwnam(user);
133 mem_free(user);
136 if (passwd && passwd->pw_dir) {
137 add_to_string(&file, passwd->pw_dir);
138 filename += 1 + userlen;
140 #endif
144 add_to_string(&file, filename);
146 return file.source;
149 unsigned char *
150 get_unique_name(unsigned char *fileprefix)
152 unsigned char *file = fileprefix;
153 int fileprefixlen = strlen(fileprefix);
154 int memtrigger = 1;
155 int suffix = 1;
156 int digits = 0;
158 while (file_exists(file)) {
159 if (!(suffix < memtrigger)) {
160 if (suffix >= 10000)
161 INTERNAL("Too big suffix in get_unique_name().");
162 memtrigger *= 10;
163 digits++;
165 if (file != fileprefix) mem_free(file);
166 file = mem_alloc(fileprefixlen + 2 + digits);
167 if (!file) return NULL;
169 memcpy(file, fileprefix, fileprefixlen);
170 file[fileprefixlen] = '.';
173 longcat(&file[fileprefixlen + 1], NULL, suffix, digits + 1, 0);
174 suffix++;
177 return file;
180 unsigned char *
181 get_tempdir_filename(unsigned char *name)
183 unsigned char *tmpdir = getenv("TMPDIR");
185 if (!tmpdir || !*tmpdir) tmpdir = getenv("TMP");
186 if (!tmpdir || !*tmpdir) tmpdir = getenv("TEMPDIR");
187 if (!tmpdir || !*tmpdir) tmpdir = getenv("TEMP");
188 if (!tmpdir || !*tmpdir) tmpdir = "/tmp";
190 return straconcat(tmpdir, "/", name, NULL);
193 unsigned char *
194 file_read_line(unsigned char *line, size_t *size, FILE *file, int *lineno)
196 size_t offset = 0;
198 if (!line) {
199 line = mem_alloc(MAX_STR_LEN);
200 if (!line)
201 return NULL;
203 *size = MAX_STR_LEN;
206 while (fgets(line + offset, *size - offset, file)) {
207 unsigned char *linepos = strchr(line + offset, '\n');
209 if (!linepos) {
210 /* Test if the line buffer should be increase because
211 * it was continued and could not fit. */
212 unsigned char *newline;
213 int next = getc(file);
215 if (next == EOF) {
216 /* We are on the last line. */
217 (*lineno)++;
218 return line;
221 /* Undo our dammage */
222 ungetc(next, file);
223 offset = *size - 1;
224 *size += MAX_STR_LEN;
226 newline = mem_realloc(line, *size);
227 if (!newline)
228 break;
229 line = newline;
230 continue;
233 /* A whole line was read. Fetch next into the buffer if
234 * the line is 'continued'. */
235 (*lineno)++;
237 while (line < linepos && isspace(*linepos))
238 linepos--;
240 if (*linepos != '\\') {
241 *(linepos + 1) = '\0';
242 return line;
244 offset = linepos - line - 1;
247 mem_free_if(line);
248 return NULL;
252 /* Some mkstemp() implementations do not set safe permissions,
253 * so it may result in temporary files readable by all users.
254 * This may be a problem when textarea fields are edited through
255 * an external editor (see src/viewer/text/textarea.c).
257 * From 2001-12-23 mkstemp(3) gnu man page:
259 * ...
260 * The file is then created with mode read/write and permissions 0666
261 * (glibc 2.0.6 and earlier), 0600 (glibc 2.0.7 and later).
262 * ...
264 * NOTES:
265 * The old behaviour (creating a file with mode 0666) may be a security
266 * risk, especially since other Unix flavours use 0600, and somebody
267 * might overlook this detail when porting programs.
268 * More generally, the POSIX specification does not say anything
269 * about file modes, so the application should make sure its umask is
270 * set appropriately before calling mkstemp.
273 safe_mkstemp(unsigned char *template)
275 #ifndef CONFIG_OS_WIN32
276 mode_t saved_mask = umask(S_IXUSR | S_IRWXG | S_IRWXO);
277 #endif
278 int fd = mkstemp(template);
279 #ifndef CONFIG_OS_WIN32
280 umask(saved_mask);
281 #endif
282 return fd;
286 /* The stat_* functions set the various attributes for directory entries. */
288 static inline void
289 stat_type(struct string *string, struct stat *stp)
291 unsigned char c = '?';
293 if (stp) {
294 if (S_ISDIR(stp->st_mode)) c = 'd';
295 else if (S_ISREG(stp->st_mode)) c = '-';
296 #ifdef S_ISBLK
297 else if (S_ISBLK(stp->st_mode)) c = 'b';
298 #endif
299 #ifdef S_ISCHR
300 else if (S_ISCHR(stp->st_mode)) c = 'c';
301 #endif
302 #ifdef S_ISFIFO
303 else if (S_ISFIFO(stp->st_mode)) c = 'p';
304 #endif
305 #ifdef S_ISLNK
306 else if (S_ISLNK(stp->st_mode)) c = 'l';
307 #endif
308 #ifdef S_ISSOCK
309 else if (S_ISSOCK(stp->st_mode)) c = 's';
310 #endif
311 #ifdef S_ISNWK
312 else if (S_ISNWK(stp->st_mode)) c = 'n';
313 #endif
316 add_char_to_string(string, c);
319 static inline void
320 stat_mode(struct string *string, struct stat *stp)
322 #ifdef FS_UNIX_RIGHTS
323 unsigned char rwx[10] = "---------";
325 if (stp) {
326 mode_t mode = stp->st_mode;
327 unsigned int shift;
329 /* Set permissions attributes for user, group and other */
330 for (shift = 0; shift <= 6; shift += 3) {
331 mode_t m = mode << shift;
333 if (m & S_IRUSR) rwx[shift + 0] = 'r';
334 if (m & S_IWUSR) rwx[shift + 1] = 'w';
335 if (m & S_IXUSR) rwx[shift + 2] = 'x';
338 #ifdef S_ISUID
339 if (mode & S_ISUID)
340 rwx[2] = (mode & S_IXUSR) ? 's' : 'S';
341 #endif
342 #ifdef S_ISGID
343 if (mode & S_ISGID)
344 rwx[5] = (mode & S_IXGRP) ? 's' : 'S';
345 #endif
346 #ifdef S_ISVTX
347 if (mode & S_ISVTX)
348 rwx[8] = (mode & S_IXOTH) ? 't' : 'T';
349 #endif
351 add_to_string(string, rwx);
352 #endif
353 add_char_to_string(string, ' ');
356 static inline void
357 stat_links(struct string *string, struct stat *stp)
359 #ifdef FS_UNIX_HARDLINKS
360 if (!stp) {
361 add_to_string(string, " ");
362 } else {
363 unsigned char lnk[64];
365 ulongcat(lnk, NULL, stp->st_nlink, 3, ' ');
366 add_to_string(string, lnk);
367 add_char_to_string(string, ' ');
369 #endif
372 static inline void
373 stat_user(struct string *string, struct stat *stp)
375 #ifdef FS_UNIX_USERS
376 static unsigned char last_user[64];
377 static int last_uid = -1;
379 if (!stp) {
380 add_to_string(string, " ");
381 return;
384 if (stp->st_uid != last_uid) {
385 struct passwd *pwd = getpwuid(stp->st_uid);
387 if (!pwd || !pwd->pw_name)
388 /* ulongcat() can't pad from right. */
389 sprintf(last_user, "%-8d", (int) stp->st_uid);
390 else
391 sprintf(last_user, "%-8.8s", pwd->pw_name);
393 last_uid = stp->st_uid;
396 add_to_string(string, last_user);
397 add_char_to_string(string, ' ');
398 #endif
401 static inline void
402 stat_group(struct string *string, struct stat *stp)
404 #ifdef FS_UNIX_USERS
405 static unsigned char last_group[64];
406 static int last_gid = -1;
408 if (!stp) {
409 add_to_string(string, " ");
410 return;
413 if (stp->st_gid != last_gid) {
414 struct group *grp = getgrgid(stp->st_gid);
416 if (!grp || !grp->gr_name)
417 /* ulongcat() can't pad from right. */
418 sprintf(last_group, "%-8d", (int) stp->st_gid);
419 else
420 sprintf(last_group, "%-8.8s", grp->gr_name);
422 last_gid = stp->st_gid;
425 add_to_string(string, last_group);
426 add_char_to_string(string, ' ');
427 #endif
430 static inline void
431 stat_size(struct string *string, struct stat *stp)
433 /* Check if st_size will cause overflow. */
434 /* FIXME: See bug 497 for info about support for big files. */
435 if (!stp || stp->st_size != (unsigned long) stp->st_size) {
436 add_to_string(string, " ");
438 } else {
439 unsigned char size[64];
440 int width = 9;
442 /* First try to fit the file size into 8 digits ... */
443 width = ulongcat(size, NULL, stp->st_size, width, ' ');
444 if (0 < width && width < sizeof(size)) {
445 /* ... if that is not enough expand the size buffer.
446 * This will only break the column alignment of the size
447 * attribute if really needed. */
448 ulongcat(size, NULL, stp->st_size, width, ' ');
451 add_to_string(string, size);
452 add_char_to_string(string, ' ');
456 static inline void
457 stat_date(struct string *string, struct stat *stp)
459 #ifdef HAVE_STRFTIME
460 if (stp) {
461 time_t current_time = time(NULL);
462 time_t when = stp->st_mtime;
463 unsigned char *fmt;
465 if (current_time > when + 6L * 30L * 24L * 60L * 60L
466 || current_time < when - 60L * 60L)
467 fmt = "%b %e %Y";
468 else
469 fmt = "%b %e %H:%M";
471 add_date_to_string(string, fmt, &when);
472 add_char_to_string(string, ' ');
473 return;
475 #endif
476 add_to_string(string, " ");
480 static int
481 compare_dir_entries(struct directory_entry *d1, struct directory_entry *d2)
483 if (d1->name[0] == '.' && d1->name[1] == '.' && !d1->name[2]) return -1;
484 if (d2->name[0] == '.' && d2->name[1] == '.' && !d2->name[2]) return 1;
485 if (d1->attrib[0] == 'd' && d2->attrib[0] != 'd') return -1;
486 if (d1->attrib[0] != 'd' && d2->attrib[0] == 'd') return 1;
487 return strcmp(d1->name, d2->name);
491 /* This function decides whether a file should be shown in directory listing or
492 * not. Returns according boolean value. */
493 static inline int
494 file_visible(unsigned char *name, int get_hidden_files, int is_root_directory)
496 /* Always show everything not beginning with a dot. */
497 if (name[0] != '.')
498 return 1;
500 /* Always hide the "." directory. */
501 if (name[1] == '\0')
502 return 0;
504 /* Always show the ".." directory (but for root directory). */
505 if (name[1] == '.' && name[2] == '\0')
506 return !is_root_directory;
508 /* Others like ".x" or "..foo" are shown if get_hidden_files
509 * == 1. */
510 return get_hidden_files;
513 /* First information such as permissions is gathered for each directory entry.
514 * All entries are then sorted and finally the sorted entries are added to the
515 * @data->fragment one by one. */
516 struct directory_entry *
517 get_directory_entries(unsigned char *dirname, int get_hidden)
519 struct directory_entry *entries = NULL;
520 DIR *directory;
521 int size = 0;
522 struct dirent *entry;
523 int is_root_directory = dirname[0] == '/' && !dirname[1];
525 directory = opendir(dirname);
526 if (!directory) return NULL;
528 while ((entry = readdir(directory))) {
529 struct stat st, *stp;
530 struct directory_entry *new_entries;
531 unsigned char *name;
532 struct string attrib;
534 if (!file_visible(entry->d_name, get_hidden, is_root_directory))
535 continue;
537 new_entries = mem_realloc(entries, (size + 2) * sizeof(*new_entries));
538 if (!new_entries) continue;
539 entries = new_entries;
541 /* We allocate the full path because it is used in a few places
542 * which means less allocation although a bit more short term
543 * memory usage. */
544 name = straconcat(dirname, entry->d_name, NULL);
545 if (!name) continue;
547 if (!init_string(&attrib)) {
548 mem_free(name);
549 continue;
552 #ifdef FS_UNIX_SOFTLINKS
553 stp = (lstat(name, &st)) ? NULL : &st;
554 #else
555 stp = (stat(name, &st)) ? NULL : &st;
556 #endif
558 stat_type(&attrib, stp);
559 stat_mode(&attrib, stp);
560 stat_links(&attrib, stp);
561 stat_user(&attrib, stp);
562 stat_group(&attrib, stp);
563 stat_size(&attrib, stp);
564 stat_date(&attrib, stp);
566 entries[size].name = name;
567 entries[size].attrib = attrib.source;
568 size++;
571 closedir(directory);
573 if (!size) {
574 /* We may have allocated space for entries but added none. */
575 mem_free_if(entries);
576 return NULL;
579 qsort(entries, size, sizeof(*entries),
580 (int (*)(const void *, const void *)) compare_dir_entries);
582 memset(&entries[size], 0, sizeof(*entries));
584 return entries;
587 /* Recursively create directories in a path. The last element in the path is
588 * taken to be a filename, and simply ignored */
590 mkalldirs(const unsigned char *path)
592 int pos, len, ret = 0;
593 unsigned char *p;
595 if (!*path) return -1;
597 /* Make a copy of path, to be able to write to it. Otherwise, the
598 * function is unsafe if called with a read-only char *argument. */
599 len = strlen(path) + 1;
600 p = fmem_alloc(len);
601 if (!p) return -1;
602 memcpy(p, path, len);
604 for (pos = 1; p[pos]; pos++) {
605 unsigned char separator = p[pos];
607 if (!dir_sep(separator))
608 continue;
610 p[pos] = 0;
612 ret = mkdir(p, S_IRWXU);
614 p[pos] = separator;
616 if (ret < 0 && errno != EEXIST)
617 break;
620 fmem_free(p);
621 return ret;