ftp://ftp.redhat.com/pub/redhat/linux/rawhide/SRPMS/SRPMS/gnome-vfs2-2.3.8-1.src.rpm
[gnome-vfs-httpcaptive.git] / modules / translate-method.c
1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2 /* translate-method.c - translator method for GNOME Virtual File System
3
4    Copyright (C) 1999, 2000 Red Hat Inc.
5
6    The Gnome Library is free software; you can redistribute it and/or
7    modify it under the terms of the GNU Library General Public License as
8    published by the Free Software Foundation; either version 2 of the
9    License, or (at your option) any later version.
10
11    The Gnome Library is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14    Library General Public License for more details.
15
16    You should have received a copy of the GNU Library General Public
17    License along with the Gnome Library; see the file COPYING.LIB.  If not,
18    write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19    Boston, MA 02111-1307, USA.
20
21    Author: Elliot Lee <sopwith@redhat.com> */
22
23 #include <config.h>
24
25 #include <errno.h>
26 #include <fcntl.h>
27 #include <glib/galloca.h>
28 #include <glib/gmessages.h>
29 #include <glib/gstrfuncs.h>
30 #include <glib/gthread.h>
31 #include <libgnomevfs/gnome-vfs-method.h>
32 #include <libgnomevfs/gnome-vfs-mime.h>
33 #include <libgnomevfs/gnome-vfs-mime-utils.h>
34 #include <libgnomevfs/gnome-vfs-module.h>
35 #include <libgnomevfs/gnome-vfs-private-utils.h>
36 #include <signal.h>
37 #include <stdio.h>
38 #include <string.h>
39 #include <sys/types.h>
40 #include <sys/wait.h>
41 #include <unistd.h>
42
43 typedef struct {
44         enum {
45                 MODE_BASIC, MODE_EXEC
46         } mode;
47
48         char *default_mime_type;
49         char *real_method_name;
50
51         union {
52                 struct {
53                         char *trans_string;
54                 } basic;
55                 struct {
56                         int argc;
57                         char **argv;
58                         char *orig_string;      /* this string gets chopped up into argv--it exists for freeing only */
59                         gboolean retain;
60                 } exec;
61         }u;
62 } ParsedArgs;
63
64 /* State used for -exec -retain */
65 typedef struct {
66         GMutex * retain_lock;
67         FILE * retain_to;               /* pipe to child */
68         FILE * retain_from;             /* pipe from child */
69         pid_t retain_pid;               /* PID of child */                              
70 } ExecState;
71
72 typedef struct {
73         GnomeVFSMethod base_method;
74         ParsedArgs pa;
75         GnomeVFSMethod *real_method;
76         ExecState exec_state;
77 } TranslateMethod;
78
79 static void
80 tr_apply_default_mime_type(TranslateMethod * tm,
81                            GnomeVFSFileInfo * file_info)
82 {
83         /* Only apply the default mime-type if the real method returns
84          * unknown mime-type and the the TranslateMethod /has/ a mime-type */
85         if (file_info->mime_type == NULL) {
86                 /* FIXME bugzilla.eazel.com 2799: 
87                  * this may not be needed since methods need to
88                  * return application/octet-stream if it is an unknown
89                  * mime-type, not NULL */
90                 if (tm->pa.default_mime_type) {
91                         file_info->mime_type =
92                                 g_strdup (tm->pa.default_mime_type);
93                         file_info->valid_fields |=
94                                 GNOME_VFS_FILE_INFO_FIELDS_MIME_TYPE;
95                 }
96                         
97         } else {
98                 if ((strcmp (file_info->mime_type, GNOME_VFS_MIME_TYPE_UNKNOWN) == 0) &&
99                     (tm->pa.default_mime_type != NULL)) {
100                         g_free (file_info->mime_type);
101                         file_info->mime_type =
102                                 g_strdup (tm->pa.default_mime_type);
103                         file_info->valid_fields |=
104                                 GNOME_VFS_FILE_INFO_FIELDS_MIME_TYPE;
105                 }               
106         }
107 }
108
109 typedef struct {
110         int child_out_fd;
111         int child_in_fd;
112 } TrForkCBData;
113
114 static void /* GnomeVFSProcessInitFunc */ tr_forkexec_cb (gpointer data)
115 {
116         TrForkCBData *cb_data;
117
118         g_assert (NULL != data);
119         cb_data = (TrForkCBData *) data;
120
121         if ( -1 == dup2 (cb_data->child_in_fd, STDIN_FILENO) ) {
122                 _exit (-1);
123         }
124
125         if ( -1 == dup2 (cb_data->child_out_fd, STDOUT_FILENO) ) {
126                 _exit (-1);
127         }
128
129 }
130
131
132 static pid_t tr_exec_open_child (char **argv, /*OUT*/ FILE ** p_from_stream, /*OUT*/ FILE ** p_to_stream)
133 {
134         pid_t ret;
135         int err;
136         TrForkCBData cb_data;
137         void *sigpipe_old;
138         int pipe_to_child[2]    = { -1, -1 };
139         int pipe_to_parent[2]   = { -1, -1 };
140
141         g_assert ( NULL != p_from_stream );
142         g_assert ( NULL != p_to_stream );
143
144         *p_to_stream = NULL;
145         *p_from_stream = NULL;
146
147         /* Blocking SIGPIPE here is probably unnecessary --
148          * GnomeVFS already does this on init
149          */
150         sigpipe_old = signal (SIGPIPE, SIG_IGN);
151
152         err = pipe (pipe_to_child);
153
154         if ( 0 != err ) {
155                 g_warning ("pipe returned error %d", errno);
156                 ret = -1;
157                 goto error;
158         }
159
160         err = pipe (pipe_to_parent);
161
162         if ( 0 != err ) {
163                 g_warning ("pipe returned error %d", errno);
164                 ret = -1;
165                 goto error;
166         }
167
168         cb_data.child_out_fd = pipe_to_parent[1];
169         cb_data.child_in_fd = pipe_to_child[0];
170
171         /* Strictly speaking, this is unnecessary since these handles will be closed anyway*/
172         err = fcntl ( pipe_to_parent[0], F_SETFD, FD_CLOEXEC ) ;
173         g_assert (0 == err );
174         err = fcntl ( pipe_to_child[1], F_SETFD, FD_CLOEXEC ) ;
175         g_assert (0 == err );
176
177         ret = gnome_vfs_forkexec (      argv[0],
178                                         (const char * const*)argv,
179                                         GNOME_VFS_PROCESS_CLOSEFDS,
180                                         tr_forkexec_cb,
181                                         (gpointer) &cb_data
182         );
183
184         close (pipe_to_parent[1]);
185         pipe_to_parent[1] = -1;
186         close (pipe_to_child[0]);
187         pipe_to_child[0] = -1;
188                         
189         if ( -1 == ret ) {
190                 g_warning ("fork returned error %d", errno);
191                 goto error;
192         }
193
194         *p_to_stream = fdopen (pipe_to_child[1], "w");
195         g_assert ( NULL != *p_to_stream );
196         pipe_to_child[1] = -1;
197
198         *p_from_stream = fdopen (pipe_to_parent[0], "r");
199         g_assert ( NULL != *p_from_stream );
200         pipe_to_parent[0] = -1;
201
202         setvbuf (*p_to_stream, NULL, _IOLBF, 0);
203         setvbuf (*p_from_stream, NULL, _IOLBF, 0);
204
205 error:
206         if ( -1 != pipe_to_parent[0] ) { close ( pipe_to_parent[0]); }
207         if ( -1 != pipe_to_parent[1] ) { close ( pipe_to_parent[1]); }
208         if ( -1 != pipe_to_child[0] ) { close ( pipe_to_child[0]); }
209         if ( -1 != pipe_to_child[1] ) { close ( pipe_to_child[1]); }
210         
211         signal (SIGPIPE, sigpipe_old);
212         
213         return ret;
214 }
215
216 #define GETLINE_DELTA 256
217 static char * tr_getline (FILE* stream)
218 {
219         char *ret;
220         size_t ret_size;
221         size_t ret_used;
222         int char_read;
223         gboolean got_newline;
224
225         ret = g_malloc( GETLINE_DELTA * sizeof(char) );
226         ret_size = GETLINE_DELTA;
227
228         for ( ret_used = 0, got_newline = FALSE ;
229               !got_newline && (EOF != (char_read = fgetc (stream))) ; 
230               ret_used++
231         ) {
232                 if (ret_size == (ret_used + 1)) {
233                         ret_size += GETLINE_DELTA;
234                         ret = g_realloc (ret, ret_size); 
235                 }
236                 if ('\n' == char_read || '\r' == char_read ) {
237                         got_newline = TRUE;
238                         ret [ret_used] = '\0';
239                 } else {
240                         ret [ret_used] = char_read;
241                 }
242         }
243
244         if (got_newline) {
245                 return ret;
246         } else {
247                 g_free (ret);
248                 return NULL;
249         }
250 }
251 #undef GETLINE_DELTA
252
253 static void tr_exec_pass_uri (char *uri_string, FILE * out_stream)
254 {
255         char *tmpstr;
256
257         /*shave off the scheme--pass only the right hand side to the child*/
258         tmpstr = strchr (uri_string, ':');
259         fprintf (out_stream, "%s\n", tmpstr ? tmpstr + 1 : uri_string);
260         fflush (out_stream);
261         
262         tmpstr = NULL;
263
264 }
265
266 static char * tr_exec_do_retain (TranslateMethod *tm, char * uri_string)
267 {
268         char * child_result = NULL;
269         int tries;
270
271         g_mutex_lock (tm->exec_state.retain_lock);
272
273
274         /* re-execute the child process in case it unexpectedly disappears */  
275         for (tries = 0 ; ( ! child_result ) && tries < 3 ; tries ++) {
276                 if ( 0 == tm->exec_state.retain_pid ) {
277                         tm->exec_state.retain_pid = tr_exec_open_child (tm->pa.u.exec.argv, &(tm->exec_state.retain_from), &(tm->exec_state.retain_to));
278                         if ( -1 == tm->exec_state.retain_pid ) {
279                                 tm->exec_state.retain_pid = 0;
280                                 goto error;
281                         }
282                 }
283
284                 g_assert (uri_string);
285                 tr_exec_pass_uri (uri_string, tm->exec_state.retain_to);
286
287                 child_result = tr_getline (tm->exec_state.retain_from);
288
289                 if (NULL == child_result) {
290                         tm->exec_state.retain_pid = 0;
291                 }
292         }
293         
294 error:
295         g_mutex_unlock (tm->exec_state.retain_lock);
296
297         return child_result;
298 }
299
300 /*
301  * -exec
302  * 
303  * USAGE: -exec <exec_string> -real-method <method> [-retain]
304  * 
305  * -exec allows a child process to filter and translate URI's passed to GNOME-VFS.
306  * -exec requires a URI scheme to be specified via -real-method, and does not
307  * allow the child process to change that method
308  *
309  * <exec_string> is a string that will be tokenized and passed to execv()
310  * <method> is the URI scheme that all requests will be translated to.
311  * 
312  * Without -retain: 
313  *      The child process is exec()'d.  The URI minus the scheme is presented
314  *      as standard input.  On success, the child is expected to print out
315  *      a translated URI minus the scheme and exit(0).  On failure, the child
316  *      should print out ":\n" or exit with a non-zero value
317  *
318  * With -retain
319  *      The child process is exec()'d and fed multiple URI's, each newline terminated,
320  *      via standard in.  For each URI, the child must print to standard out a
321  *      translated URI followed by a newline or a ":" followed by a newline on error.
322  *      
323  *      Note than children written to work with -retain that use stdio need
324  *      to set their stdin and stdout buffering discipline to "line" instead of "block" 
325  *      like this:
326  *              setvbuf (stdin, NULL, _IOLBF, 0);
327  *              setvbuf (stdout, NULL, _IOLBF, 0);
328  *
329  * Config file example:
330  * 
331  * foo: libvfs-translate.so -real-method http -exec "/bin/sed -e s/\/\/www.microsoft.com/\/\/www.eazel.com/"
332  * 
333  * (note that sed won't work with -retain)
334  */
335
336 /* FIXME bugzilla.eazel.com 2796: this may be broken when the child produces compound URI's */
337 static GnomeVFSURI *tr_handle_exec (TranslateMethod *tm, const GnomeVFSURI * uri)
338 {
339
340         int err;
341         char *tmpstr;
342
343         int child_status;
344         char *uri_string        = NULL;
345         char *child_result      = NULL;
346         GnomeVFSURI * retval    = NULL;
347
348         /*
349          * Note that normally having a parent and child communicate
350          * bi-directionally via pipes is a bad idea.  However, in this case,
351          * the parent will write everything and then close, so it'll all be OK
352          * as long as the child doesnt intermix reads and writes and fill
353          * up their output pipe's buffer before the parent is done writing.
354          */
355
356         uri_string = gnome_vfs_uri_to_string (uri, 0);
357
358         if ( NULL == uri_string ) {
359                 goto error;
360         }
361
362         if (tm->pa.u.exec.retain) {
363                 child_result = tr_exec_do_retain (tm, uri_string);
364
365                 if ( NULL == child_result ) {
366                         goto error;
367                 }
368         } else {
369                 pid_t child_pid;
370                 FILE *from_stream, *to_stream;
371
372                 child_pid = tr_exec_open_child (tm->pa.u.exec.argv, &from_stream, &to_stream);
373
374                 if ( -1 == child_pid ) {
375                         goto error;
376                 }
377         
378                 uri_string = gnome_vfs_uri_to_string (uri, 0);
379                 g_assert (uri_string);
380                 tr_exec_pass_uri (uri_string, to_stream);
381
382                 fclose (to_stream);
383                 to_stream = NULL;
384
385                 child_result = tr_getline (from_stream);
386                 
387                 err = waitpid (child_pid, &child_status, 0);
388
389                 g_assert ( child_pid == err );
390
391                 if (! WIFEXITED (child_status) ) {
392                         goto error;
393                 }
394
395                 if (NULL == child_result) {
396                         g_warning ("Child produced no result");
397                         goto error;
398                 }
399         }
400
401
402         /* FIXME bugzilla.eazel.com 2797: 
403          * just because we've appended the same scheme, doesn't mean
404          * we're going to get the same method from gnome_vfs_uri_new
405          */
406
407         /* A child result that ends in a :\n indicates an error */
408         if ( ':' != child_result[ strlen(child_result) - 1 ]) {
409                 /* append the real scheme */
410                 tmpstr = g_strconcat (tm->pa.real_method_name, ":", child_result, NULL);
411                 g_free (child_result);
412                 child_result = tmpstr;
413                 tmpstr = NULL;          
414
415                 retval = gnome_vfs_uri_new_private (child_result, FALSE, TRUE, TRUE);
416
417                 if ( NULL == retval ) {
418                         g_warning ("Unable to make URI from child process's result '%s'", child_result);
419                         goto error;
420                 }
421         }
422
423
424 error:
425         g_free (child_result);
426         g_free (uri_string);
427         
428         return retval;
429 }
430
431 static void
432 tr_exec_init (ExecState *exec_state)
433 {
434         exec_state->retain_lock = g_mutex_new();
435 }
436
437 static void
438 tr_exec_cleanup (ExecState *exec_state)
439 {
440         if (NULL != exec_state->retain_lock) {
441                 g_mutex_free (exec_state->retain_lock);
442         }
443
444         if (NULL != exec_state->retain_to) {
445                 fclose (exec_state->retain_to);
446         }
447         if (NULL != exec_state->retain_from) {
448                 fclose (exec_state->retain_from);
449         }
450
451         if ( 0 != exec_state->retain_pid ) {
452                 int child_status, err;
453                 
454                 kill (exec_state->retain_pid, SIGTERM);
455                 err = waitpid (exec_state->retain_pid, &child_status, 0);               
456                 g_assert (err == exec_state->retain_pid);
457         }
458 }
459
460 static GnomeVFSURI *tr_uri_translate(TranslateMethod * tm,
461                                      const GnomeVFSURI * uri)
462 {
463         const char *text_uri;
464         const char *text_uri_no_method;
465         char *translated_text;
466         char *translated_uri;
467         GnomeVFSURI *retval;
468
469         retval = NULL;
470
471         if (uri->method != (GnomeVFSMethod *) tm)
472                 return gnome_vfs_uri_ref((GnomeVFSURI *) uri);  /* Don't translate things that don't belong to us */
473
474         /* Hack it all up to pieces */
475
476         if (MODE_BASIC == tm->pa.mode) {
477                 text_uri = gnome_vfs_uri_to_string (uri, GNOME_VFS_URI_HIDE_NONE);
478                 text_uri_no_method = strchr (text_uri, ':');
479
480                 if (text_uri_no_method == NULL) {
481                         text_uri_no_method = text_uri;
482                 } else {
483                         text_uri_no_method = text_uri_no_method + 1;
484                 }
485                         
486                 translated_text = g_strdup_printf (tm->pa.u.basic.trans_string, 
487                                                    uri->text, uri->text,
488                                                    uri->text, uri->text, uri->text);
489                 translated_uri = g_strconcat (tm->pa.real_method_name, ":", 
490                                               translated_text, NULL);
491
492                 retval = gnome_vfs_uri_new_private (translated_uri, FALSE, TRUE, TRUE);
493
494                 g_free (translated_text);
495                 g_free (translated_uri);
496         } else if (MODE_EXEC == tm->pa.mode) {
497                 retval = tr_handle_exec (tm, uri);
498         } else {
499                 g_assert (FALSE);
500         }
501
502         return retval;
503 }
504
505 static GnomeVFSResult
506 tr_do_open(GnomeVFSMethod * method,
507            GnomeVFSMethodHandle ** method_handle_return,
508            GnomeVFSURI * uri,
509            GnomeVFSOpenMode mode, GnomeVFSContext * context)
510 {
511         TranslateMethod *tm = (TranslateMethod *) method;
512         GnomeVFSURI *real_uri;
513         GnomeVFSResult retval;
514
515         real_uri = tr_uri_translate(tm, uri);
516
517         if ( NULL != real_uri ) {
518                 retval =
519                     tm->real_method->open(tm->real_method, method_handle_return,
520                                           real_uri, mode, context);
521
522                 gnome_vfs_uri_unref(real_uri);
523         } else {
524                 retval = GNOME_VFS_ERROR_NOT_FOUND;
525         }
526
527         return retval;
528 }
529
530 static GnomeVFSResult
531 tr_do_create(GnomeVFSMethod * method,
532              GnomeVFSMethodHandle
533              ** method_handle_return,
534              GnomeVFSURI * uri,
535              GnomeVFSOpenMode mode,
536              gboolean exclusive, guint perm, GnomeVFSContext * context)
537 {
538         TranslateMethod *tm = (TranslateMethod *) method;
539         GnomeVFSURI *real_uri;
540         GnomeVFSResult retval;
541
542         real_uri = tr_uri_translate(tm, uri);
543
544         if ( NULL != real_uri ) {
545                 retval =
546                     tm->real_method->create(tm->real_method, method_handle_return,
547                                             real_uri, mode, exclusive, perm,
548                                             context);
549                 gnome_vfs_uri_unref(real_uri);
550         } else {
551                 retval = GNOME_VFS_ERROR_NOT_FOUND;
552         }
553
554         return retval;
555 }
556
557 static GnomeVFSResult
558 tr_do_close(GnomeVFSMethod * method,
559             GnomeVFSMethodHandle * method_handle,
560             GnomeVFSContext * context)
561 {
562         TranslateMethod *tm = (TranslateMethod *) method;
563         return tm->real_method->close(tm->real_method, method_handle,
564                                       context);
565 }
566
567 static GnomeVFSResult
568 tr_do_read(GnomeVFSMethod * method,
569            GnomeVFSMethodHandle * method_handle,
570            gpointer buffer,
571            GnomeVFSFileSize num_bytes,
572            GnomeVFSFileSize * bytes_read_return, GnomeVFSContext * context)
573 {
574         TranslateMethod *tm = (TranslateMethod *) method;
575         return tm->real_method->read(tm->real_method, method_handle,
576                                      buffer, num_bytes, bytes_read_return,
577                                      context);
578 }
579
580 static GnomeVFSResult
581 tr_do_write(GnomeVFSMethod * method,
582             GnomeVFSMethodHandle * method_handle,
583             gconstpointer buffer,
584             GnomeVFSFileSize num_bytes,
585             GnomeVFSFileSize * bytes_written_return,
586             GnomeVFSContext * context)
587 {
588         TranslateMethod *tm = (TranslateMethod *) method;
589         return tm->real_method->write(tm->real_method, method_handle,
590                                       buffer, num_bytes,
591                                       bytes_written_return, context);
592 }
593
594 static GnomeVFSResult
595 tr_do_seek(GnomeVFSMethod * method,
596            GnomeVFSMethodHandle * method_handle,
597            GnomeVFSSeekPosition whence,
598            GnomeVFSFileOffset offset, GnomeVFSContext * context)
599 {
600         TranslateMethod *tm = (TranslateMethod *) method;
601         return tm->real_method->seek(tm->real_method, method_handle,
602                                      whence, offset, context);
603 }
604
605 static GnomeVFSResult
606 tr_do_tell(GnomeVFSMethod * method,
607            GnomeVFSMethodHandle * method_handle,
608            GnomeVFSFileOffset * offset_return)
609 {
610         TranslateMethod *tm = (TranslateMethod *) method;
611         return tm->real_method->tell(tm->real_method, method_handle,
612                                      offset_return);
613 }
614
615 static GnomeVFSResult
616 tr_do_open_directory(GnomeVFSMethod * method,
617                      GnomeVFSMethodHandle ** method_handle,
618                      GnomeVFSURI * uri,
619                      GnomeVFSFileInfoOptions options,
620                      GnomeVFSContext * context)
621 {
622         TranslateMethod *tm = (TranslateMethod *) method;
623         GnomeVFSURI *real_uri;
624         GnomeVFSResult retval;
625
626         real_uri = tr_uri_translate(tm, uri);
627
628         if ( NULL != real_uri ) {
629                 retval =
630                     tm->real_method->open_directory(tm->real_method, method_handle,
631                                                     real_uri, options,
632                                                     context);
633                 gnome_vfs_uri_unref(real_uri);
634         } else {
635                 retval = GNOME_VFS_ERROR_NOT_FOUND;
636         }
637
638         return retval;
639 }
640
641 static GnomeVFSResult
642 tr_do_close_directory(GnomeVFSMethod * method,
643                       GnomeVFSMethodHandle * method_handle,
644                       GnomeVFSContext * context)
645 {
646         TranslateMethod *tm = (TranslateMethod *) method;
647         return tm->real_method->close_directory(tm->real_method,
648                                                 method_handle, context);
649 }
650
651 static GnomeVFSResult
652 tr_do_read_directory(GnomeVFSMethod * method,
653                      GnomeVFSMethodHandle * method_handle,
654                      GnomeVFSFileInfo * file_info,
655                      GnomeVFSContext * context)
656 {
657         TranslateMethod *tm = (TranslateMethod *) method;
658         GnomeVFSResult retval;
659
660         retval =
661             tm->real_method->read_directory(tm->real_method, method_handle,
662                                             file_info, context);
663
664         tr_apply_default_mime_type(tm, file_info);
665
666         return retval;
667 }
668
669 static GnomeVFSResult
670 tr_do_get_file_info(GnomeVFSMethod * method,
671                     GnomeVFSURI * uri,
672                     GnomeVFSFileInfo * file_info,
673                     GnomeVFSFileInfoOptions options,
674                     GnomeVFSContext * context)
675 {
676         TranslateMethod *tm = (TranslateMethod *) method;
677         GnomeVFSURI *real_uri;
678         GnomeVFSResult retval;
679
680         real_uri = tr_uri_translate(tm, uri);
681
682         if ( NULL != real_uri ) {
683                 retval =
684                     tm->real_method->get_file_info(tm->real_method, real_uri,
685                                                    file_info, options,
686                                                    context);
687
688                 gnome_vfs_uri_unref(real_uri);
689         } else {
690                 retval = GNOME_VFS_ERROR_NOT_FOUND;
691         }
692
693         tr_apply_default_mime_type(tm, file_info);
694
695         return retval;
696 }
697
698 static GnomeVFSResult
699 tr_do_get_file_info_from_handle(GnomeVFSMethod * method,
700                                 GnomeVFSMethodHandle * method_handle,
701                                 GnomeVFSFileInfo * file_info,
702                                 GnomeVFSFileInfoOptions options,
703                                 GnomeVFSContext * context)
704 {
705         TranslateMethod *tm = (TranslateMethod *) method;
706         GnomeVFSResult retval;
707
708         retval =
709             tm->real_method->get_file_info_from_handle(tm->real_method,
710                                                        method_handle,
711                                                        file_info, 
712                                                        options,
713                                                        context);
714
715         tr_apply_default_mime_type(tm, file_info);
716
717         return retval;
718 }
719
720 static GnomeVFSResult
721 tr_do_truncate(GnomeVFSMethod * method,
722                GnomeVFSURI * uri,
723                GnomeVFSFileSize length, GnomeVFSContext * context)
724 {
725         TranslateMethod *tm = (TranslateMethod *) method;
726         GnomeVFSURI *real_uri;
727         GnomeVFSResult retval;
728
729         real_uri = tr_uri_translate(tm, uri);
730
731         if ( NULL != real_uri ) {
732                 retval =
733                     tm->real_method->truncate(tm->real_method, real_uri, length,
734                                               context);
735
736                 gnome_vfs_uri_unref(real_uri);
737         } else {
738                 retval = GNOME_VFS_ERROR_NOT_FOUND;
739         }
740
741         return retval;
742 }
743
744 static GnomeVFSResult
745 tr_do_truncate_handle(GnomeVFSMethod * method,
746                       GnomeVFSMethodHandle * method_handle,
747                       GnomeVFSFileSize length, GnomeVFSContext * context)
748 {
749         TranslateMethod *tm = (TranslateMethod *) method;
750
751         return tm->real_method->truncate_handle(tm->real_method,
752                                                 method_handle, length,
753                                                 context);
754 }
755
756 static gboolean
757 tr_do_is_local(GnomeVFSMethod * method, const GnomeVFSURI * uri)
758 {
759         TranslateMethod *tm = (TranslateMethod *) method;
760         GnomeVFSURI *real_uri;
761         GnomeVFSResult retval;
762
763         real_uri = tr_uri_translate(tm, uri);
764
765         if ( NULL != real_uri ) {
766                 retval = tm->real_method->is_local(tm->real_method, real_uri);
767
768                 gnome_vfs_uri_unref(real_uri);
769         } else {
770                 retval = GNOME_VFS_ERROR_NOT_FOUND;
771         }
772
773         return retval;
774 }
775
776 static GnomeVFSResult
777 tr_do_make_directory(GnomeVFSMethod * method,
778                      GnomeVFSURI * uri,
779                      guint perm, GnomeVFSContext * context)
780 {
781         TranslateMethod *tm = (TranslateMethod *) method;
782         GnomeVFSURI *real_uri;
783         GnomeVFSResult retval;
784
785         real_uri = tr_uri_translate(tm, uri);
786
787         if ( NULL != real_uri ) {
788                 retval =
789                     tm->real_method->make_directory(tm->real_method, real_uri,
790                                                     perm, context);
791
792                 gnome_vfs_uri_unref(real_uri);
793         } else {
794                 retval = GNOME_VFS_ERROR_NOT_FOUND;
795         }
796
797         return retval;
798 }
799
800 static GnomeVFSResult
801 tr_do_find_directory(GnomeVFSMethod * method,
802                      GnomeVFSURI * near_uri,
803                      GnomeVFSFindDirectoryKind kind,
804                      GnomeVFSURI ** result_uri,
805                      gboolean create_if_needed,
806                      gboolean find_if_needed,
807                      guint permissions, GnomeVFSContext * context)
808 {
809         TranslateMethod *tm = (TranslateMethod *) method;
810         GnomeVFSURI *real_uri;
811         GnomeVFSResult retval;
812
813         real_uri = tr_uri_translate(tm, near_uri);
814
815         if ( NULL != real_uri ) {
816                 retval =
817                     tm->real_method->find_directory(tm->real_method, real_uri,
818                                                     kind, result_uri,
819                                                     create_if_needed, find_if_needed,
820                                                     permissions,
821                                                     context);
822
823                 gnome_vfs_uri_unref(real_uri);
824         } else {
825                 retval = GNOME_VFS_ERROR_NOT_FOUND;
826         }
827
828         return retval;
829 }
830
831
832
833 static GnomeVFSResult
834 tr_do_remove_directory(GnomeVFSMethod * method,
835                        GnomeVFSURI * uri, GnomeVFSContext * context)
836 {
837         TranslateMethod *tm = (TranslateMethod *) method;
838         GnomeVFSURI *real_uri;
839         GnomeVFSResult retval;
840
841         real_uri = tr_uri_translate(tm, uri);
842
843         if ( NULL != real_uri ) {
844                 retval =
845                     tm->real_method->remove_directory(tm->real_method, real_uri,
846                                                       context);
847
848                 gnome_vfs_uri_unref(real_uri);
849         } else {
850                 retval = GNOME_VFS_ERROR_NOT_FOUND;
851         }
852
853         return retval;
854 }
855
856 static GnomeVFSResult
857 tr_do_move(GnomeVFSMethod * method,
858            GnomeVFSURI * old_uri,
859            GnomeVFSURI * new_uri,
860            gboolean force_replace, GnomeVFSContext * context)
861 {
862         TranslateMethod *tm = (TranslateMethod *) method;
863         GnomeVFSURI *real_uri_old, *real_uri_new;
864         GnomeVFSResult retval;
865
866         real_uri_old = tr_uri_translate(tm, old_uri);
867         real_uri_new = tr_uri_translate(tm, new_uri);
868
869         if ( NULL != real_uri_old && NULL != real_uri_new ) {
870                 retval =
871                     tm->real_method->move(tm->real_method, real_uri_old,
872                                           real_uri_new, force_replace, context);
873
874         } else {
875                 retval = GNOME_VFS_ERROR_NOT_FOUND;
876         }
877
878         if (real_uri_old) {gnome_vfs_uri_unref(real_uri_old);}
879         if (real_uri_new) {gnome_vfs_uri_unref(real_uri_new);}
880
881         return retval;
882 }
883
884 static GnomeVFSResult
885 tr_do_unlink(GnomeVFSMethod * method,
886              GnomeVFSURI * uri, GnomeVFSContext * context)
887 {
888         TranslateMethod *tm = (TranslateMethod *) method;
889         GnomeVFSURI *real_uri;
890         GnomeVFSResult retval;
891
892         real_uri = tr_uri_translate(tm, uri);
893
894         if ( NULL != real_uri ) {
895                 retval =
896                     tm->real_method->unlink(tm->real_method, real_uri, context);
897
898                 gnome_vfs_uri_unref(real_uri);
899         } else {
900                 retval = GNOME_VFS_ERROR_NOT_FOUND;
901         }
902
903         return retval;
904 }
905
906 static GnomeVFSResult
907 tr_do_check_same_fs(GnomeVFSMethod * method,
908                     GnomeVFSURI * a,
909                     GnomeVFSURI * b,
910                     gboolean * same_fs_return, GnomeVFSContext * context)
911 {
912         TranslateMethod *tm = (TranslateMethod *) method;
913         GnomeVFSURI *real_uri_a, *real_uri_b;
914         GnomeVFSResult retval;
915
916         real_uri_a = tr_uri_translate(tm, a);
917         real_uri_b = tr_uri_translate(tm, b);
918
919         if ( NULL != real_uri_a && NULL != real_uri_b ) {
920                 retval =
921                     tm->real_method->check_same_fs(tm->real_method, real_uri_a,
922                                                    real_uri_b, same_fs_return,
923                                                    context);
924         } else {
925                 retval = GNOME_VFS_ERROR_NOT_FOUND;
926         }
927
928         if (real_uri_a) {gnome_vfs_uri_unref(real_uri_a);}
929         if (real_uri_b) {gnome_vfs_uri_unref(real_uri_b);}
930
931         return retval;
932 }
933
934 static GnomeVFSResult
935 tr_do_set_file_info(GnomeVFSMethod * method,
936                     GnomeVFSURI * a,
937                     const GnomeVFSFileInfo * info,
938                     GnomeVFSSetFileInfoMask mask,
939                     GnomeVFSContext * context)
940 {
941         TranslateMethod *tm = (TranslateMethod *) method;
942         GnomeVFSURI *real_uri_a;
943         GnomeVFSResult retval;
944
945         real_uri_a = tr_uri_translate(tm, a);
946
947         if ( NULL != real_uri_a ) {
948                 retval =
949                     tm->real_method->set_file_info(tm->real_method, real_uri_a,
950                                                    info, mask, context);
951
952                 gnome_vfs_uri_unref(real_uri_a);
953         } else {
954                 retval = GNOME_VFS_ERROR_NOT_FOUND;
955         }
956
957         return retval;
958 }
959
960 /******** from poptparse.c:
961   Copyright (c) 1998  Red Hat Software
962
963   Permission is hereby granted, free of charge, to any person obtaining a copy
964   of this software and associated documentation files (the "Software"), to deal
965   in the Software without restriction, including without limitation the rights
966   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
967   copies of the Software, and to permit persons to whom the Software is
968   furnished to do so, subject to the following conditions:
969
970   The above copyright notice and this permission notice shall be included in
971   all copies or substantial portions of the Software.
972
973   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
974   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
975   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
976   X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
977   AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
978   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
979
980   Except as contained in this notice, the name of the X Consortium shall not be
981   used in advertising or otherwise to promote the sale, use or other dealings
982   in this Software without prior written authorization from the X Consortium.
983
984 */
985
986 #define POPT_ARGV_ARRAY_GROW_DELTA 5
987 static int my_poptParseArgvString(char *buf, int *argcPtr, char ***argvPtr)
988 {
989         char *src;
990         char quote = '\0';
991         int argvAlloced = POPT_ARGV_ARRAY_GROW_DELTA;
992         char **argv = g_new(char *, argvAlloced);
993         int argc = 0;
994         char *s;
995
996         s = g_alloca(strlen(buf) + 1);
997         strcpy(s, buf);
998
999         argv[argc] = buf;
1000
1001         for (src = s; *src; src++) {
1002                 if (quote == *src) {
1003                         quote = '\0';
1004                 } else if (quote) {
1005                         if (*src == '\\') {
1006                                 src++;
1007                                 if (!*src) {
1008                                         g_free(argv);
1009                                         return -1;
1010                                 }
1011                                 if (*src != quote)
1012                                         *(buf++) = '\\';
1013                         }
1014                         *(buf++) = *src;
1015                 } else if (g_ascii_isspace(*src)) {
1016                         *buf = '\0';
1017                         if (*argv[argc]) {
1018                                 buf++, argc++;
1019                                 /* leave one empty argv at the end */
1020                                 if (argc == argvAlloced - 1) {
1021                                         argvAlloced +=
1022                                             POPT_ARGV_ARRAY_GROW_DELTA;
1023                                         argv =
1024                                             g_realloc(argv,
1025                                                       sizeof(*argv) *
1026                                                       argvAlloced);
1027                                 }
1028                                 argv[argc] = buf;
1029                         }
1030                 } else
1031                         switch (*src) {
1032                         case '"':
1033                         case '\'':
1034                                 quote = *src;
1035                                 break;
1036                         case '\\':
1037                                 src++;
1038                                 if (!*src) {
1039                                         g_free(argv);
1040                                         return -1;
1041                                 }
1042                                 /*@fallthrough@ */
1043                         default:
1044                                 *(buf++) = *src;
1045                                 break;
1046                         }
1047         }
1048
1049         *buf = '\0';
1050         if (strlen(argv[argc])) {
1051                 argc++, buf++;
1052         }
1053
1054         /* add NULL to the end of argv */
1055         argv[argc] = NULL;
1056
1057         *argcPtr = argc;
1058         *argvPtr = argv;
1059
1060         return 0;
1061 }
1062
1063 static gboolean tr_args_parse(ParsedArgs * pa, const char *args)
1064 {
1065         char **argv;
1066         int argc;
1067         char *tmp_args;
1068         int i;
1069         gboolean badargs = FALSE;
1070         gboolean mode_determined = FALSE;
1071
1072         memset(pa, 0, sizeof(ParsedArgs));
1073
1074         tmp_args = g_alloca(strlen(args) + 1);
1075         strcpy(tmp_args, args);
1076
1077         if (my_poptParseArgvString(tmp_args, &argc, &argv)) {
1078                 g_warning("Failed to parse arguments: %s", args);
1079                 return FALSE;
1080         }
1081
1082         for (i = 0; i < argc; i++) {
1083 #define CHECK_ARG() if((++i) >= argc) { badargs = TRUE; goto out; }
1084 #define CHECK_MODE(my_mode) if (mode_determined && pa->mode != (my_mode)) { badargs = TRUE ; goto out; } else { pa->mode = my_mode; mode_determined = TRUE; }
1085                 if (g_ascii_strcasecmp(argv[i], "-pattern") == 0) {
1086                         CHECK_ARG();
1087                         CHECK_MODE(MODE_BASIC);
1088                         pa->u.basic.trans_string = g_strdup(argv[i]);
1089                 } else if (g_ascii_strcasecmp(argv[i], "-real-method") == 0) {
1090                         CHECK_ARG();
1091                         pa->real_method_name = g_strdup(argv[i]);
1092                 } else if (g_ascii_strcasecmp(argv[i], "-exec") == 0) {
1093                         CHECK_ARG();
1094                         CHECK_MODE(MODE_EXEC);
1095                         pa->u.exec.orig_string = g_strdup(argv[i]);
1096                 } else if (g_ascii_strcasecmp(argv[i], "-retain") == 0) {
1097                         CHECK_MODE(MODE_EXEC);
1098                         pa->u.exec.retain = TRUE;
1099                 } else if (g_ascii_strcasecmp(argv[i], "-default-mime-type") == 0) {
1100                         CHECK_ARG();
1101                         pa->default_mime_type = g_strdup(argv[i]);
1102                 } else {
1103                         g_warning("Unknown option `%s'", argv[i]);
1104                         badargs = TRUE;
1105                         goto out;
1106                 }
1107 #undef CHECK_ARG
1108 #undef CHECK_MODE
1109         }
1110
1111         if ( ! mode_determined ) {
1112                 g_warning("Need a -pattern option or -exec option");
1113                 badargs = TRUE;
1114         } else if (MODE_BASIC == pa->mode) {
1115                 if (!pa->real_method_name) {
1116                         g_warning("Need a -real-method option");
1117                         badargs = TRUE;
1118                 } else if (!pa->u.basic.trans_string) {
1119                         g_warning("Need a -pattern option");
1120                         badargs = TRUE;
1121                 }
1122         } else if (MODE_EXEC == pa->mode) {
1123                 if (!pa->real_method_name) {
1124                         g_warning("Need a -real-method option");
1125                         badargs = TRUE;
1126                 } else if (!pa->u.exec.orig_string) {
1127                         g_warning("Need a -exec option");
1128                         badargs = TRUE;
1129                 }
1130
1131                 /* Chop up the exec string here */
1132                 if (my_poptParseArgvString (
1133                         pa->u.exec.orig_string, 
1134                         &(pa->u.exec.argc), 
1135                         &(pa->u.exec.argv))
1136                 ) {
1137                         g_warning ("Failed to parse -exec args");
1138                         badargs = TRUE;
1139                 }
1140         } else {
1141                 g_assert (FALSE);
1142         }
1143         
1144       out:
1145         g_free(argv);
1146         return !badargs;
1147 }
1148
1149 static GnomeVFSMethod base_vfs_method = {
1150         sizeof (GnomeVFSMethod),
1151         tr_do_open,
1152         tr_do_create,
1153         tr_do_close,
1154         tr_do_read,
1155         tr_do_write,
1156         tr_do_seek,
1157         tr_do_tell,
1158         tr_do_truncate_handle,
1159         tr_do_open_directory,
1160         tr_do_close_directory,
1161         tr_do_read_directory,
1162         tr_do_get_file_info,
1163         tr_do_get_file_info_from_handle,
1164         tr_do_is_local,
1165         tr_do_make_directory,
1166         tr_do_remove_directory,
1167         tr_do_move,
1168         tr_do_unlink,
1169         tr_do_check_same_fs,
1170         tr_do_set_file_info,
1171         tr_do_truncate,
1172         tr_do_find_directory,
1173         NULL                            /* create_symbolic_link can't be supported */
1174         /* Hey YOU!  If you add a GnomeVFS method, you need to do two things
1175          * in the translate-method module:
1176          * 
1177          * 1. Add a line to the CHECK_NULL_METHOD list in vfs_module_init
1178          * 2. Implement the function in this module.  This is pretty simple--
1179          *    just follow the pattern of existing functions
1180          */ 
1181 };
1182
1183 static void tr_args_free(ParsedArgs * pa)
1184 {
1185         g_free(pa->default_mime_type);
1186         g_free(pa->real_method_name);
1187
1188         if (MODE_BASIC == pa->mode) {
1189                 g_free(pa->u.basic.trans_string);
1190         } else {
1191                 g_free(pa->u.exec.orig_string);
1192         }
1193 }
1194
1195 GnomeVFSMethod *vfs_module_init(const char *method_name, const char *args)
1196 {
1197         TranslateMethod *retval;
1198         ParsedArgs pa;
1199
1200         if (!tr_args_parse(&pa, args))
1201                 return NULL;
1202
1203         retval = g_new0(TranslateMethod, 1);
1204         retval->pa = pa;
1205         retval->real_method = gnome_vfs_method_get(pa.real_method_name);
1206
1207         if (!retval->real_method) {
1208                 tr_args_free(&retval->pa);
1209                 g_free(retval);
1210                 return NULL;
1211         }
1212
1213         tr_exec_init(&(retval->exec_state));
1214
1215         retval->base_method = base_vfs_method;
1216  
1217 #define CHECK_NULL_METHOD(x) if(!VFS_METHOD_HAS_FUNC (retval->real_method, x)) retval->base_method.x = NULL
1218         CHECK_NULL_METHOD(open);
1219         CHECK_NULL_METHOD(create);
1220         CHECK_NULL_METHOD(close);
1221         CHECK_NULL_METHOD(read);
1222         CHECK_NULL_METHOD(write);
1223         CHECK_NULL_METHOD(seek);
1224         CHECK_NULL_METHOD(tell);
1225         CHECK_NULL_METHOD(truncate);
1226         CHECK_NULL_METHOD(open_directory);
1227         CHECK_NULL_METHOD(close_directory);
1228         CHECK_NULL_METHOD(read_directory);
1229         CHECK_NULL_METHOD(get_file_info);
1230         CHECK_NULL_METHOD(get_file_info_from_handle);
1231         CHECK_NULL_METHOD(is_local);
1232         CHECK_NULL_METHOD(make_directory);
1233         CHECK_NULL_METHOD(remove_directory);
1234         CHECK_NULL_METHOD(move);
1235         CHECK_NULL_METHOD(unlink);
1236         CHECK_NULL_METHOD(check_same_fs);
1237         CHECK_NULL_METHOD(set_file_info);
1238         CHECK_NULL_METHOD(truncate_handle);
1239         CHECK_NULL_METHOD(find_directory);
1240         /*CHECK_NULL_METHOD(create_symbolic_link);*/
1241         retval->base_method.create_symbolic_link = NULL;
1242 #undef CHECK_NULL_METHOD
1243
1244         return (GnomeVFSMethod *) retval;
1245 }
1246
1247 void
1248 vfs_module_shutdown (GnomeVFSMethod * method)
1249 {
1250         TranslateMethod *tmethod = (TranslateMethod *) method;
1251
1252         tr_exec_cleanup (&(tmethod->exec_state));
1253
1254         tr_args_free(&tmethod->pa);
1255
1256         g_free(tmethod);
1257 }