1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2 /* extfs-method.c - Integrated support for various archiving methods via
5 Copyright (C) 1999 Free Software Foundation
7 The Gnome Library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU Library General Public License as
9 published by the Free Software Foundation; either version 2 of the
10 License, or (at your option) any later version.
12 The Gnome Library is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Library General Public License for more details.
17 You should have received a copy of the GNU Library General Public
18 License along with the Gnome Library; see the file COPYING.LIB. If not,
19 write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20 Boston, MA 02111-1307, USA.
22 Author: Ettore Perazzoli <ettore@comm2000.it>
23 Based on the ideas from the extfs system implemented in the GNU Midnight
26 /* TODO: Support archives on non-local file systems. Although I am not
27 that sure it's such a terrific idea anymore. */
33 #include <glib/ghash.h>
34 #include <glib/gmessages.h>
35 #include <glib/gstrfuncs.h>
36 #include <glib/gthread.h>
37 #include <glib/gutils.h>
38 #include <libgnomevfs/gnome-vfs-cancellable-ops.h>
39 #include <libgnomevfs/gnome-vfs-mime.h>
40 #include <libgnomevfs/gnome-vfs-module-shared.h>
41 #include <libgnomevfs/gnome-vfs-module.h>
42 #include <libgnomevfs/gnome-vfs-ops.h>
43 #include <libgnomevfs/gnome-vfs-parse-ls.h>
44 #include <libgnomevfs/gnome-vfs-private-utils.h>
48 #include <sys/types.h>
55 /* Read up to (and including) a TERMINATOR from STREAM into *LINEPTR
56 (and null-terminate it). *LINEPTR is a pointer returned from malloc (or
57 NULL), pointing to *N characters of space. It is realloc'd as
58 necessary. Returns the number of characters read (not including the
59 null terminator), or -1 on error or EOF. */
64 getdelim (lineptr, n, terminator, stream)
73 if (lineptr == NULL || n == NULL)
81 /* Make sure we have a line buffer to start with. */
82 if (*lineptr == NULL || *n < 2) /* !seen and no buf yet need 2 chars. */
87 line = realloc (*lineptr, MAX_CANON);
105 register int c = getc (stream);
108 else if ((*p++ = c) == terminator)
112 /* Need to enlarge the line buffer. */
115 line = realloc (line, size);
127 /* Return a partial line since we got an error in the middle. */
136 #define EXTFS_COMMAND_DIR LIBDIR "/vfs/2.0/extfs"
138 /* Our private handle struct. */
139 struct _ExtfsHandle {
140 GnomeVFSOpenMode open_mode;
141 GnomeVFSHandle *vfs_handle;
144 typedef struct _ExtfsHandle ExtfsHandle;
146 #define VFS_HANDLE(method_handle) \
147 ((ExtfsHandle *) method_handle)->vfs_handle
149 /* List of current handles, for cleaning up in `vfs_module_shutdown_2()'. */
150 static GList *handle_list;
151 G_LOCK_DEFINE_STATIC (handle_list);
154 struct _ExtfsDirectoryEntry {
156 GnomeVFSFileInfo *info;
158 typedef struct _ExtfsDirectoryEntry ExtfsDirectoryEntry;
160 struct _ExtfsDirectory {
165 typedef struct _ExtfsDirectory ExtfsDirectory;
167 /* Hash of directory lists. */
168 /* Notice that, for the way the code currently works, this is useless. But I
169 plan to add some caching (i.e. keep directory lists for a while to make
170 visiting easier) in the future, so this will help. The main reason for not
171 doing so now right is that we need some support for expiration in the GNOME
173 static GHashTable *uri_to_directory_hash;
174 G_LOCK_DEFINE_STATIC (uri_to_directory_hash);
177 #define ERROR_IF_NOT_LOCAL(uri) \
178 if ((!uri) || (!uri->parent) || (!(uri)->parent->method_string) || strcmp ((uri)->parent->method_string, "file") != 0) \
179 return GNOME_VFS_ERROR_NOT_SUPPORTED;
181 static GnomeVFSResult
182 extfs_handle_close (ExtfsHandle *handle)
184 GnomeVFSResult close_result;
186 close_result = gnome_vfs_close (handle->vfs_handle);
188 /* Maybe we could use the VFS functions here. */
189 if (unlink (handle->local_path) != 0)
190 g_warning ("Cannot unlink temporary file `%s': %s",
191 handle->local_path, g_strerror (errno));
193 g_free (handle->local_path);
200 quote_file_name (const gchar *file_name)
208 for (p = file_name; *p != 0; p++) {
215 new = g_malloc (len + 1);
218 for (p = file_name, q = new + 1; *p != 0; p++) {
237 get_script_path (const GnomeVFSURI *uri)
239 return g_strconcat (EXTFS_COMMAND_DIR, "/", uri->method_string, NULL);
243 strip_separators (const gchar *pth)
245 gchar *path_buf = g_strdup(pth);
246 gchar *path = path_buf, *p, *s;
248 while (*path == G_DIR_SEPARATOR) path++;
250 p = path+strlen(path)-1;
251 while (p > path && *p == G_DIR_SEPARATOR) *(p--) = '\0';
260 get_basename (const gchar *pth)
262 gchar *path = strip_separators(pth);
265 s = g_path_get_basename (path);
273 get_dirname (const gchar *pth)
278 gchar *path = strip_separators(pth);
280 p = strrchr (path, G_DIR_SEPARATOR);
290 /* URI -> directory hash table handling. */
293 free_directory_entries (GList *entries)
297 for (p = entries; p != NULL; p = p->next) {
298 ExtfsDirectoryEntry *entry;
301 gnome_vfs_file_info_unref (entry->info);
302 g_free (entry->directory);
306 g_list_free (entries);
309 static ExtfsDirectory *
310 extfs_directory_new (const GnomeVFSURI *uri,
314 ExtfsDirectory *existing;
316 G_LOCK (uri_to_directory_hash);
318 /* First check that a new directory has not been registered for this
320 existing = g_hash_table_lookup (uri_to_directory_hash, uri);
321 if (existing != NULL) {
322 free_directory_entries (entries);
323 G_UNLOCK (uri_to_directory_hash);
327 new = g_new (ExtfsDirectory, 1);
329 new->uri = gnome_vfs_uri_dup (uri);
330 new->entries = entries;
333 g_hash_table_insert (uri_to_directory_hash, new->uri, new);
335 G_UNLOCK (uri_to_directory_hash);
342 extfs_directory_unref (ExtfsDirectory *dir)
344 g_return_if_fail (dir->ref_count > 0);
346 G_LOCK (uri_to_directory_hash);
349 if (dir->ref_count == 0) {
350 g_hash_table_remove (uri_to_directory_hash, dir->uri);
352 free_directory_entries (dir->entries);
353 gnome_vfs_uri_unref (dir->uri);
357 G_UNLOCK (uri_to_directory_hash);
361 static ExtfsDirectory *
362 extfs_directory_lookup (GnomeVFSURI *uri)
364 ExtfsDirectory *directory;
366 G_LOCK (uri_to_directory_hash);
367 directory = g_hash_table_lookup (uri_to_directory_hash, uri);
368 if (directory != NULL)
369 directory->ref_count++;
370 G_UNLOCK (uri_to_directory_hash);
376 static GnomeVFSResult
377 do_open (GnomeVFSMethod *method,
378 GnomeVFSMethodHandle **method_handle,
380 GnomeVFSOpenMode mode,
381 GnomeVFSContext *context)
383 GnomeVFSResult result;
384 GnomeVFSProcessResult process_result;
385 GnomeVFSHandle *temp_handle;
388 const gchar *stored_name;
389 const gchar *args[6];
392 gint process_exit_value;
394 ERROR_IF_NOT_LOCAL (uri);
396 /* TODO: Support write mode. */
397 if (mode & GNOME_VFS_OPEN_WRITE)
398 return GNOME_VFS_ERROR_READ_ONLY_FILE_SYSTEM;
400 if (uri->text == NULL)
401 return GNOME_VFS_ERROR_INVALID_URI;
403 if (uri->method_string == NULL)
404 return GNOME_VFS_ERROR_INTERNAL;
406 stored_name = uri->text;
407 while (*stored_name == G_DIR_SEPARATOR)
410 if (*stored_name == '\0')
411 return GNOME_VFS_ERROR_INVALID_URI;
413 result = gnome_vfs_create_temp ("/tmp/extfs", &temp_name, &temp_handle);
414 if (result != GNOME_VFS_OK)
417 handle = g_new (ExtfsHandle, 1);
418 handle->vfs_handle = temp_handle;
419 handle->open_mode = mode;
420 handle->local_path = temp_name;
422 script_path = get_script_path (uri);
424 args[0] = uri->method_string;
426 args[2] = uri->parent->text;
427 args[3] = (gchar *) stored_name;
431 /* FIXME bugzilla.eazel.com 1223:
433 * Ettore needs to elaborate here some more, it is not clear what this
436 process_result = gnome_vfs_process_run_cancellable
437 (script_path, args, GNOME_VFS_PROCESS_CLOSEFDS,
438 context ? gnome_vfs_context_get_cancellation(context) : NULL,
439 &process_exit_value);
441 switch (process_result) {
442 case GNOME_VFS_PROCESS_RUN_OK:
443 if (process_exit_value == 0) {
444 result = GNOME_VFS_OK;
447 /* This is not very accurate, but it should be
449 result = GNOME_VFS_ERROR_NOT_FOUND;
453 case GNOME_VFS_PROCESS_RUN_CANCELLED:
454 result = GNOME_VFS_ERROR_CANCELLED;
457 case GNOME_VFS_PROCESS_RUN_SIGNALED:
458 result = GNOME_VFS_ERROR_INTERRUPTED;
461 case GNOME_VFS_PROCESS_RUN_STOPPED:
462 result = GNOME_VFS_ERROR_INTERRUPTED;
465 case GNOME_VFS_PROCESS_RUN_ERROR:
467 /* If we get `GNOME_VFS_PROCESS_RUN_ERROR', it means that we
468 could not run the executable for some weird reason.*/
469 result = GNOME_VFS_ERROR_INTERNAL;
475 extfs_handle_close (handle);
477 *method_handle = (GnomeVFSMethodHandle *) handle;
478 G_LOCK (handle_list);
479 handle_list = g_list_prepend (handle_list, handle);
480 G_UNLOCK (handle_list);
483 g_free (script_path);
487 static GnomeVFSResult
488 do_create (GnomeVFSMethod *method,
489 GnomeVFSMethodHandle **method_handle,
491 GnomeVFSOpenMode mode,
494 GnomeVFSContext *context)
496 return GNOME_VFS_ERROR_READ_ONLY_FILE_SYSTEM;
499 static GnomeVFSResult
500 do_close (GnomeVFSMethod *method,
501 GnomeVFSMethodHandle *method_handle,
502 GnomeVFSContext *context)
504 ExtfsHandle *extfs_handle;
505 GnomeVFSResult result;
507 extfs_handle = (ExtfsHandle *) method_handle;
508 result = extfs_handle_close (extfs_handle);
510 if (result == GNOME_VFS_OK) {
511 G_LOCK (handle_list);
512 handle_list = g_list_remove (handle_list, extfs_handle);
513 G_UNLOCK (handle_list);
519 static GnomeVFSResult
520 do_read (GnomeVFSMethod *method,
521 GnomeVFSMethodHandle *method_handle,
523 GnomeVFSFileSize num_bytes,
524 GnomeVFSFileSize *bytes_read,
525 GnomeVFSContext *context)
527 return gnome_vfs_read_cancellable (VFS_HANDLE (method_handle),
528 buffer, num_bytes, bytes_read,
532 static GnomeVFSResult
533 do_write (GnomeVFSMethod *method,
534 GnomeVFSMethodHandle *method_handle,
535 gconstpointer buffer,
536 GnomeVFSFileSize num_bytes,
537 GnomeVFSFileSize *bytes_written,
538 GnomeVFSContext *context)
540 return GNOME_VFS_ERROR_READ_ONLY_FILE_SYSTEM;
543 static GnomeVFSResult
544 do_seek (GnomeVFSMethod *method,
545 GnomeVFSMethodHandle *method_handle,
546 GnomeVFSSeekPosition whence,
547 GnomeVFSFileOffset offset,
548 GnomeVFSContext *context)
550 return gnome_vfs_seek_cancellable (VFS_HANDLE (method_handle),
555 static GnomeVFSResult
556 do_tell (GnomeVFSMethod *method,
557 GnomeVFSMethodHandle *method_handle,
558 GnomeVFSFileOffset *offset_return)
560 return gnome_vfs_tell (VFS_HANDLE (method_handle), offset_return);
563 static GnomeVFSResult
564 do_truncate_handle (GnomeVFSMethod *method,
565 GnomeVFSMethodHandle *method_handle,
566 GnomeVFSFileSize where,
567 GnomeVFSContext *context)
569 return GNOME_VFS_ERROR_NOT_SUPPORTED;
572 static GnomeVFSResult
573 do_truncate (GnomeVFSMethod *method,
575 GnomeVFSFileSize where,
576 GnomeVFSContext *context)
578 return GNOME_VFS_ERROR_NOT_SUPPORTED;
582 /* Directory reading. */
583 static GnomeVFSResult
584 read_directory_list (FILE *p,
586 GnomeVFSFileInfoOptions info_options,
587 GnomeVFSContext *context)
589 GnomeVFSResult result;
592 size_t line_buffer_size = 0;
596 line_buffer_size = 0;
597 result = GNOME_VFS_OK;
600 GnomeVFSFileInfo *info;
601 ExtfsDirectoryEntry *entry;
607 if (gnome_vfs_context_check_cancellation(context)) {
608 result = GNOME_VFS_ERROR_CANCELLED;
612 chars_read = getdelim (&line_buffer, &line_buffer_size, '\n', p);
613 if (chars_read == -1)
616 /* FIXME bugzilla.eazel.com 1223: */
617 fputs (line_buffer, stdout);
619 line_buffer[chars_read] = '\0';
621 if (! gnome_vfs_parse_ls_lga (line_buffer, &statbuf,
622 &name, &symlink_name)) {
626 info = gnome_vfs_file_info_new ();
628 info->valid_fields = GNOME_VFS_FILE_INFO_FIELDS_NONE;
629 gnome_vfs_stat_to_file_info (info, &statbuf);
631 GNOME_VFS_FILE_INFO_SET_LOCAL (info, FALSE);
632 info->name = g_strdup (get_basename (name));
633 info->symlink_name = symlink_name;
635 /* Notice that we always do stupid, fast MIME type checking.
636 Real checking based on contents would be too expensive. */
637 if (info_options & GNOME_VFS_FILE_INFO_GET_MIME_TYPE) {
638 info->mime_type = g_strdup (gnome_vfs_get_file_mime_type(
639 info->name, &statbuf, FALSE));
640 info->valid_fields |= GNOME_VFS_FILE_INFO_FIELDS_MIME_TYPE;
643 entry = g_new (ExtfsDirectoryEntry, 1);
645 entry->directory = get_dirname (name);
649 /* Order does not really matter here. */
650 list = g_list_prepend (list, entry);
657 static GnomeVFSResult
658 do_open_directory (GnomeVFSMethod *method,
659 GnomeVFSMethodHandle **method_handle,
661 GnomeVFSFileInfoOptions info_options,
662 GnomeVFSContext *context)
665 ExtfsDirectoryEntry *entry;
666 ExtfsDirectory *directory;
669 gchar *quoted_file_name;
678 ERROR_IF_NOT_LOCAL (uri);
680 directory = extfs_directory_lookup (uri->parent);
681 if (directory == NULL) {
683 GnomeVFSResult result;
685 /* Check that the file exists first. */
686 if (stat (uri->parent->text, &statbuf) != 0)
687 return GNOME_VFS_ERROR_NOT_FOUND;
689 quoted_file_name = quote_file_name (uri->parent->text);
690 script_path = get_script_path (uri);
691 cmd = g_strconcat (script_path, " list ", quoted_file_name,
694 pipe = popen (cmd, "r");
697 g_free (script_path);
698 g_free (quoted_file_name);
701 return GNOME_VFS_ERROR_NOT_SUPPORTED;
703 result = read_directory_list (pipe, &list, info_options,
706 if (pclose (pipe) == 0 && result == GNOME_VFS_OK) {
707 directory = extfs_directory_new (uri->parent, list);
709 free_directory_entries (list);
710 if (result == GNOME_VFS_OK)
711 return GNOME_VFS_ERROR_IO; /* FIXME bugzilla.eazel.com 1223:? */
717 /* Remove all leading slashes, as they don't matter for us. */
718 if (uri->text != NULL) {
719 sub_uri = strip_separators(uri->text);
724 l = directory->entries;
728 /* check if one of entry->directory or sub_uri is NULL */
729 if ((entry->directory != NULL && sub_uri == NULL)
730 || (entry->directory == NULL &&
736 /* check if the paths match */
737 if(strcmp(entry->directory, sub_uri)) {
742 entries = g_list_append(entries, entry->info);
749 handle = g_malloc( sizeof(GList *) );
753 *method_handle = (GnomeVFSMethodHandle *) handle;
757 return GNOME_VFS_ERROR_NOT_A_DIRECTORY;
761 static GnomeVFSResult
762 do_close_directory (GnomeVFSMethod *method,
763 GnomeVFSMethodHandle *method_handle,
764 GnomeVFSContext *context)
768 item = *((GList **)method_handle);
770 g_list_free (g_list_first (item));
772 g_free(method_handle);
777 static GnomeVFSResult
778 do_read_directory (GnomeVFSMethod *method,
779 GnomeVFSMethodHandle *method_handle,
780 GnomeVFSFileInfo *file_info,
781 GnomeVFSContext *context)
785 item = *((GList **)method_handle);
788 return GNOME_VFS_ERROR_EOF;
790 gnome_vfs_file_info_copy (file_info, (GnomeVFSFileInfo *)item->data);
792 //gnome_vfs_file_info_unref((GnomeVFSFileInfo *)item->data);
794 *((GList **)method_handle) = item->next;
799 static GnomeVFSResult
800 do_get_file_info (GnomeVFSMethod *method,
802 GnomeVFSFileInfo *file_info,
803 GnomeVFSFileInfoOptions options,
804 GnomeVFSContext *context)
806 GnomeVFSMethodHandle *method_handle;
807 GnomeVFSResult result;
811 parent = gnome_vfs_uri_get_parent(uri);
813 if (parent == NULL) {
814 return GNOME_VFS_ERROR_INVALID_URI;
817 filename = gnome_vfs_uri_extract_short_name(uri);
819 if(strcmp(parent->method_string, uri->method_string)) {
821 result = gnome_vfs_get_file_info_uri(parent, file_info, options);
822 /* now we get evil and tell the app that this is in fact a dir */
823 file_info->type = GNOME_VFS_FILE_TYPE_DIRECTORY;
824 g_free(file_info->mime_type);
825 file_info->mime_type = g_strdup("x-directory/normal");
830 result = do_open_directory (method, &method_handle, parent, options, context);
831 if (result != GNOME_VFS_OK)
835 result = do_read_directory (method, method_handle, file_info, context);
836 if (result != GNOME_VFS_OK ||
837 !strcmp (file_info->name, filename))
840 do_close_directory (method, method_handle, context);
842 if (result == GNOME_VFS_ERROR_EOF)
843 result = GNOME_VFS_ERROR_NOT_FOUND;
849 static GnomeVFSResult
850 do_get_file_info_from_handle (GnomeVFSMethod *method,
851 GnomeVFSMethodHandle *method_handle,
852 GnomeVFSFileInfo *file_info,
853 GnomeVFSFileInfoOptions options,
854 GnomeVFSContext *context)
856 return GNOME_VFS_ERROR_NOT_SUPPORTED;
860 do_is_local (GnomeVFSMethod *method,
861 const GnomeVFSURI *uri)
866 static GnomeVFSResult
867 do_make_directory (GnomeVFSMethod *method,
870 GnomeVFSContext *context)
872 return GNOME_VFS_ERROR_NOT_SUPPORTED;
875 static GnomeVFSResult
876 do_find_directory (GnomeVFSMethod *method,
877 GnomeVFSURI *near_uri,
878 GnomeVFSFindDirectoryKind kind,
879 GnomeVFSURI **result_uri,
880 gboolean create_if_needed,
881 gboolean find_if_needed,
883 GnomeVFSContext *context)
885 return GNOME_VFS_ERROR_NOT_SUPPORTED;
889 static GnomeVFSResult
890 do_remove_directory (GnomeVFSMethod *method,
892 GnomeVFSContext *context)
894 return GNOME_VFS_ERROR_NOT_SUPPORTED;
897 static GnomeVFSResult
898 do_move (GnomeVFSMethod *method,
899 GnomeVFSURI *old_uri,
900 GnomeVFSURI *new_uri,
901 gboolean force_replace,
902 GnomeVFSContext *context)
904 return GNOME_VFS_ERROR_NOT_SUPPORTED;
908 static GnomeVFSResult
909 do_unlink (GnomeVFSMethod *method,
911 GnomeVFSContext *context)
913 return GNOME_VFS_ERROR_NOT_SUPPORTED;
916 static GnomeVFSResult
917 do_check_same_fs (GnomeVFSMethod *method,
920 gboolean *same_fs_return,
921 GnomeVFSContext *context)
923 return GNOME_VFS_ERROR_NOT_SUPPORTED;
927 static GnomeVFSMethod method = {
928 sizeof (GnomeVFSMethod),
941 do_get_file_info_from_handle,
951 NULL /* FIXME bugzilla.eazel.com 2804: do_create_symbolic_link */
955 vfs_module_init (const char *method_name, const char *args)
958 uri_to_directory_hash = g_hash_table_new (gnome_vfs_uri_hash,
959 gnome_vfs_uri_hequal);
965 vfs_module_shutdown (GnomeVFSMethod *method)
969 for (p = handle_list; p != NULL; p = p->next)
970 extfs_handle_close ((ExtfsHandle *) p->data);