make open file handles thread-safe. Fixes RT #57517 when using threads.
[perl-fuse.git] / Fuse.xs
1 #define PERL_NO_GET_CONTEXT
2 #include "EXTERN.h"
3 #include "perl.h"
4 #include "XSUB.h"
5
6 #include <fuse.h>
7
8 /* Determine if threads support should be included */
9 #ifdef USE_ITHREADS
10 # ifdef I_PTHREAD
11 #  define FUSE_USE_ITHREADS
12 # else
13 #  warning "Sorry, I don't know how to handle ithreads on this architecture. Building non-threaded version"
14 # endif
15 #endif
16
17 /* Global Data */
18
19 #define MY_CXT_KEY "Fuse::_guts" XS_VERSION
20 #define N_CALLBACKS 25
21
22 typedef struct {
23         SV *callback[N_CALLBACKS];
24         HV *handles;
25         tTHX self;
26         int threaded;
27         perl_mutex mutex;
28 } my_cxt_t;
29 START_MY_CXT;
30
31 #ifdef FUSE_USE_ITHREADS
32 tTHX master_interp = NULL;
33 # define FUSE_CONTEXT_PRE dTHX; if(!aTHX) {                   \
34         dMY_CXT_INTERP(master_interp);                            \
35         if(MY_CXT.threaded) {                                     \
36                 MUTEX_LOCK(&MY_CXT.mutex);                            \
37                 PERL_SET_CONTEXT(master_interp);                      \
38                 my_perl = perl_clone(MY_CXT.self, CLONEf_CLONE_HOST); \
39                 MUTEX_UNLOCK(&MY_CXT.mutex);                          \
40         }                                                         \
41 } { dMY_CXT; dSP;
42 # define FUSE_CONTEXT_POST }
43 #else
44 # define FUSE_CONTEXT_PRE dTHX; dMY_CXT; dSP;
45 # define FUSE_CONTEXT_POST
46 #endif
47
48 #undef DEBUGf
49 #if 0
50 #define DEBUGf(f, a...) fprintf(stderr, "%s:%d (%i): " f,__BASE_FILE__,__LINE__,sp-PL_stack_base ,##a )
51 #else
52 #define DEBUGf(a...)
53 #endif
54
55 #define FH_KEY(fi) sv_2mortal(newSViv((fi)->fh))
56 #define FH_GETHANDLE(fi) S_fh_get_handle(aTHX_ aMY_CXT_ fi)
57 #define FH_STOREHANDLE(fi,sv) S_fh_store_handle(aTHX_ aMY_CXT_ fi, sv)
58 #define FH_RELEASEHANDLE(fi) S_fh_release_handle(aTHX_ aMY_CXT_ fi)
59
60 SV *S_fh_get_handle(pTHX_ pMY_CXT_ struct fuse_file_info *fi) {
61         SV *val;
62         val = &PL_sv_undef;
63         if(fi->fh != 0) {
64                 HE *he;
65                 if((he = hv_fetch_ent(MY_CXT.handles, FH_KEY(fi), 0, 0))) {
66                         val = HeVAL(he);
67                         mg_get(val);
68                 }
69         }
70         return val;
71 }
72
73 void S_fh_release_handle(pTHX_ pMY_CXT_ struct fuse_file_info *fi) {
74         if(fi->fh != 0) {
75                 (void)hv_delete_ent(MY_CXT.handles, FH_KEY(fi), 0, G_DISCARD);
76                 fi->fh = 0;
77         }
78 }
79
80 void S_fh_store_handle(pTHX_ pMY_CXT_ struct fuse_file_info *fi, SV *sv) {
81         if(SvOK(sv)) {
82 #ifdef FUSE_USE_ITHREADS
83                 if(MY_CXT.threaded) {
84                         SvSHARE(sv);
85                 }
86 #endif
87                 MAGIC *mg = mg_find(sv, PERL_MAGIC_shared_scalar);
88                 fi->fh = mg ? PTR2IV(mg->mg_ptr) : PTR2IV(sv);
89                 (void)hv_store_ent(MY_CXT.handles, FH_KEY(fi), sv, 0);
90                 mg_set(sv);
91         }
92 }
93
94 int _PLfuse_getattr(const char *file, struct stat *result) {
95         int rv;
96         FUSE_CONTEXT_PRE;
97         DEBUGf("getattr begin: %s\n",file);
98         ENTER;
99         SAVETMPS;
100         PUSHMARK(SP);
101         XPUSHs(sv_2mortal(newSVpv(file,strlen(file))));
102         PUTBACK;
103         rv = call_sv(MY_CXT.callback[0],G_ARRAY);
104         SPAGAIN;
105         if(rv != 13) {
106                 if(rv > 1) {
107                         fprintf(stderr,"inappropriate number of returned values from getattr\n");
108                         rv = -ENOSYS;
109                 } else if(rv)
110                         rv = POPi;
111                 else
112                         rv = -ENOENT;
113         } else {
114                 result->st_blocks = POPi;
115                 result->st_blksize = POPi;
116                 result->st_ctime = POPi;
117                 result->st_mtime = POPi;
118                 result->st_atime = POPi;
119                 result->st_size = POPn; // we pop double here to support files larger than 4Gb (long limit)
120                 result->st_rdev = POPi;
121                 result->st_gid = POPi;
122                 result->st_uid = POPi;
123                 result->st_nlink = POPi;
124                 result->st_mode = POPi;
125                 result->st_ino   = POPi;
126                 result->st_dev = POPi;
127                 rv = 0;
128         }
129         FREETMPS;
130         LEAVE;
131         PUTBACK;
132         DEBUGf("getattr end: %i\n",rv);
133         FUSE_CONTEXT_POST;
134         return rv;
135 }
136
137 int _PLfuse_readlink(const char *file,char *buf,size_t buflen) {
138         int rv;
139         if(buflen < 1)
140                 return EINVAL;
141         FUSE_CONTEXT_PRE;
142         DEBUGf("readlink begin\n");
143         ENTER;
144         SAVETMPS;
145         PUSHMARK(SP);
146         XPUSHs(sv_2mortal(newSVpv(file,0)));
147         PUTBACK;
148         rv = call_sv(MY_CXT.callback[1],G_SCALAR);
149         SPAGAIN;
150         if(!rv)
151                 rv = -ENOENT;
152         else {
153                 SV *mysv = POPs;
154                 if(SvTYPE(mysv) == SVt_IV || SvTYPE(mysv) == SVt_NV)
155                         rv = SvIV(mysv);
156                 else {
157                         strncpy(buf,SvPV_nolen(mysv),buflen);
158                         rv = 0;
159                 }
160         }
161         FREETMPS;
162         LEAVE;
163         buf[buflen-1] = 0;
164         PUTBACK;
165         DEBUGf("readlink end: %i\n",rv);
166         FUSE_CONTEXT_POST;
167         return rv;
168 }
169
170 #if 0
171 /*
172  * This doesn't yet work... we alwas get ENOSYS when trying to use readdir().
173  * Well, of course, getdir() is fine as well.
174  */
175  int _PLfuse_readdir(const char *file, void *dirh, fuse_fill_dir_t dirfil, off_t off, struct fuse_file_info *fi) {
176 #endif
177 int _PLfuse_getdir(const char *file, fuse_dirh_t dirh, fuse_dirfil_t dirfil) {
178         int prv, rv;
179         FUSE_CONTEXT_PRE;
180         DEBUGf("getdir begin\n");
181         ENTER;
182         SAVETMPS;
183         PUSHMARK(SP);
184         XPUSHs(sv_2mortal(newSVpv(file,0)));
185         PUTBACK;
186         prv = call_sv(MY_CXT.callback[2],G_ARRAY);
187         SPAGAIN;
188         if(prv) {
189                 rv = POPi;
190                 while(--prv)
191                         dirfil(dirh,POPp,0,0);
192         } else {
193                 fprintf(stderr,"getdir() handler returned nothing!\n");
194                 rv = -ENOSYS;
195         }
196         FREETMPS;
197         LEAVE;
198         PUTBACK;
199         DEBUGf("getdir end: %i\n",rv);
200         FUSE_CONTEXT_POST;
201         return rv;
202 }
203
204 int _PLfuse_mknod (const char *file, mode_t mode, dev_t dev) {
205         int rv;
206         FUSE_CONTEXT_PRE;
207         DEBUGf("mknod begin\n");
208         ENTER;
209         SAVETMPS;
210         PUSHMARK(SP);
211         XPUSHs(sv_2mortal(newSVpv(file,0)));
212         XPUSHs(sv_2mortal(newSViv(mode)));
213         XPUSHs(sv_2mortal(newSViv(dev)));
214         PUTBACK;
215         rv = call_sv(MY_CXT.callback[3],G_SCALAR);
216         SPAGAIN;
217         if(rv)
218                 rv = POPi;
219         else
220                 rv = 0;
221         FREETMPS;
222         LEAVE;
223         PUTBACK;
224         DEBUGf("mknod end: %i\n",rv);
225         FUSE_CONTEXT_POST;
226         return rv;
227 }
228
229 int _PLfuse_mkdir (const char *file, mode_t mode) {
230         int rv;
231         FUSE_CONTEXT_PRE;
232         DEBUGf("mkdir begin\n");
233         ENTER;
234         SAVETMPS;
235         PUSHMARK(SP);
236         XPUSHs(sv_2mortal(newSVpv(file,0)));
237         XPUSHs(sv_2mortal(newSViv(mode)));
238         PUTBACK;
239         rv = call_sv(MY_CXT.callback[4],G_SCALAR);
240         SPAGAIN;
241         if(rv)
242                 rv = POPi;
243         else
244                 rv = 0;
245         FREETMPS;
246         LEAVE;
247         PUTBACK;
248         DEBUGf("mkdir end: %i\n",rv);
249         FUSE_CONTEXT_POST;
250         return rv;
251 }
252
253
254 int _PLfuse_unlink (const char *file) {
255         int rv;
256         FUSE_CONTEXT_PRE;
257         DEBUGf("unlink begin\n");
258         ENTER;
259         SAVETMPS;
260         PUSHMARK(SP);
261         XPUSHs(sv_2mortal(newSVpv(file,0)));
262         PUTBACK;
263         rv = call_sv(MY_CXT.callback[5],G_SCALAR);
264         SPAGAIN;
265         if(rv)
266                 rv = POPi;
267         else
268                 rv = 0;
269         FREETMPS;
270         LEAVE;
271         PUTBACK;
272         DEBUGf("unlink end: %i\n",rv);
273         FUSE_CONTEXT_POST;
274         return rv;
275 }
276
277 int _PLfuse_rmdir (const char *file) {
278         int rv;
279         FUSE_CONTEXT_PRE;
280         DEBUGf("rmdir begin\n");
281         ENTER;
282         SAVETMPS;
283         PUSHMARK(SP);
284         XPUSHs(sv_2mortal(newSVpv(file,0)));
285         PUTBACK;
286         rv = call_sv(MY_CXT.callback[6],G_SCALAR);
287         SPAGAIN;
288         if(rv)
289                 rv = POPi;
290         else
291                 rv = 0;
292         FREETMPS;
293         LEAVE;
294         PUTBACK;
295         DEBUGf("rmdir end: %i\n",rv);
296         FUSE_CONTEXT_POST;
297         return rv;
298 }
299
300 int _PLfuse_symlink (const char *file, const char *new) {
301         int rv;
302         FUSE_CONTEXT_PRE;
303         DEBUGf("symlink begin\n");
304         ENTER;
305         SAVETMPS;
306         PUSHMARK(SP);
307         XPUSHs(sv_2mortal(newSVpv(file,0)));
308         XPUSHs(sv_2mortal(newSVpv(new,0)));
309         PUTBACK;
310         rv = call_sv(MY_CXT.callback[7],G_SCALAR);
311         SPAGAIN;
312         if(rv)
313                 rv = POPi;
314         else
315                 rv = 0;
316         FREETMPS;
317         LEAVE;
318         PUTBACK;
319         DEBUGf("symlink end: %i\n",rv);
320         FUSE_CONTEXT_POST;
321         return rv;
322 }
323
324 int _PLfuse_rename (const char *file, const char *new) {
325         int rv;
326         FUSE_CONTEXT_PRE;
327         DEBUGf("rename begin\n");
328         ENTER;
329         SAVETMPS;
330         PUSHMARK(SP);
331         XPUSHs(sv_2mortal(newSVpv(file,0)));
332         XPUSHs(sv_2mortal(newSVpv(new,0)));
333         PUTBACK;
334         rv = call_sv(MY_CXT.callback[8],G_SCALAR);
335         SPAGAIN;
336         if(rv)
337                 rv = POPi;
338         else
339                 rv = 0;
340         FREETMPS;
341         LEAVE;
342         PUTBACK;
343         DEBUGf("rename end: %i\n",rv);
344         FUSE_CONTEXT_POST;
345         return rv;
346 }
347
348 int _PLfuse_link (const char *file, const char *new) {
349         int rv;
350         FUSE_CONTEXT_PRE;
351         DEBUGf("link begin\n");
352         ENTER;
353         SAVETMPS;
354         PUSHMARK(SP);
355         XPUSHs(sv_2mortal(newSVpv(file,0)));
356         XPUSHs(sv_2mortal(newSVpv(new,0)));
357         PUTBACK;
358         rv = call_sv(MY_CXT.callback[9],G_SCALAR);
359         SPAGAIN;
360         if(rv)
361                 rv = POPi;
362         else
363                 rv = 0;
364         FREETMPS;
365         LEAVE;
366         PUTBACK;
367         DEBUGf("link end: %i\n",rv);
368         FUSE_CONTEXT_POST;
369         return rv;
370 }
371
372 int _PLfuse_chmod (const char *file, mode_t mode) {
373         int rv;
374         FUSE_CONTEXT_PRE;
375         DEBUGf("chmod begin\n");
376         ENTER;
377         SAVETMPS;
378         PUSHMARK(SP);
379         XPUSHs(sv_2mortal(newSVpv(file,0)));
380         XPUSHs(sv_2mortal(newSViv(mode)));
381         PUTBACK;
382         rv = call_sv(MY_CXT.callback[10],G_SCALAR);
383         SPAGAIN;
384         if(rv)
385                 rv = POPi;
386         else
387                 rv = 0;
388         FREETMPS;
389         LEAVE;
390         PUTBACK;
391         DEBUGf("chmod end: %i\n",rv);
392         FUSE_CONTEXT_POST;
393         return rv;
394 }
395
396 int _PLfuse_chown (const char *file, uid_t uid, gid_t gid) {
397         int rv;
398         FUSE_CONTEXT_PRE;
399         DEBUGf("chown begin\n");
400         ENTER;
401         SAVETMPS;
402         PUSHMARK(SP);
403         XPUSHs(sv_2mortal(newSVpv(file,0)));
404         XPUSHs(sv_2mortal(newSViv(uid)));
405         XPUSHs(sv_2mortal(newSViv(gid)));
406         PUTBACK;
407         rv = call_sv(MY_CXT.callback[11],G_SCALAR);
408         SPAGAIN;
409         if(rv)
410                 rv = POPi;
411         else
412                 rv = 0;
413         FREETMPS;
414         LEAVE;
415         PUTBACK;
416         DEBUGf("chown end: %i\n",rv);
417         FUSE_CONTEXT_POST;
418         return rv;
419 }
420
421 int _PLfuse_truncate (const char *file, off_t off) {
422         int rv;
423 #ifndef PERL_HAS_64BITINT
424         char *temp;
425 #endif
426         FUSE_CONTEXT_PRE;
427         DEBUGf("truncate begin\n");
428         ENTER;
429         SAVETMPS;
430         PUSHMARK(SP);
431         XPUSHs(sv_2mortal(newSVpv(file,0)));
432 #ifdef PERL_HAS_64BITINT
433         XPUSHs(sv_2mortal(newSViv(off)));
434 #else
435         asprintf(&temp, "%llu", off);
436         XPUSHs(sv_2mortal(newSVpv(temp, 0)));
437         free(temp);
438 #endif
439         PUTBACK;
440         rv = call_sv(MY_CXT.callback[12],G_SCALAR);
441         SPAGAIN;
442         if(rv)
443                 rv = POPi;
444         else
445                 rv = 0;
446         FREETMPS;
447         LEAVE;
448         PUTBACK;
449         DEBUGf("truncate end: %i\n",rv);
450         FUSE_CONTEXT_POST;
451         return rv;
452 }
453
454 int _PLfuse_utime (const char *file, struct utimbuf *uti) {
455         int rv;
456         FUSE_CONTEXT_PRE;
457         DEBUGf("utime begin\n");
458         ENTER;
459         SAVETMPS;
460         PUSHMARK(SP);
461         XPUSHs(sv_2mortal(newSVpv(file,0)));
462         XPUSHs(sv_2mortal(newSViv(uti->actime)));
463         XPUSHs(sv_2mortal(newSViv(uti->modtime)));
464         PUTBACK;
465         rv = call_sv(MY_CXT.callback[13],G_SCALAR);
466         SPAGAIN;
467         if(rv)
468                 rv = POPi;
469         else
470                 rv = 0;
471         FREETMPS;
472         LEAVE;
473         PUTBACK;
474         DEBUGf("utime end: %i\n",rv);
475         FUSE_CONTEXT_POST;
476         return rv;
477 }
478
479 int _PLfuse_open (const char *file, struct fuse_file_info *fi) {
480         int rv;
481         int flags = fi->flags;
482         HV *fihash;
483         FUSE_CONTEXT_PRE;
484         DEBUGf("open begin\n");
485         ENTER;
486         SAVETMPS;
487         PUSHMARK(SP);
488         XPUSHs(sv_2mortal(newSVpv(file,0)));
489         XPUSHs(sv_2mortal(newSViv(flags)));
490         /* Create a hashref containing the details from fi
491          * which we can look at or modify.
492          */
493         fi->fh = 0; /* Ensure it starts with 0 - important if they don't set it */
494         fihash = newHV();
495 #if FUSE_VERSION >= 24
496         hv_store(fihash, "direct_io", 9, newSViv(fi->direct_io), 0);
497         hv_store(fihash, "keep_cache", 10, newSViv(fi->keep_cache), 0);
498 #endif
499 #if FUSE_VERSION >= 29
500         hv_store(fihash, "nonseekable", 11, newSViv(fi->nonseekable), 0);
501 #endif
502         XPUSHs(sv_2mortal(newRV_noinc((SV*) fihash)));
503         /* All hashref things done */
504
505         PUTBACK;
506         /* Open called with filename, flags */
507         rv = call_sv(MY_CXT.callback[14],G_ARRAY);
508         SPAGAIN;
509         if(rv) {
510                 if(rv > 1) {
511                         FH_STOREHANDLE(fi,POPs);
512                 }
513                 rv = POPi;
514         }
515         else
516                 rv = 0;
517         if (rv == 0)
518         {
519                 /* Success, so copy the file handle which they returned */
520 #if FUSE_VERSION >= 24
521                 SV **svp;
522                 svp = hv_fetch(fihash, "direct_io", 9, 0);
523                 if (svp != NULL)
524                 {
525                         fi->direct_io = SvIV(*svp);
526                 }
527                 svp = hv_fetch(fihash, "keep_cache", 10, 0);
528                 if (svp != NULL)
529                 {
530                         fi->keep_cache = SvIV(*svp);
531                 }
532 #endif
533 #if FUSE_VERSION >= 29
534                 svp = hv_fetch(fihash, "nonseekable", 11, 0);
535                 if (svp != NULL)
536                 {
537                         fi->nonseekable = SvIV(*svp);
538                 }
539 #endif
540         }
541         FREETMPS;
542         LEAVE;
543         PUTBACK;
544         DEBUGf("open end: %i\n",rv);
545         FUSE_CONTEXT_POST;
546         return rv;
547 }
548
549 int _PLfuse_read (const char *file, char *buf, size_t buflen, off_t off, struct fuse_file_info *fi) {
550         int rv;
551 #ifndef PERL_HAS_64BITINT
552         char *temp;
553 #endif
554         FUSE_CONTEXT_PRE;
555         DEBUGf("read begin\n");
556         ENTER;
557         SAVETMPS;
558         PUSHMARK(SP);
559         XPUSHs(sv_2mortal(newSVpv(file,0)));
560         XPUSHs(sv_2mortal(newSViv(buflen)));
561 #ifdef PERL_HAS_64BITINT
562         XPUSHs(sv_2mortal(newSViv(off)));
563 #else
564         asprintf(&temp, "%llu", off);
565         XPUSHs(sv_2mortal(newSVpv(temp, 0)));
566         free(temp);
567 #endif
568         XPUSHs(FH_GETHANDLE(fi));
569         PUTBACK;
570         rv = call_sv(MY_CXT.callback[15],G_SCALAR);
571         SPAGAIN;
572         if(!rv)
573                 rv = -ENOENT;
574         else {
575                 SV *mysv = POPs;
576                 if(SvTYPE(mysv) == SVt_NV || SvTYPE(mysv) == SVt_IV)
577                         rv = SvIV(mysv);
578                 else {
579                         if(SvPOK(mysv)) {
580                                 rv = SvCUR(mysv);
581                         } else {
582                                 rv = 0;
583                         }
584                         if(rv > buflen)
585                                 croak("read() handler returned more than buflen! (%i > %i)",rv,buflen);
586                         if(rv)
587                                 memcpy(buf,SvPV_nolen(mysv),rv);
588                 }
589         }
590         FREETMPS;
591         LEAVE;
592         PUTBACK;
593         DEBUGf("read end: %i\n",rv);
594         FUSE_CONTEXT_POST;
595         return rv;
596 }
597
598 int _PLfuse_write (const char *file, const char *buf, size_t buflen, off_t off, struct fuse_file_info *fi) {
599         int rv;
600 #ifndef PERL_HAS_64BITINT
601         char *temp;
602 #endif
603         FUSE_CONTEXT_PRE;
604         DEBUGf("write begin\n");
605         ENTER;
606         SAVETMPS;
607         PUSHMARK(SP);
608         XPUSHs(sv_2mortal(newSVpv(file,0)));
609         XPUSHs(sv_2mortal(newSVpvn(buf,buflen)));
610 #ifdef PERL_HAS_64BITINT
611         XPUSHs(sv_2mortal(newSViv(off)));
612 #else
613         asprintf(&temp, "%llu", off);
614         XPUSHs(sv_2mortal(newSVpv(temp, 0)));
615         free(temp);
616 #endif
617         XPUSHs(FH_GETHANDLE(fi));
618         PUTBACK;
619         rv = call_sv(MY_CXT.callback[16],G_SCALAR);
620         SPAGAIN;
621         if(rv)
622                 rv = POPi;
623         else
624                 rv = 0;
625         FREETMPS;
626         LEAVE;
627         PUTBACK;
628         DEBUGf("write end: %i\n",rv);
629         FUSE_CONTEXT_POST;
630         return rv;
631 }
632
633 int _PLfuse_statfs (const char *file, struct statvfs *st) {
634         int rv;
635         FUSE_CONTEXT_PRE;
636         DEBUGf("statfs begin\n");
637         ENTER;
638         SAVETMPS;
639         PUSHMARK(SP);
640         PUTBACK;
641         rv = call_sv(MY_CXT.callback[17],G_ARRAY);
642         SPAGAIN;
643         DEBUGf("statfs got %i params\n",rv);
644         if(rv == 6 || rv == 7) {
645                 st->f_bsize     = POPi;
646                 st->f_bfree     = POPi;
647                 st->f_blocks    = POPi;
648                 st->f_ffree     = POPi;
649                 st->f_files     = POPi;
650                 st->f_namemax   = POPi;
651                 /* zero and fill-in other */
652                 st->f_fsid = 0;
653                 st->f_frsize = 4096;
654                 st->f_flag = 0;
655                 st->f_bavail = st->f_bfree;
656                 st->f_favail = st->f_ffree;
657
658                 if(rv == 7)
659                         rv = POPi;
660                 else
661                         rv = 0;
662         } else
663         if(rv > 1)
664                 croak("inappropriate number of returned values from statfs");
665         else
666         if(rv)
667                 rv = POPi;
668         else
669                 rv = -ENOSYS;
670         FREETMPS;
671         LEAVE;
672         PUTBACK;
673         DEBUGf("statfs end: %i\n",rv);
674         FUSE_CONTEXT_POST;
675         return rv;
676 }
677
678 int _PLfuse_flush (const char *file, struct fuse_file_info *fi) {
679         int rv;
680         FUSE_CONTEXT_PRE;
681         DEBUGf("flush begin\n");
682         ENTER;
683         SAVETMPS;
684         PUSHMARK(SP);
685         XPUSHs(sv_2mortal(newSVpv(file,0)));
686         XPUSHs(FH_GETHANDLE(fi));
687         PUTBACK;
688         rv = call_sv(MY_CXT.callback[18],G_SCALAR);
689         SPAGAIN;
690         if(rv)
691                 rv = POPi;
692         else
693                 rv = 0;
694         FREETMPS;
695         LEAVE;
696         PUTBACK;
697         DEBUGf("flush end: %i\n",rv);
698         FUSE_CONTEXT_POST;
699         return rv;
700 }
701
702 int _PLfuse_release (const char *file, struct fuse_file_info *fi) {
703         int rv;
704         int flags = fi->flags;
705         FUSE_CONTEXT_PRE;
706         DEBUGf("release begin\n");
707         ENTER;
708         SAVETMPS;
709         PUSHMARK(SP);
710         XPUSHs(sv_2mortal(newSVpv(file,0)));
711         XPUSHs(sv_2mortal(newSViv(flags)));
712         XPUSHs(FH_GETHANDLE(fi));
713         PUTBACK;
714         rv = call_sv(MY_CXT.callback[19],G_SCALAR);
715         SPAGAIN;
716         if(rv)
717                 rv = POPi;
718         else
719                 rv = 0;
720         FH_RELEASEHANDLE(fi);
721         FREETMPS;
722         LEAVE;
723         PUTBACK;
724         DEBUGf("release end: %i\n",rv);
725         FUSE_CONTEXT_POST;
726         return rv;
727 }
728
729 int _PLfuse_fsync (const char *file, int datasync, struct fuse_file_info *fi) {
730         int rv;
731         int flags = fi->flags;
732         FUSE_CONTEXT_PRE;
733         DEBUGf("fsync begin\n");
734         ENTER;
735         SAVETMPS;
736         PUSHMARK(SP);
737         XPUSHs(sv_2mortal(newSVpv(file,0)));
738         XPUSHs(sv_2mortal(newSViv(flags)));
739         XPUSHs(FH_GETHANDLE(fi));
740         PUTBACK;
741         rv = call_sv(MY_CXT.callback[20],G_SCALAR);
742         SPAGAIN;
743         if(rv)
744                 rv = POPi;
745         else
746                 rv = 0;
747         FREETMPS;
748         LEAVE;
749         PUTBACK;
750         DEBUGf("fsync end: %i\n",rv);
751         FUSE_CONTEXT_POST;
752         return rv;
753 }
754
755 int _PLfuse_setxattr (const char *file, const char *name, const char *buf, size_t buflen, int flags) {
756         int rv;
757         FUSE_CONTEXT_PRE;
758         DEBUGf("setxattr begin\n");
759         ENTER;
760         SAVETMPS;
761         PUSHMARK(SP);
762         XPUSHs(sv_2mortal(newSVpv(file,0)));
763         XPUSHs(sv_2mortal(newSVpv(name,0)));
764         XPUSHs(sv_2mortal(newSVpvn(buf,buflen)));
765         XPUSHs(sv_2mortal(newSViv(flags)));
766         PUTBACK;
767         rv = call_sv(MY_CXT.callback[21],G_SCALAR);
768         SPAGAIN;
769         if(rv)
770                 rv = POPi;
771         else
772                 rv = 0;
773         FREETMPS;
774         LEAVE;
775         PUTBACK;
776         DEBUGf("setxattr end: %i\n",rv);
777         FUSE_CONTEXT_POST;
778         return rv;
779 }
780
781 int _PLfuse_getxattr (const char *file, const char *name, char *buf, size_t buflen) {
782         int rv;
783         FUSE_CONTEXT_PRE;
784         DEBUGf("getxattr begin\n");
785         ENTER;
786         SAVETMPS;
787         PUSHMARK(SP);
788         XPUSHs(sv_2mortal(newSVpv(file,0)));
789         XPUSHs(sv_2mortal(newSVpv(name,0)));
790         PUTBACK;
791         rv = call_sv(MY_CXT.callback[22],G_SCALAR);
792         SPAGAIN;
793         if(!rv)
794                 rv = -ENOENT;
795         else {
796                 SV *mysv = POPs;
797
798                 rv = 0;
799                 if(SvTYPE(mysv) == SVt_NV || SvTYPE(mysv) == SVt_IV)
800                         rv = SvIV(mysv);
801                 else {
802                         if(SvPOK(mysv)) {
803                                 rv = SvCUR(mysv);
804                         } else {
805                                 rv = 0;
806                         }
807                         if ((rv > 0) && (buflen > 0))
808                         {
809                                 if(rv > buflen)
810                                         rv = -ERANGE;
811                                 else
812                                         memcpy(buf,SvPV_nolen(mysv),rv);
813                         }
814                 }
815         }
816         FREETMPS;
817         LEAVE;
818         PUTBACK;
819         DEBUGf("getxattr end: %i\n",rv);
820         FUSE_CONTEXT_POST;
821         return rv;
822 }
823
824 int _PLfuse_listxattr (const char *file, char *list, size_t size) {
825         int prv, rv;
826         FUSE_CONTEXT_PRE;
827         DEBUGf("listxattr begin\n");
828         ENTER;
829         SAVETMPS;
830         PUSHMARK(SP);
831         XPUSHs(sv_2mortal(newSVpv(file,0)));
832         PUTBACK;
833         prv = call_sv(MY_CXT.callback[23],G_ARRAY);
834         SPAGAIN;
835         if(!prv)
836                 rv = -ENOENT;
837         else {
838
839                 char *p = list;
840                 int spc = size;
841                 int total_len = 0;
842
843                 rv = POPi;
844                 prv--;
845
846                 /* Always nul terminate */
847                 if (list && (size > 0))
848                         list[0] = '\0';
849
850                 while (prv > 0)
851                 {
852                         SV *mysv = POPs;
853                         prv--;
854
855                         if (SvPOK(mysv)) {
856                                 /* Copy nul too */
857                                 int s = SvCUR(mysv) + 1;
858                                 total_len += s;
859
860                                 if (p && (size > 0) && (spc >= s))
861                                 {
862                                         memcpy(p,SvPV_nolen(mysv),s);
863                                         p += s;
864                                         spc -= s;
865                                 }
866                         }
867                 }
868
869                 /*
870                  * If the Perl returned an error, return that.
871                  * Otherwise check that the buffer was big enough.
872                  */
873                 if (rv == 0)
874                 {
875                         rv = total_len;
876                         if ((size > 0) && (size < total_len))
877                                 rv = -ERANGE;
878                 }
879         }
880         FREETMPS;
881         LEAVE;
882         PUTBACK;
883         DEBUGf("listxattr end: %i\n",rv);
884         FUSE_CONTEXT_POST;
885         return rv;
886 }
887
888 int _PLfuse_removexattr (const char *file, const char *name) {
889         int rv;
890         FUSE_CONTEXT_PRE;
891         DEBUGf("removexattr begin\n");
892         ENTER;
893         SAVETMPS;
894         PUSHMARK(SP);
895         XPUSHs(sv_2mortal(newSVpv(file,0)));
896         XPUSHs(sv_2mortal(newSVpv(name,0)));
897         PUTBACK;
898         rv = call_sv(MY_CXT.callback[24],G_SCALAR);
899         SPAGAIN;
900         if(rv)
901                 rv = POPi;
902         else
903                 rv = 0;
904         FREETMPS;
905         LEAVE;
906         PUTBACK;
907         DEBUGf("removexattr end: %i\n",rv);
908         FUSE_CONTEXT_POST;
909         return rv;
910 }
911
912 struct fuse_operations _available_ops = {
913 getattr:                _PLfuse_getattr,
914 readlink:               _PLfuse_readlink,
915 getdir:                 _PLfuse_getdir,
916 #if 0
917 readdir:                _PLfuse_readdir,
918 #endif
919 mknod:                  _PLfuse_mknod,
920 mkdir:                  _PLfuse_mkdir,
921 unlink:                 _PLfuse_unlink,
922 rmdir:                  _PLfuse_rmdir,
923 symlink:                _PLfuse_symlink,
924 rename:                 _PLfuse_rename,
925 link:                   _PLfuse_link,
926 chmod:                  _PLfuse_chmod,
927 chown:                  _PLfuse_chown,
928 truncate:               _PLfuse_truncate,
929 utime:                  _PLfuse_utime,
930 open:                   _PLfuse_open,
931 read:                   _PLfuse_read,
932 write:                  _PLfuse_write,
933 statfs:                 _PLfuse_statfs,
934 flush:                  _PLfuse_flush,
935 release:                _PLfuse_release,
936 fsync:                  _PLfuse_fsync,
937 setxattr:               _PLfuse_setxattr,
938 getxattr:               _PLfuse_getxattr,
939 listxattr:              _PLfuse_listxattr,
940 removexattr:            _PLfuse_removexattr,
941 };
942
943 MODULE = Fuse           PACKAGE = Fuse
944 PROTOTYPES: DISABLE
945
946 BOOT:
947         MY_CXT_INIT;
948         MY_CXT.self = aTHX;
949
950 void
951 CLONE(...)
952         PREINIT:
953                 int i;
954                 dTHX;
955         CODE:
956                 MY_CXT_CLONE;
957                 tTHX parent = MY_CXT.self;
958                 MY_CXT.self = my_perl;
959 #if (PERL_VERSION < 13) || (PERL_VERSION == 13 && PERL_SUBVERSION <= 1)
960                 CLONE_PARAMS clone_param;
961                 clone_param.flags = 0;
962                 for(i=0;i<N_CALLBACKS;i++) {
963                         if(MY_CXT.callback[i]) {
964                                 MY_CXT.callback[i] = sv_dup(MY_CXT.callback[i], &clone_param);
965                         }
966                 }
967 #else
968                 CLONE_PARAMS *clone_param = clone_params_new(parent, aTHX);
969                 for(i=0;i<N_CALLBACKS;i++) {
970                         if(MY_CXT.callback[i]) {
971                                 MY_CXT.callback[i] = sv_dup(MY_CXT.callback[i], clone_param);
972                         }
973                 }
974                 clone_params_del(clone_param);
975 #endif
976
977 SV*
978 fuse_get_context()
979         PREINIT:
980         struct fuse_context *fc;
981         CODE:
982         fc = fuse_get_context();
983         if(fc) {
984                 HV *hash = newHV();
985                 hv_store(hash, "uid", 3, newSViv(fc->uid), 0);
986                 hv_store(hash, "gid", 3, newSViv(fc->gid), 0);
987                 hv_store(hash, "pid", 3, newSViv(fc->pid), 0);
988                 RETVAL = newRV_noinc((SV*)hash);
989         } else {
990                 XSRETURN_UNDEF;
991         }
992         OUTPUT:
993         RETVAL
994
995 void
996 perl_fuse_main(...)
997         PREINIT:
998         struct fuse_operations fops = 
999                 {NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,
1000                  NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL};
1001         int i, debug;
1002         char *mountpoint;
1003         char *mountopts;
1004         struct fuse_args margs = FUSE_ARGS_INIT(0, NULL);
1005         struct fuse_args fargs = FUSE_ARGS_INIT(0, NULL);
1006         struct fuse_chan *fc;
1007         dMY_CXT;
1008         INIT:
1009         if(items != 29) {
1010                 fprintf(stderr,"Perl<->C inconsistency or internal error\n");
1011                 XSRETURN_UNDEF;
1012         }
1013         CODE:
1014         debug = SvIV(ST(0));
1015         MY_CXT.threaded = SvIV(ST(1));
1016         MY_CXT.handles = MUTABLE_HV(sv_2mortal(MUTABLE_SV(newHV())));
1017         if(MY_CXT.threaded) {
1018 #ifdef FUSE_USE_ITHREADS
1019                 master_interp = aTHX;
1020                 MUTEX_INIT(&MY_CXT.mutex);
1021                 SvSHARE(MUTABLE_SV(MY_CXT.handles));
1022 #else
1023                 fprintf(stderr,"FUSE warning: Your script has requested multithreaded "
1024                                "mode, but your perl was not built with a supported "
1025                                "thread model. Threads are disabled.\n");
1026                 MY_CXT.threaded = 0;
1027 #endif
1028         }
1029         mountpoint = SvPV_nolen(ST(2));
1030         mountopts = SvPV_nolen(ST(3));
1031         for(i=0;i<N_CALLBACKS;i++) {
1032                 SV *var = ST(i+4);
1033                 /* allow symbolic references, or real code references. */
1034                 if(SvOK(var) && (SvPOK(var) || (SvROK(var) && SvTYPE(SvRV(var)) == SVt_PVCV))) {
1035                         void **tmp1 = (void**)&_available_ops, **tmp2 = (void**)&fops;
1036                         tmp2[i] = tmp1[i];
1037                         MY_CXT.callback[i] = var;
1038                 } else if(SvOK(var)) {
1039                         croak("invalid callback (%i) passed to perl_fuse_main "
1040                               "(%s is not a string, code ref, or undef).\n",
1041                               i+4,SvPVbyte_nolen(var));
1042                 } else {
1043                         MY_CXT.callback[i] = NULL;
1044                 }
1045         }
1046         /*
1047          * XXX: What comes here is just a ridiculous use of the option parsing API
1048          * to hack on compatibility with other parts of the new API. First and
1049          * foremost, real C argc/argv would be good to get at...
1050          */
1051         if (mountopts &&
1052             (fuse_opt_add_arg(&margs, "") == -1 ||
1053              fuse_opt_add_arg(&margs, "-o") == -1 ||
1054              fuse_opt_add_arg(&margs, mountopts) == -1)) {
1055                 fuse_opt_free_args(&margs);
1056                 croak("out of memory\n");
1057         }
1058         fc = fuse_mount(mountpoint,&margs);
1059         fuse_opt_free_args(&margs);        
1060         if (fc == NULL)
1061                 croak("could not mount fuse filesystem!\n");
1062         if (debug) {
1063                 if ( fuse_opt_add_arg(&fargs, "") == -1 ||
1064                         fuse_opt_add_arg(&fargs, "-d") == -1) {
1065                         fuse_opt_free_args(&fargs);
1066                         croak("out of memory\n");
1067                 }
1068         } else {
1069                 if (fuse_opt_add_arg(&fargs, "") == -1)
1070                         croak("out of memory\n");
1071         }
1072 #ifndef __NetBSD__
1073         if(MY_CXT.threaded) {
1074                 fuse_loop_mt(fuse_new(fc,&fargs,&fops,sizeof(fops),NULL));
1075         } else
1076 #endif
1077                 fuse_loop(fuse_new(fc,&fargs,&fops,sizeof(fops),NULL));
1078         fuse_unmount(mountpoint,fc);
1079         fuse_opt_free_args(&fargs);