Move all stuff from lib to contrib
[midnight-commander.git] / vfs / extfs.c
blobb3650360bc8f0cbdb68edc0a183f95830653d106
1 /* Virtual File System: External file system.
2 Copyright (C) 1995, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005,
3 2006, 2007 Free Software Foundation, Inc.
5 Written by: 1995 Jakub Jelinek
6 Rewritten by: 1998 Pavel Machek
7 Additional changes by: 1999 Andrew T. Veliath
9 This program is free software; you can redistribute it and/or
10 modify it under the terms of the GNU Library General Public License
11 as published by the Free Software Foundation; either version 2 of
12 the License, or (at your option) any later version.
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU Library General Public License for more details.
19 You should have received a copy of the GNU Library General Public
20 License along with this program; if not, write to the Free Software
21 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
23 /**
24 * \file
25 * \brief Source: Virtual File System: External file system
26 * \author Jakub Jelinek
27 * \author Pavel Machek
28 * \author Andrew T. Veliath
29 * \date 1995, 1998, 1999
32 /* Namespace: init_extfs */
34 #include <config.h>
35 #include <stdio.h>
36 #include <ctype.h>
37 #include <string.h>
38 #include <stdlib.h>
39 #include <sys/types.h>
40 #include <sys/stat.h>
41 #include <unistd.h>
42 #include <signal.h>
43 #ifdef HAVE_SYS_WAIT_H
44 #include <sys/wait.h>
45 #endif
46 #include <errno.h>
48 #include "../src/global.h"
49 #include "../src/tty.h" /* enable/disable interrupt key */
50 #include "../src/wtools.h" /* message() */
51 #include "../src/main.h" /* print_vfs_message */
52 #include "utilvfs.h"
53 #include "../src/execute.h" /* For shell_execute */
54 #include "vfs.h"
55 #include "vfs-impl.h"
56 #include "gc.h" /* vfs_rmstamp */
58 #undef ERRNOR
59 #define ERRNOR(x,y) do { my_errno = x; return y; } while(0)
61 struct inode {
62 nlink_t nlink;
63 struct entry *first_in_subdir; /* only used if this is a directory */
64 struct entry *last_in_subdir;
65 ino_t inode; /* This is inode # */
66 dev_t dev; /* This is an internal identification of the extfs archive */
67 struct archive *archive; /* And this is an archive structure */
68 dev_t rdev;
69 mode_t mode;
70 uid_t uid;
71 gid_t gid;
72 off_t size;
73 time_t mtime;
74 char *linkname;
75 time_t atime;
76 time_t ctime;
77 char *local_filename;
80 struct entry {
81 struct entry *next_in_dir;
82 struct entry *dir;
83 char *name;
84 struct inode *inode;
87 struct pseudofile {
88 struct archive *archive;
89 unsigned int has_changed:1;
90 int local_handle;
91 struct entry *entry;
94 struct archive {
95 int fstype;
96 char *name;
97 char *local_name;
98 struct stat local_stat;
99 dev_t rdev;
100 int fd_usage;
101 ino_t inode_counter;
102 struct entry *root_entry;
103 struct archive *next;
106 static struct entry *extfs_find_entry (struct entry *dir, char *name,
107 int make_dirs, int make_file);
108 static int extfs_which (struct vfs_class *me, const char *path);
109 static void extfs_remove_entry (struct entry *e);
110 static void extfs_free (vfsid id);
111 static void extfs_free_entry (struct entry *e);
113 static struct vfs_class vfs_extfs_ops;
114 static struct archive *first_archive = NULL;
115 static int my_errno = 0;
117 #define MAXEXTFS 32
118 static char *extfs_prefixes [MAXEXTFS];
119 static char extfs_need_archive [MAXEXTFS];
120 static int extfs_no = 0;
122 static void
123 extfs_fill_names (struct vfs_class *me, fill_names_f func)
125 struct archive *a = first_archive;
126 char *name;
128 (void) me;
130 while (a) {
131 name =
132 g_strconcat (a->name ? a->name : "", "#",
133 extfs_prefixes[a->fstype], (char *) NULL);
134 (*func) (name);
135 g_free (name);
136 a = a->next;
140 static void extfs_make_dots (struct entry *ent)
142 struct entry *entry = g_new (struct entry, 1);
143 struct entry *parentry = ent->dir;
144 struct inode *inode = ent->inode, *parent;
146 parent = (parentry != NULL) ? parentry->inode : NULL;
147 entry->name = g_strdup (".");
148 entry->inode = inode;
149 entry->dir = ent;
150 inode->local_filename = NULL;
151 inode->first_in_subdir = entry;
152 inode->nlink++;
153 entry->next_in_dir = g_new (struct entry, 1);
154 entry = entry->next_in_dir;
155 entry->name = g_strdup ("..");
156 inode->last_in_subdir = entry;
157 entry->next_in_dir = NULL;
158 if (parent != NULL) {
159 entry->inode = parent;
160 entry->dir = parentry;
161 parent->nlink++;
162 } else {
163 entry->inode = inode;
164 entry->dir = ent;
165 inode->nlink++;
169 static struct entry *extfs_generate_entry (struct archive *archive,
170 const char *name, struct entry *parentry, mode_t mode)
172 mode_t myumask;
173 struct inode *inode, *parent;
174 struct entry *entry;
176 parent = (parentry != NULL) ? parentry->inode : NULL;
177 entry = g_new (struct entry, 1);
179 entry->name = g_strdup (name);
180 entry->next_in_dir = NULL;
181 entry->dir = parentry;
182 if (parent != NULL) {
183 parent->last_in_subdir->next_in_dir = entry;
184 parent->last_in_subdir = entry;
186 inode = g_new (struct inode, 1);
187 entry->inode = inode;
188 inode->local_filename = NULL;
189 inode->linkname = NULL;
190 inode->last_in_subdir = NULL;
191 inode->inode = (archive->inode_counter)++;
192 inode->dev = archive->rdev;
193 inode->archive = archive;
194 myumask = umask (022);
195 umask (myumask);
196 inode->mode = mode & ~myumask;
197 mode = inode->mode;
198 inode->rdev = 0;
199 inode->uid = getuid ();
200 inode->gid = getgid ();
201 inode->size = 0;
202 inode->mtime = time (NULL);
203 inode->atime = inode->mtime;
204 inode->ctime = inode->mtime;
205 inode->nlink = 1;
206 if (S_ISDIR (mode))
207 extfs_make_dots (entry);
208 return entry;
211 #if 0
212 static void extfs_free_entries (struct entry *entry)
214 (void) entry;
215 return;
217 #endif
219 static void extfs_free_archive (struct archive *archive)
221 extfs_free_entry (archive->root_entry);
222 if (archive->local_name != NULL) {
223 struct stat my;
225 mc_stat (archive->local_name, &my);
226 mc_ungetlocalcopy (archive->name, archive->local_name,
227 archive->local_stat.st_mtime != my.st_mtime);
228 g_free(archive->local_name);
230 g_free (archive->name);
231 g_free (archive);
234 static FILE *
235 extfs_open_archive (int fstype, const char *name, struct archive **pparc)
237 static dev_t archive_counter = 0;
238 FILE *result;
239 mode_t mode;
240 char *cmd;
241 char *mc_extfsdir;
242 struct stat mystat;
243 struct archive *current_archive;
244 struct entry *root_entry;
245 char *local_name = NULL, *tmp = 0;
246 int uses_archive = extfs_need_archive[fstype];
248 if (uses_archive) {
249 if (mc_stat (name, &mystat) == -1)
250 return NULL;
251 if (!vfs_file_is_local (name)) {
252 local_name = mc_getlocalcopy (name);
253 if (local_name == NULL)
254 return NULL;
256 tmp = name_quote (name, 0);
259 mc_extfsdir = concat_dir_and_file (mc_home, "extfs" PATH_SEP_STR);
260 cmd =
261 g_strconcat (mc_extfsdir, extfs_prefixes[fstype], " list ",
262 local_name ? local_name : tmp, (char *) NULL);
263 g_free (tmp);
264 g_free (mc_extfsdir);
265 open_error_pipe ();
266 result = popen (cmd, "r");
267 g_free (cmd);
268 if (result == NULL) {
269 close_error_pipe (D_ERROR, NULL);
270 if (local_name) {
271 mc_ungetlocalcopy (name, local_name, 0);
272 g_free(local_name);
274 return NULL;
276 #ifdef ___QNXNTO__
277 setvbuf (result, NULL, _IONBF, 0);
278 #endif
280 current_archive = g_new (struct archive, 1);
281 current_archive->fstype = fstype;
282 current_archive->name = name ? g_strdup (name) : NULL;
283 current_archive->local_name = local_name;
285 if (local_name != NULL)
286 mc_stat (local_name, &current_archive->local_stat);
287 current_archive->inode_counter = 0;
288 current_archive->fd_usage = 0;
289 current_archive->rdev = archive_counter++;
290 current_archive->next = first_archive;
291 first_archive = current_archive;
292 mode = mystat.st_mode & 07777;
293 if (mode & 0400)
294 mode |= 0100;
295 if (mode & 0040)
296 mode |= 0010;
297 if (mode & 0004)
298 mode |= 0001;
299 mode |= S_IFDIR;
300 root_entry = extfs_generate_entry (current_archive, "/", NULL, mode);
301 root_entry->inode->uid = mystat.st_uid;
302 root_entry->inode->gid = mystat.st_gid;
303 root_entry->inode->atime = mystat.st_atime;
304 root_entry->inode->ctime = mystat.st_ctime;
305 root_entry->inode->mtime = mystat.st_mtime;
306 current_archive->root_entry = root_entry;
308 *pparc = current_archive;
310 return result;
314 * Main loop for reading an archive.
315 * Return 0 on success, -1 on error.
317 static int
318 extfs_read_archive (int fstype, const char *name, struct archive **pparc)
320 FILE *extfsd;
321 char *buffer;
322 struct archive *current_archive;
323 char *current_file_name, *current_link_name;
325 if ((extfsd =
326 extfs_open_archive (fstype, name, &current_archive)) == NULL) {
327 message (D_ERROR, MSG_ERROR, _("Cannot open %s archive\n%s"),
328 extfs_prefixes[fstype], name);
329 return -1;
332 buffer = g_malloc (4096);
333 while (fgets (buffer, 4096, extfsd) != NULL) {
334 struct stat hstat;
336 current_link_name = NULL;
337 if (vfs_parse_ls_lga
338 (buffer, &hstat, &current_file_name, &current_link_name)) {
339 struct entry *entry, *pent;
340 struct inode *inode;
341 char *p, *q, *cfn = current_file_name;
343 if (*cfn) {
344 if (*cfn == '/')
345 cfn++;
346 p = strchr (cfn, 0);
347 if (p != cfn && *(p - 1) == '/')
348 *(p - 1) = 0;
349 p = strrchr (cfn, '/');
350 if (p == NULL) {
351 p = cfn;
352 q = strchr (cfn, 0);
353 } else {
354 *(p++) = 0;
355 q = cfn;
357 if (S_ISDIR (hstat.st_mode)
358 && (!strcmp (p, ".") || !strcmp (p, "..")))
359 goto read_extfs_continue;
360 pent =
361 extfs_find_entry (current_archive->root_entry, q, 1,
363 if (pent == NULL) {
364 /* FIXME: Should clean everything one day */
365 g_free (buffer);
366 pclose (extfsd);
367 close_error_pipe (D_ERROR, _("Inconsistent extfs archive"));
368 return -1;
370 entry = g_new (struct entry, 1);
371 entry->name = g_strdup (p);
372 entry->next_in_dir = NULL;
373 entry->dir = pent;
374 if (pent->inode->last_in_subdir) {
375 pent->inode->last_in_subdir->next_in_dir = entry;
376 pent->inode->last_in_subdir = entry;
378 if (!S_ISLNK (hstat.st_mode) && current_link_name != NULL) {
379 pent =
380 extfs_find_entry (current_archive->root_entry,
381 current_link_name, 0, 0);
382 if (pent == NULL) {
383 /* FIXME: Should clean everything one day */
384 g_free (buffer);
385 pclose (extfsd);
386 close_error_pipe (D_ERROR,
387 _("Inconsistent extfs archive"));
388 return -1;
389 } else {
390 entry->inode = pent->inode;
391 pent->inode->nlink++;
393 } else {
394 inode = g_new (struct inode, 1);
395 entry->inode = inode;
396 inode->local_filename = NULL;
397 inode->inode = (current_archive->inode_counter)++;
398 inode->nlink = 1;
399 inode->dev = current_archive->rdev;
400 inode->archive = current_archive;
401 inode->mode = hstat.st_mode;
402 #ifdef HAVE_STRUCT_STAT_ST_RDEV
403 inode->rdev = hstat.st_rdev;
404 #else
405 inode->rdev = 0;
406 #endif
407 inode->uid = hstat.st_uid;
408 inode->gid = hstat.st_gid;
409 inode->size = hstat.st_size;
410 inode->mtime = hstat.st_mtime;
411 inode->atime = hstat.st_atime;
412 inode->ctime = hstat.st_ctime;
413 inode->first_in_subdir = NULL;
414 inode->last_in_subdir = NULL;
415 if (current_link_name != NULL
416 && S_ISLNK (hstat.st_mode)) {
417 inode->linkname = current_link_name;
418 current_link_name = NULL;
419 } else {
420 if (S_ISLNK (hstat.st_mode))
421 inode->mode &= ~S_IFLNK; /* You *DON'T* want to do this always */
422 inode->linkname = NULL;
424 if (S_ISDIR (hstat.st_mode))
425 extfs_make_dots (entry);
428 read_extfs_continue:
429 g_free (current_file_name);
430 g_free (current_link_name);
433 g_free (buffer);
435 /* Check if extfs 'list' returned 0 */
436 if (pclose (extfsd) != 0) {
437 extfs_free (current_archive);
438 close_error_pipe (D_ERROR, _("Inconsistent extfs archive"));
439 return -1;
442 close_error_pipe (D_ERROR, NULL);
443 *pparc = current_archive;
444 return 0;
448 * Dissect the path and create corresponding superblock. Note that inname
449 * can be changed and the result may point inside the original string.
451 static char *
452 extfs_get_path_mangle (struct vfs_class *me, char *inname, struct archive **archive,
453 int do_not_open)
455 char *local, *op;
456 const char *archive_name;
457 int result = -1;
458 struct archive *parc;
459 int fstype;
461 archive_name = inname;
462 vfs_split (inname, &local, &op);
464 fstype = extfs_which (me, op);
466 if (fstype == -1)
467 return NULL;
468 if (!local)
469 local = inname + strlen (inname);
472 * All filesystems should have some local archive, at least
473 * it can be '/'.
475 for (parc = first_archive; parc != NULL; parc = parc->next)
476 if (parc->name) {
477 if (!strcmp (parc->name, archive_name)) {
478 vfs_stamp (&vfs_extfs_ops, (vfsid) parc);
479 goto return_success;
483 result =
484 do_not_open ? -1 : extfs_read_archive (fstype, archive_name,
485 &parc);
486 if (result == -1)
487 ERRNOR (EIO, NULL);
489 return_success:
490 *archive = parc;
491 return local;
496 * Dissect the path and create corresponding superblock.
497 * The result should be freed.
499 static char *
500 extfs_get_path (struct vfs_class *me, const char *inname, struct archive **archive,
501 int do_not_open)
503 char *buf = g_strdup (inname);
504 char *res = extfs_get_path_mangle (me, buf, archive, do_not_open);
505 char *res2 = NULL;
506 if (res)
507 res2 = g_strdup (res);
508 g_free (buf);
509 return res2;
513 /* Return allocated path (without leading slash) inside the archive */
514 static char *extfs_get_path_from_entry (struct entry *entry)
516 struct list {
517 struct list *next;
518 char *name;
519 } *head, *p;
520 char *localpath;
521 size_t len;
523 for (len = 0, head = 0; entry->dir; entry = entry->dir) {
524 p = g_new (struct list, 1);
525 p->next = head;
526 p->name = entry->name;
527 head = p;
528 len += strlen (entry->name) + 1;
531 if (len == 0)
532 return g_strdup ("");
534 localpath = g_malloc (len);
535 *localpath = '\0';
536 while (head) {
537 strcat (localpath, head->name);
538 if (head->next)
539 strcat (localpath, "/");
540 p = head;
541 head = head->next;
542 g_free (p);
544 return (localpath);
548 struct loop_protect {
549 struct entry *entry;
550 struct loop_protect *next;
552 static int errloop;
553 static int notadir;
555 static struct entry *
556 extfs_find_entry_int (struct entry *dir, char *name,
557 struct loop_protect *list, int make_dirs, int make_file);
559 static struct entry *
560 extfs_resolve_symlinks_int (struct entry *entry,
561 struct loop_protect *list)
563 struct entry *pent;
564 struct loop_protect *looping;
566 if (!S_ISLNK (entry->inode->mode))
567 return entry;
568 for (looping = list; looping != NULL; looping = looping->next)
569 if (entry == looping->entry) { /* Here we protect us against symlink looping */
570 errloop = 1;
571 return NULL;
573 looping = g_new (struct loop_protect, 1);
574 looping->entry = entry;
575 looping->next = list;
576 pent = extfs_find_entry_int (entry->dir, entry->inode->linkname, looping, 0, 0);
577 g_free (looping);
578 if (pent == NULL)
579 my_errno = ENOENT;
580 return pent;
583 static struct entry *extfs_resolve_symlinks (struct entry *entry)
585 struct entry *res;
587 errloop = 0;
588 notadir = 0;
589 res = extfs_resolve_symlinks_int (entry, NULL);
590 if (res == NULL) {
591 if (errloop)
592 my_errno = ELOOP;
593 else if (notadir)
594 my_errno = ENOTDIR;
596 return res;
599 static const char *
600 extfs_get_archive_name (struct archive *archive)
602 const char *archive_name;
604 if (archive->local_name)
605 archive_name = archive->local_name;
606 else
607 archive_name = archive->name;
609 if (!archive_name || !*archive_name)
610 return "no_archive_name";
611 else
612 return archive_name;
615 /* Don't pass localname as NULL */
616 static int
617 extfs_cmd (const char *extfs_cmd, struct archive *archive,
618 struct entry *entry, const char *localname)
620 char *file;
621 char *quoted_file;
622 char *quoted_localname;
623 char *archive_name;
624 char *mc_extfsdir;
625 char *cmd;
626 int retval;
628 file = extfs_get_path_from_entry (entry);
629 quoted_file = name_quote (file, 0);
630 g_free (file);
631 archive_name = name_quote (extfs_get_archive_name (archive), 0);
632 quoted_localname = name_quote (localname, 0);
634 mc_extfsdir = concat_dir_and_file (mc_home, "extfs" PATH_SEP_STR);
635 cmd = g_strconcat (mc_extfsdir, extfs_prefixes[archive->fstype],
636 extfs_cmd, archive_name, " ", quoted_file, " ",
637 quoted_localname, (char *) NULL);
638 g_free (quoted_file);
639 g_free (quoted_localname);
640 g_free (mc_extfsdir);
641 g_free (archive_name);
643 open_error_pipe ();
644 retval = my_system (EXECUTE_AS_SHELL, shell, cmd);
645 g_free (cmd);
646 close_error_pipe (D_ERROR, NULL);
647 return retval;
650 static void
651 extfs_run (struct vfs_class *me, const char *file)
653 struct archive *archive = NULL;
654 char *p, *q, *archive_name, *mc_extfsdir;
655 char *cmd;
657 if ((p = extfs_get_path (me, file, &archive, 0)) == NULL)
658 return;
659 q = name_quote (p, 0);
660 g_free (p);
662 archive_name = name_quote (extfs_get_archive_name (archive), 0);
663 mc_extfsdir = concat_dir_and_file (mc_home, "extfs" PATH_SEP_STR);
664 cmd = g_strconcat (mc_extfsdir, extfs_prefixes[archive->fstype],
665 " run ", archive_name, " ", q, (char *) NULL);
666 g_free (mc_extfsdir);
667 g_free (archive_name);
668 g_free (q);
669 shell_execute (cmd, 0);
670 g_free (cmd);
673 static void *
674 extfs_open (struct vfs_class *me, const char *file, int flags, int mode)
676 struct pseudofile *extfs_info;
677 struct archive *archive = NULL;
678 char *q;
679 struct entry *entry;
680 int local_handle;
681 int created = 0;
683 if ((q = extfs_get_path (me, file, &archive, 0)) == NULL)
684 return NULL;
685 entry = extfs_find_entry (archive->root_entry, q, 0, 0);
686 if (entry == NULL && (flags & O_CREAT)) {
687 /* Create new entry */
688 entry = extfs_find_entry (archive->root_entry, q, 0, 1);
689 created = (entry != NULL);
692 g_free (q);
693 if (entry == NULL)
694 return NULL;
695 if ((entry = extfs_resolve_symlinks (entry)) == NULL)
696 return NULL;
698 if (S_ISDIR (entry->inode->mode))
699 ERRNOR (EISDIR, NULL);
701 if (entry->inode->local_filename == NULL) {
702 char *local_filename;
704 local_handle = vfs_mkstemps (&local_filename, "extfs", entry->name);
706 if (local_handle == -1)
707 return NULL;
708 close (local_handle);
710 if (!created && !(flags & O_TRUNC)
711 && extfs_cmd (" copyout ", archive, entry, local_filename)) {
712 unlink (local_filename);
713 free (local_filename);
714 my_errno = EIO;
715 return NULL;
717 entry->inode->local_filename = local_filename;
720 local_handle =
721 open (entry->inode->local_filename, NO_LINEAR (flags), mode);
722 if (local_handle == -1)
723 ERRNOR (EIO, NULL);
725 extfs_info = g_new (struct pseudofile, 1);
726 extfs_info->archive = archive;
727 extfs_info->entry = entry;
728 extfs_info->has_changed = created;
729 extfs_info->local_handle = local_handle;
731 /* i.e. we had no open files and now we have one */
732 vfs_rmstamp (&vfs_extfs_ops, (vfsid) archive);
733 archive->fd_usage++;
734 return extfs_info;
737 static ssize_t extfs_read (void *data, char *buffer, int count)
739 struct pseudofile *file = (struct pseudofile *)data;
741 return read (file->local_handle, buffer, count);
744 static int
745 extfs_close (void *data)
747 struct pseudofile *file;
748 int errno_code = 0;
749 file = (struct pseudofile *) data;
751 close (file->local_handle);
753 /* Commit the file if it has changed */
754 if (file->has_changed) {
755 if (extfs_cmd
756 (" copyin ", file->archive, file->entry,
757 file->entry->inode->local_filename))
758 errno_code = EIO;
760 struct stat file_status;
761 if (stat (file->entry->inode->local_filename, &file_status) !=
763 errno_code = EIO;
764 else
765 file->entry->inode->size = file_status.st_size;
768 file->entry->inode->mtime = time (NULL);
771 file->archive->fd_usage--;
772 if (!file->archive->fd_usage)
773 vfs_stamp_create (&vfs_extfs_ops, file->archive);
775 g_free (data);
776 if (errno_code)
777 ERRNOR (EIO, -1);
778 return 0;
781 #define RECORDSIZE 512
783 static struct entry*
784 extfs_find_entry_int (struct entry *dir, char *name,
785 struct loop_protect *list, int make_dirs, int make_file)
787 struct entry *pent, *pdir;
788 char *p, *q, *name_end;
789 char c;
791 if (*name == '/') { /* Handle absolute paths */
792 name++;
793 dir = dir->inode->archive->root_entry;
796 pent = dir;
797 p = name;
798 name_end = name + strlen (name);
799 q = strchr (p, '/');
800 c = '/';
801 if (!q)
802 q = strchr (p, 0);
804 for (; pent != NULL && c && *p; ){
805 c = *q;
806 *q = 0;
808 if (strcmp (p, ".")){
809 if (!strcmp (p, ".."))
810 pent = pent->dir;
811 else {
812 if ((pent = extfs_resolve_symlinks_int (pent, list))==NULL){
813 *q = c;
814 return NULL;
816 if (!S_ISDIR (pent->inode->mode)){
817 *q = c;
818 notadir = 1;
819 return NULL;
821 pdir = pent;
822 for (pent = pent->inode->first_in_subdir; pent; pent = pent->next_in_dir)
823 /* Hack: I keep the original semanthic unless
824 q+1 would break in the strchr */
825 if (!strcmp (pent->name, p)){
826 if (q + 1 > name_end){
827 *q = c;
828 notadir = !S_ISDIR (pent->inode->mode);
829 return pent;
831 break;
834 /* When we load archive, we create automagically
835 * non-existant directories
837 if (pent == NULL && make_dirs) {
838 pent = extfs_generate_entry (dir->inode->archive, p, pdir, S_IFDIR | 0777);
840 if (pent == NULL && make_file) {
841 pent = extfs_generate_entry (dir->inode->archive, p, pdir, S_IFREG | 0666);
845 /* Next iteration */
846 *q = c;
847 p = q + 1;
848 q = strchr (p, '/');
849 if (!q)
850 q = strchr (p, 0);
852 if (pent == NULL)
853 my_errno = ENOENT;
854 return pent;
857 static struct entry *extfs_find_entry (struct entry *dir, char *name, int make_dirs, int make_file)
859 struct entry *res;
861 errloop = 0;
862 notadir = 0;
863 res = extfs_find_entry_int (dir, name, NULL, make_dirs, make_file);
864 if (res == NULL) {
865 if (errloop)
866 my_errno = ELOOP;
867 else if (notadir)
868 my_errno = ENOTDIR;
870 return res;
874 static int extfs_errno (struct vfs_class *me)
876 (void) me;
878 return my_errno;
881 static void * extfs_opendir (struct vfs_class *me, const char *dirname)
883 struct archive *archive = NULL;
884 char *q;
885 struct entry *entry;
886 struct entry **info;
888 if ((q = extfs_get_path (me, dirname, &archive, 0)) == NULL)
889 return NULL;
890 entry = extfs_find_entry (archive->root_entry, q, 0, 0);
891 g_free (q);
892 if (entry == NULL)
893 return NULL;
894 if ((entry = extfs_resolve_symlinks (entry)) == NULL)
895 return NULL;
896 if (!S_ISDIR (entry->inode->mode)) ERRNOR (ENOTDIR, NULL);
898 info = g_new (struct entry *, 2);
899 info[0] = entry->inode->first_in_subdir;
900 info[1] = entry->inode->first_in_subdir;
902 return info;
905 static void * extfs_readdir(void *data)
907 static union vfs_dirent dir;
908 struct entry **info = (struct entry **) data;
910 if (!*info)
911 return NULL;
913 g_strlcpy(dir.dent.d_name, (*info)->name, MC_MAXPATHLEN);
915 compute_namelen(&dir.dent);
916 *info = (*info)->next_in_dir;
918 return (void *) &dir;
921 static int extfs_closedir (void *data)
923 g_free (data);
924 return 0;
927 static void extfs_stat_move (struct stat *buf, const struct inode *inode)
929 buf->st_dev = inode->dev;
930 buf->st_ino = inode->inode;
931 buf->st_mode = inode->mode;
932 buf->st_nlink = inode->nlink;
933 buf->st_uid = inode->uid;
934 buf->st_gid = inode->gid;
935 #ifdef HAVE_STRUCT_STAT_ST_RDEV
936 buf->st_rdev = inode->rdev;
937 #endif
938 buf->st_size = inode->size;
939 #ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
940 buf->st_blksize = RECORDSIZE;
941 #endif
942 #ifdef HAVE_STRUCT_STAT_ST_BLOCKS
943 buf->st_blocks = (inode->size + RECORDSIZE - 1) / RECORDSIZE;
944 #endif
945 buf->st_atime = inode->atime;
946 buf->st_mtime = inode->mtime;
947 buf->st_ctime = inode->ctime;
950 static int
951 extfs_internal_stat (struct vfs_class *me, const char *path, struct stat *buf,
952 int resolve)
954 struct archive *archive;
955 char *q;
956 struct entry *entry;
957 char *path2 = g_strdup (path);
958 int result = -1;
960 if ((q = extfs_get_path_mangle (me, path2, &archive, 0)) == NULL)
961 goto cleanup;
962 entry = extfs_find_entry (archive->root_entry, q, 0, 0);
963 if (entry == NULL)
964 goto cleanup;
965 if (resolve && (entry = extfs_resolve_symlinks (entry)) == NULL)
966 goto cleanup;
967 extfs_stat_move (buf, entry->inode);
968 result = 0;
969 cleanup:
970 g_free (path2);
971 return result;
974 static int extfs_stat (struct vfs_class *me, const char *path, struct stat *buf)
976 return extfs_internal_stat (me, path, buf, 1);
979 static int extfs_lstat (struct vfs_class *me, const char *path, struct stat *buf)
981 return extfs_internal_stat (me, path, buf, 0);
984 static int extfs_fstat (void *data, struct stat *buf)
986 struct pseudofile *file = (struct pseudofile *)data;
988 extfs_stat_move (buf, file->entry->inode);
989 return 0;
992 static int
993 extfs_readlink (struct vfs_class *me, const char *path, char *buf, size_t size)
995 struct archive *archive;
996 char *q;
997 size_t len;
998 struct entry *entry;
999 char *mpath = g_strdup (path);
1000 int result = -1;
1002 if ((q = extfs_get_path_mangle (me, mpath, &archive, 0)) == NULL)
1003 goto cleanup;
1004 entry = extfs_find_entry (archive->root_entry, q, 0, 0);
1005 if (entry == NULL)
1006 goto cleanup;
1007 if (!S_ISLNK (entry->inode->mode)) {
1008 me->verrno = EINVAL;
1009 goto cleanup;
1011 len = strlen (entry->inode->linkname);
1012 if (size < len)
1013 len = size;
1014 /* readlink() does not append a NUL character to buf */
1015 memcpy (buf, entry->inode->linkname, result = len);
1016 cleanup:
1017 g_free (mpath);
1018 return result;
1021 static int extfs_chmod (struct vfs_class *me, const char *path, int mode)
1023 (void) me;
1024 (void) path;
1025 (void) mode;
1026 return 0;
1029 static ssize_t extfs_write (void *data, const char *buf, int nbyte)
1031 struct pseudofile *file = (struct pseudofile *)data;
1033 file->has_changed = 1;
1034 return write (file->local_handle, buf, nbyte);
1037 static int extfs_unlink (struct vfs_class *me, const char *file)
1039 struct archive *archive;
1040 char *q, *mpath = g_strdup (file);
1041 struct entry *entry;
1042 int result = -1;
1044 if ((q = extfs_get_path_mangle (me, mpath, &archive, 0)) == NULL)
1045 goto cleanup;
1046 entry = extfs_find_entry (archive->root_entry, q, 0, 0);
1047 if (entry == NULL)
1048 goto cleanup;
1049 if ((entry = extfs_resolve_symlinks (entry)) == NULL)
1050 goto cleanup;
1051 if (S_ISDIR (entry->inode->mode)) {
1052 me->verrno = EISDIR;
1053 goto cleanup;
1055 if (extfs_cmd (" rm ", archive, entry, "")){
1056 my_errno = EIO;
1057 goto cleanup;
1059 extfs_remove_entry (entry);
1060 result = 0;
1061 cleanup:
1062 g_free (mpath);
1063 return result;
1066 static int extfs_mkdir (struct vfs_class *me, const char *path, mode_t mode)
1068 struct archive *archive;
1069 char *q, *mpath = g_strdup(path);
1070 struct entry *entry;
1071 int result = -1;
1073 (void) mode;
1075 if ((q = extfs_get_path_mangle (me, mpath, &archive, 0)) == NULL)
1076 goto cleanup;
1077 entry = extfs_find_entry (archive->root_entry, q, 0, 0);
1078 if (entry != NULL) {
1079 me->verrno = EEXIST;
1080 goto cleanup;
1082 entry = extfs_find_entry (archive->root_entry, q, 1, 0);
1083 if (entry == NULL)
1084 goto cleanup;
1085 if ((entry = extfs_resolve_symlinks (entry)) == NULL)
1086 goto cleanup;
1087 if (!S_ISDIR (entry->inode->mode)) {
1088 me->verrno = ENOTDIR;
1089 goto cleanup;
1092 if (extfs_cmd (" mkdir ", archive, entry, "")){
1093 my_errno = EIO;
1094 extfs_remove_entry (entry);
1095 goto cleanup;
1097 result = 0;
1098 cleanup:
1099 g_free (mpath);
1100 return result;
1103 static int extfs_rmdir (struct vfs_class *me, const char *path)
1105 struct archive *archive;
1106 char *q, *mpath = g_strdup(path);
1107 struct entry *entry;
1108 int result = -1;
1110 if ((q = extfs_get_path_mangle (me, mpath, &archive, 0)) == NULL)
1111 goto cleanup;
1112 entry = extfs_find_entry (archive->root_entry, q, 0, 0);
1113 if (entry == NULL)
1114 goto cleanup;
1115 if ((entry = extfs_resolve_symlinks (entry)) == NULL)
1116 goto cleanup;
1117 if (!S_ISDIR (entry->inode->mode)) {
1118 me->verrno = ENOTDIR;
1119 goto cleanup;
1122 if (extfs_cmd (" rmdir ", archive, entry, "")){
1123 my_errno = EIO;
1124 goto cleanup;
1126 extfs_remove_entry (entry);
1127 result = 0;
1128 cleanup:
1129 g_free (mpath);
1130 return result;
1133 static int
1134 extfs_chdir (struct vfs_class *me, const char *path)
1136 struct archive *archive = NULL;
1137 char *q;
1138 struct entry *entry;
1140 my_errno = ENOTDIR;
1141 if ((q = extfs_get_path (me, path, &archive, 0)) == NULL)
1142 return -1;
1143 entry = extfs_find_entry (archive->root_entry, q, 0, 0);
1144 g_free (q);
1145 if (!entry)
1146 return -1;
1147 entry = extfs_resolve_symlinks (entry);
1148 if ((!entry) || (!S_ISDIR (entry->inode->mode)))
1149 return -1;
1150 my_errno = 0;
1151 return 0;
1154 static off_t extfs_lseek (void *data, off_t offset, int whence)
1156 struct pseudofile *file = (struct pseudofile *) data;
1158 return lseek (file->local_handle, offset, whence);
1161 static vfsid
1162 extfs_getid (struct vfs_class *me, const char *path)
1164 struct archive *archive = NULL;
1165 char *p;
1167 if (!(p = extfs_get_path (me, path, &archive, 1)))
1168 return NULL;
1169 g_free (p);
1170 return (vfsid) archive;
1173 static int extfs_nothingisopen (vfsid id)
1175 if (((struct archive *)id)->fd_usage <= 0)
1176 return 1;
1177 return 0;
1180 static void extfs_remove_entry (struct entry *e)
1182 int i = --(e->inode->nlink);
1183 struct entry *pe, *ent, *prev;
1185 if (S_ISDIR (e->inode->mode) && e->inode->first_in_subdir != NULL) {
1186 struct entry *f = e->inode->first_in_subdir;
1187 e->inode->first_in_subdir = NULL;
1188 extfs_remove_entry (f);
1190 pe = e->dir;
1191 if (e == pe->inode->first_in_subdir)
1192 pe->inode->first_in_subdir = e->next_in_dir;
1194 prev = NULL;
1195 for (ent = pe->inode->first_in_subdir; ent && ent->next_in_dir;
1196 ent = ent->next_in_dir)
1197 if (e == ent->next_in_dir) {
1198 prev = ent;
1199 break;
1201 if (prev)
1202 prev->next_in_dir = e->next_in_dir;
1203 if (e == pe->inode->last_in_subdir)
1204 pe->inode->last_in_subdir = prev;
1206 if (i <= 0) {
1207 if (e->inode->local_filename != NULL) {
1208 unlink (e->inode->local_filename);
1209 free (e->inode->local_filename);
1211 g_free (e->inode->linkname);
1212 g_free (e->inode);
1215 g_free (e->name);
1216 g_free (e);
1219 static void extfs_free_entry (struct entry *e)
1221 int i = --(e->inode->nlink);
1222 if (S_ISDIR (e->inode->mode) && e->inode->first_in_subdir != NULL) {
1223 struct entry *f = e->inode->first_in_subdir;
1225 e->inode->first_in_subdir = NULL;
1226 extfs_free_entry (f);
1228 if (i <= 0) {
1229 if (e->inode->local_filename != NULL) {
1230 unlink (e->inode->local_filename);
1231 free (e->inode->local_filename);
1233 g_free (e->inode->linkname);
1234 g_free (e->inode);
1236 if (e->next_in_dir != NULL)
1237 extfs_free_entry (e->next_in_dir);
1238 g_free (e->name);
1239 g_free (e);
1242 static void extfs_free (vfsid id)
1244 struct archive *parc;
1245 struct archive *archive = (struct archive *)id;
1247 if (archive == first_archive) {
1248 first_archive = archive->next;
1249 } else {
1250 for (parc = first_archive; parc != NULL; parc = parc->next)
1251 if (parc->next == archive) {
1252 parc->next = archive->next;
1253 break;
1256 extfs_free_archive (archive);
1259 static char *
1260 extfs_getlocalcopy (struct vfs_class *me, const char *path)
1262 struct pseudofile *fp =
1263 (struct pseudofile *) extfs_open (me, path, O_RDONLY, 0);
1264 char *p;
1266 if (fp == NULL)
1267 return NULL;
1268 if (fp->entry->inode->local_filename == NULL) {
1269 extfs_close ((void *) fp);
1270 return NULL;
1272 p = g_strdup (fp->entry->inode->local_filename);
1273 fp->archive->fd_usage++;
1274 extfs_close ((void *) fp);
1275 return p;
1278 static int
1279 extfs_ungetlocalcopy (struct vfs_class *me, const char *path,
1280 const char *local, int has_changed)
1282 struct pseudofile *fp =
1283 (struct pseudofile *) extfs_open (me, path, O_RDONLY, 0);
1285 if (fp == NULL)
1286 return 0;
1287 if (!strcmp (fp->entry->inode->local_filename, local)) {
1288 fp->archive->fd_usage--;
1289 fp->has_changed |= has_changed;
1290 extfs_close ((void *) fp);
1291 return 0;
1292 } else {
1293 /* Should not happen */
1294 extfs_close ((void *) fp);
1295 return 0;
1300 static int extfs_init (struct vfs_class *me)
1302 FILE *cfg;
1303 char *mc_extfsini;
1304 char key[256];
1306 (void) me;
1308 mc_extfsini = concat_dir_and_file (mc_home, "extfs" PATH_SEP_STR "extfs.ini");
1309 cfg = fopen (mc_extfsini, "r");
1311 /* We may not use vfs_die() message or message or similar,
1312 * UI is not initialized at this time and message would not
1313 * appear on screen. */
1314 if (!cfg) {
1315 fprintf (stderr, _("Warning: file %s not found\n"), mc_extfsini);
1316 g_free (mc_extfsini);
1317 return 0;
1320 extfs_no = 0;
1321 while (extfs_no < MAXEXTFS && fgets (key, sizeof (key), cfg)) {
1322 char *c;
1324 /* Handle those with a trailing ':', those flag that the
1325 * file system does not require an archive to work
1328 if (*key == '[') {
1329 fprintf(stderr, "Warning: You need to update your %s file.\n",
1330 mc_extfsini);
1331 fclose(cfg);
1332 g_free (mc_extfsini);
1333 return 0;
1335 if (*key == '#' || *key == '\n')
1336 continue;
1338 if ((c = strchr (key, '\n'))){
1339 *c-- = 0;
1340 } else { /* Last line without newline or strlen (key) > 255 */
1341 c = &key [strlen (key) - 1];
1343 extfs_need_archive [extfs_no] = !(*c == ':');
1344 if (*c == ':')
1345 *c = 0;
1346 if (!(*key))
1347 continue;
1349 extfs_prefixes [extfs_no++] = g_strdup (key);
1351 fclose(cfg);
1352 g_free (mc_extfsini);
1353 return 1;
1356 static int extfs_which (struct vfs_class *me, const char *path)
1358 int i;
1360 (void) me;
1362 for (i = 0; i < extfs_no; i++)
1363 if (!strcmp (path, extfs_prefixes [i]))
1364 return i;
1365 return -1;
1368 static void extfs_done (struct vfs_class *me)
1370 int i;
1371 struct archive *ar;
1373 (void) me;
1375 for (ar = first_archive; ar != NULL;) {
1376 extfs_free ((vfsid) ar);
1377 ar = first_archive;
1380 for (i = 0; i < extfs_no; i++ )
1381 g_free (extfs_prefixes [i]);
1382 extfs_no = 0;
1385 static int
1386 extfs_setctl (struct vfs_class *me, const char *path, int ctlop, void *arg)
1388 (void) arg;
1390 if (ctlop == VFS_SETCTL_RUN) {
1391 extfs_run (me, path);
1392 return 1;
1394 return 0;
1397 void
1398 init_extfs (void)
1400 vfs_extfs_ops.name = "extfs";
1401 vfs_extfs_ops.init = extfs_init;
1402 vfs_extfs_ops.done = extfs_done;
1403 vfs_extfs_ops.fill_names = extfs_fill_names;
1404 vfs_extfs_ops.which = extfs_which;
1405 vfs_extfs_ops.open = extfs_open;
1406 vfs_extfs_ops.close = extfs_close;
1407 vfs_extfs_ops.read = extfs_read;
1408 vfs_extfs_ops.write = extfs_write;
1409 vfs_extfs_ops.opendir = extfs_opendir;
1410 vfs_extfs_ops.readdir = extfs_readdir;
1411 vfs_extfs_ops.closedir = extfs_closedir;
1412 vfs_extfs_ops.stat = extfs_stat;
1413 vfs_extfs_ops.lstat = extfs_lstat;
1414 vfs_extfs_ops.fstat = extfs_fstat;
1415 vfs_extfs_ops.chmod = extfs_chmod;
1416 vfs_extfs_ops.readlink = extfs_readlink;
1417 vfs_extfs_ops.unlink = extfs_unlink;
1418 vfs_extfs_ops.chdir = extfs_chdir;
1419 vfs_extfs_ops.ferrno = extfs_errno;
1420 vfs_extfs_ops.lseek = extfs_lseek;
1421 vfs_extfs_ops.getid = extfs_getid;
1422 vfs_extfs_ops.nothingisopen = extfs_nothingisopen;
1423 vfs_extfs_ops.free = extfs_free;
1424 vfs_extfs_ops.getlocalcopy = extfs_getlocalcopy;
1425 vfs_extfs_ops.ungetlocalcopy = extfs_ungetlocalcopy;
1426 vfs_extfs_ops.mkdir = extfs_mkdir;
1427 vfs_extfs_ops.rmdir = extfs_rmdir;
1428 vfs_extfs_ops.setctl = extfs_setctl;
1429 vfs_register_class (&vfs_extfs_ops);