pax_global_header00006660000000000000000000000064151667402630014524gustar00rootroot0000000000000052 comment=ca14fb43ba68cdd40799e1000103f6e6ddddcd79 libfilesys-fuse3-perl-0.021/000077500000000000000000000000001516674026300156565ustar00rootroot00000000000000libfilesys-fuse3-perl-0.021/.gitignore000066400000000000000000000001471516674026300176500ustar00rootroot00000000000000Makefile Makefile.old blib pm_to_blib *.o Fuse3.c Fuse3.bs MYMETA.* MANIFEST.bak Fuse-*.tar.gz Fuse-*/ libfilesys-fuse3-perl-0.021/AUTHORS000066400000000000000000000021221516674026300167230ustar00rootroot00000000000000Perl bindings ------------- Dominique Dumont - Filesys::Fuse3 maintainer Mark Glines - original author of perl bindings, ithreads Dobrica Pavlinusic - Fuse maintainer Richard Dawe - lot of improvements Mark Wilkinson - added mount options Csaba Henk - update to API 25 Vladimir V. Kolpakov - contributed cleanup for warnings Andrew Chadwick - fixes for Ubuntu and dh-make-perl (fakeroot) Chris Dolan - fixes for MacFuse 1.1.0 Reuben Thomas - contributed examples/filter_attr_fs.pl jaslong from CPAN::Forum - documentation patch for fuse_get_context Justin Fletcher - added file handles on open files Derrik Pates - added 64-bit support and fuse 2.6 binding, *BSD support Daniel Frett - make static callbacks thread-safe Alex Sudakov - fixes for readdir support Bojan Petrovic - FreeBSD support libfilesys-fuse3-perl-0.021/Changes000066400000000000000000000006151516674026300171530ustar00rootroot00000000000000Revision history for Perl module Filesys::Fuse3 0.021 2026-04-12 * fix(Makefile.PL): fix error message for Debian * fix(Makefile.PL): fix bugtracker and repository URLs * fix(Makefile.PL): fix test before cpan-upload 0.020 2026-04-11 - Forked from Fuse (https://github.com/dpavlin/perl-fuse.git) and updated to work with fuse3: - getdir, ftruncate and fgetattr functions are gone libfilesys-fuse3-perl-0.021/Fuse3.pm000077500000000000000000000677731516674026300172300ustar00rootroot00000000000000package Filesys::Fuse3; use v5.20; use strict; use warnings; use Errno; use Carp; use Config; use List::Util qw(sum); require Exporter; require DynaLoader; use AutoLoader; our @ISA = qw(Exporter DynaLoader); # Items to export into callers namespace by default. Note: do not export # names by default without a very good reason. Use EXPORT_OK instead. # Do not simply export all your public functions/methods/constants. # This allows declaration use Filesys::Fuse3 ':all'; # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK # will save memory. our %EXPORT_TAGS = ( 'all' => [ qw(FUSE_BUF_IS_FD FUSE_BUF_FD_SEEK FUSE_BUF_FD_RETRY UTIME_NOW UTIME_OMIT XATTR_CREATE XATTR_REPLACE fuse_get_context fuse_version fuse_buf_copy fuse_buf_size FUSE_IOCTL_COMPAT FUSE_IOCTL_UNRESTRICTED FUSE_IOCTL_RETRY FUSE_IOCTL_MAX_IOV notify_poll pollhandle_destroy) ], 'xattr' => [ qw(XATTR_CREATE XATTR_REPLACE) ], 'utime' => [ qw(UTIME_NOW UTIME_OMIT) ], 'zerocopy' => [ qw(FUSE_BUF_IS_FD FUSE_BUF_FD_SEEK FUSE_BUF_FD_RETRY) ], 'ioctl' => [ qw(FUSE_IOCTL_COMPAT FUSE_IOCTL_UNRESTRICTED FUSE_IOCTL_RETRY FUSE_IOCTL_MAX_IOV) ], ); our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); our @EXPORT = (); our $VERSION = '0.021'; sub AUTOLOAD { # This AUTOLOAD is used to 'autoload' constants from the constant() # XS function. If a constant is not found then control is passed # to the AUTOLOAD in AutoLoader. my $constname; our $AUTOLOAD; ($constname = $AUTOLOAD) =~ s/.*:://; croak '& not defined' if $constname eq 'constant'; my $val = constant($constname, @_ ? $_[0] : 0); if ($! != 0) { if ($!{EINVAL}) { $AutoLoader::AUTOLOAD = $AUTOLOAD; goto &AutoLoader::AUTOLOAD; } else { croak "Your vendor has not defined Fuse macro $constname"; } } { no strict 'refs'; # Fixed between 5.005_53 and 5.005_61 if ($] >= 5.00561) { *$AUTOLOAD = sub () { $val }; } else { *$AUTOLOAD = sub { $val }; } } goto &$AUTOLOAD; } bootstrap Filesys::Fuse3 $VERSION; use constant FUSE_IOCTL_COMPAT => (1 << 0); use constant FUSE_IOCTL_UNRESTRICTED => (1 << 1); use constant FUSE_IOCTL_RETRY => (1 << 2); use constant FUSE_IOCTL_MAX_IOV => 256; sub main { my ($fuse_vmajor, $fuse_vminor, $fuse_vmicro) = fuse_version(); my $fuse_version = $fuse_vmajor + ($fuse_vminor * 1.0 / 1_000) + ($fuse_vmicro * 1.0 / 1_000_000); # ⚠️ @names must match the callback_index enum defined in Fuse.xs my @names = qw(getattr readlink mknod mkdir unlink rmdir symlink rename link chmod chown truncate open read write statfs flush release fsync setxattr getxattr listxattr removexattr opendir readdir releasedir fsyncdir init destroy access create lock utimens bmap ioctl poll write_buf read_buf flock fallocate); my @subs = map {undef} @names; my $tmp = 0; my %mapping = map { $_ => $tmp++ } @names; # ⚠️ @otherargs must match the arg_index enum defined in Fuse.xs my @otherargs = qw(debug threaded mountpoint mountopts nullpath_ok utimens_as_array); my %otherargs = ( debug => 0, threaded => 0, mountpoint => '', mountopts => '', nullpath_ok => 0, utimens_as_array => 0, ); while(my $name = shift) { my ($subref) = shift; if(exists($otherargs{$name})) { $otherargs{$name} = $subref; } else { croak 'Usage: Filesys::Fuse3::main(getattr => "main::my_getattr", ...)' unless $subref; if (exists $mapping{$name}) { $subs[$mapping{$name}] = $subref; } else { carp "There is no function $name"; } } } if($otherargs{threaded}) { # make sure threads are both available, and loaded. if($Config{useithreads}) { if(exists($threads::{VERSION})) { if(exists($threads::shared::{VERSION})) { # threads will work. } else { carp("Thread support requires you to use threads::shared.\nThreads are disabled.\n"); $otherargs{threaded} = 0; } } else { carp("Thread support requires you to use threads and threads::shared.\nThreads are disabled.\n"); $otherargs{threaded} = 0; } } else { carp("Thread support was not compiled into this build of perl.\nThreads are disabled.\n"); $otherargs{threaded} = 0; } } perl_fuse3_main(@otherargs{@otherargs},@subs); } sub fuse_buf_size { my ($buf) = @_; return sum(map { $_->{size} } @$buf); } # Autoload methods go after =cut, and are processed by the autosplit program. 1; __END__ =head1 NAME Filesys::Fuse3 - write filesystems in Perl using Fuse3 =head1 SYNOPSIS use Filesys::Fuse3; Filesys::Fuse3::main( mountpoint => '/mnt/my_fs', threaded => 0, debug => 1, getattr => sub { ... }, # fetches attributes, like 'stat' getdir => sub { ... }, # obtains directory listings open => sub { ... }, # opens files statfs => sub { ... }, # returns filesystem data read => sub { ... }, # reads file contents # there are many more you can implement! # See "FUNCTIONS YOUR FILESYSTEM MAY IMPLEMENT" below. ); =head1 DESCRIPTION This module lets you implement filesystems in Perl, through the L (Filesystem in USErspace) kernel/lib interface. FUSE expects you to implement callbacks for the various functions. In the following definitions, "errno" can be 0 (for a success), -EINVAL, -ENOENT, -EONFIRE, any integer less than 1 really. You can import standard error constants by saying something like: use POSIX qw(EDOTDOT ENOANO); Every constant you need (file types, open() flags, error values, etc) can be imported either from POSIX or from Fcntl, often both. See their respective documentations, for more information. =head2 EXPORTED SYMBOLS None by default. You can request all exportable symbols by using the tag ":all". You can request the extended attribute symbols by using the tag ":xattr". This will export XATTR_CREATE and XATTR_REPLACE. =head2 FUNCTIONS =head3 Filesys::Fuse3::main Takes arguments in the form of hash key=>value pairs. There are many valid keys. Most of them correspond with names of callback functions, as described in section 'FUNCTIONS YOUR FILESYSTEM MAY IMPLEMENT'. A few special keys also exist: =over 1 =item debug => boolean This turns FUSE call tracing on and off. Default is 0 (which means off). =item mountpoint => string The point at which to mount this filesystem. There is no default, you must specify this. An example would be '/mnt'. =item mountopts => string This is a comma separated list of mount options to pass to the FUSE kernel module. At present, it allows the specification of the allow_other argument when mounting the new FUSE filesystem. To use this, you will also need 'user_allow_other' in /etc/fuse.conf as per the FUSE documention mountopts => "allow_other" or mountopts => "" =item threaded => boolean This turns FUSE multithreading on and off. The default is 0, meaning your FUSE script will run in single-threaded mode. Note that single-threaded mode also means that you will not have to worry about reentrancy, though you will have to worry about recursive lookups. In single-threaded mode, FUSE holds a global lock on your filesystem, and will wait for one callback to return before calling another. This can lead to deadlocks, if your script makes any attempt to access files or directories in the filesystem it is providing. (This includes calling stat() on the mount-point, statfs() calls from the 'df' command, and so on and so forth.) It is worth paying a little attention and being careful about this. Enabling multithreading will cause FUSE to make multiple simultaneous calls into the various callback functions of your perl script. If you enable threaded mode, you can enjoy all the parallel execution and interactive response benefits of threads, and you get to enjoy all the benefits of race conditions and locking bugs, too. Please also ensure any other perl modules you're using are also thread-safe. (If enabled, this option will cause a warning if your perl interpreter was not built with USE_ITHREADS, or if you have failed to use threads or threads::shared.) =item nullpath_ok => boolean This flag tells Filesys::Fuse3 to not pass paths for functions that operate on file or directory handles. This will yield empty path parameters for functions including read, write, flush, release, fsync, readdir, releasedir, fsyncdir, truncate, fgetattr and lock. If you use this, you must return file/directory handles from open, opendir and create. Default is 0 (off). Only effective on Fuse 2.8 and up; with earlier versions, this does nothing. =item utimens_as_array => boolean This flag causes timestamps passed via the utimens() call to be passed as arrays containing the time in seconds, and a second value containing the number of nanoseconds, instead of a floating point value. This allows for more precise times, as the normal floating point type used by Perl (double) loses accuracy starting at about tenths of a microsecond. =item nopath => boolean Flag indicating that the path need not be calculated for the following operations: read, write, flush, release, fsync, readdir, releasedir, fsyncdir, ftruncate, fgetattr, lock, ioctl and poll Closely related to nullpath_ok, but if this flag is set then the path will not be calculated even if the file wasn't unlinked. However the path can still be defined if it needs to be calculated for some other reason. Only effective on Fuse 2.9 and up. =item utime_omit_ok => boolean Flag indicating that the filesystem accepts special UTIME_NOW and UTIME_OMIT values in its C operation. If you wish to use these constants, make sure to include the ':utime' flag when including the Fuse module, or the ':all' flag. Only effective on Fuse 2.9 and up. =back =head3 Filesys::Fuse3::fuse_get_context use Filesys::Fuse3 'fuse_get_context'; my $caller_uid = fuse_get_context()->{uid}; my $caller_gid = fuse_get_context()->{gid}; my $caller_pid = fuse_get_context()->{pid}; Access context information about the current Filesys::Fuse3 operation. =head3 Filesys::Fuse3::fuse_version Indicates the B version in use; more accurately, indicates the version of the B API in use at build time. If called in scalar context, the version will be returned as a decimal value; i.e., for B API v2.6, will return "2.6". If called in array context, an array will be returned, containing the major, minor and micro version numbers of the B API it was built against. =head3 Filesys::Fuse3::fuse_buf_size Computes the total size of a buffer vector. Applicable for C and C operations. =head3 Filesys::Fuse3::fuse_buf_copy Copies data from one buffer vector to another. Primarily useful if a buffer vector contains multiple, fragmented chunks or if it contains an FD buffer instead of a memory buffer. Applicable for C. =head3 Filesys::Fuse3::notify_poll Only available if the Fuse module is built against libfuse 2.8 or later. Use fuse_version() to determine if this is the case. Calling this function with a pollhandle argument (as provided to the C operation implementation) will send a notification to the caller poll()ing for I/O operation availability. If more than one pollhandle is provided for the same filehandle, only use the latest; you *can* send notifications to them all, but it is unnecessary and decreases performance. ONLY supply poll handles fed to you through C to this function. Due to thread safety requirements, we can't currently package the pointer up in an object the way we'd like to to prevent this situation, but your filesystem server program may segfault, or worse, if you feed things to this function which it is not supposed to receive. If you do anyway, we take no responsibility for whatever Bad Things(tm) may happen. =head3 Filesys::Fuse3::pollhandle_destroy Only available if the Fuse module is built against libfuse 2.8 or later. Use fuse_version() to determine if this is the case. This function destroys a poll handle (fed to your program through C). When you are done with a poll handle, either because it has been replaced, or because a notification has been sent to it, pass it to this function to dispose of it safely. ONLY supply poll handles fed to you through C to this function. Due to thread safety requirements, we can't currently package the pointer up in an object the way we'd like to to prevent this situation, but your filesystem server program may segfault, or worse, if you feed things to this function which it is not supposed to receive. If you do anyway, we take no responsibility for whatever Bad Things(tm) may happen. =head2 FUNCTIONS YOUR FILESYSTEM MAY IMPLEMENT =head3 getattr Arguments: filename. Returns a list, very similar to the 'stat' function (see perlfunc). On error, simply return a single numeric scalar value (e.g. "return -ENOENT();"). FIXME: the "ino" field is currently ignored. I tried setting it to 0 in an example script, which consistently caused segfaults. Fields (the following was stolen from perlfunc(1) with apologies): ( $dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime, $blksize, $blocks ) = getattr($filename); Here are the meaning of the fields: 0 dev device number of filesystem 1 ino inode number 2 mode file mode (type and permissions) 3 nlink number of (hard) links to the file 4 uid numeric user ID of file's owner 5 gid numeric group ID of file's owner 6 rdev the device identifier (special files only) 7 size total size of file, in bytes 8 atime last access time in seconds since the epoch 9 mtime last modify time in seconds since the epoch 10 ctime inode change time (NOT creation time!) in seconds since the epoch 11 blksize preferred block size for file system I/O 12 blocks actual number of blocks allocated (The epoch was at 00:00 January 1, 1970 GMT.) If you wish to provide sub-second precision timestamps, they may be passed either as the fractional part of a floating-point value, or as a two-element array, passed as an array ref, with the first element containing the number of seconds since the epoch, and the second containing the number of nanoseconds. This provides complete time precision, as a floating point number starts losing precision at about a tenth of a microsecond. So if you really care about that sort of thing... =head3 readlink Arguments: link pathname. Returns a scalar: either a numeric constant, or a text string. This is called when dereferencing symbolic links, to learn the target. example rv: return "/proc/self/fd/stdin"; =head3 mknod Arguments: Filename, numeric modes, numeric device Returns an errno (0 upon success, as usual). This function is called for all non-directory, non-symlink nodes, not just devices. =head3 mkdir Arguments: New directory pathname, numeric modes. Returns an errno. Called to create a directory. =head3 unlink Arguments: Filename. Returns an errno. Called to remove a file, device, or symlink. =head3 rmdir Arguments: Pathname. Returns an errno. Called to remove a directory. =head3 symlink Arguments: Existing filename, symlink name. Returns an errno. Called to create a symbolic link. =head3 rename Arguments: old filename, new filename. Returns an errno. Called to rename a file, and/or move a file from one directory to another. =head3 link Arguments: Existing filename, hardlink name. Returns an errno. Called to create hard links. =head3 chmod Arguments: Pathname, numeric modes. Returns an errno. Called to change permissions on a file/directory/device/symlink. =head3 chown Arguments: Pathname, numeric uid, numeric gid. Returns an errno. Called to change ownership of a file/directory/device/symlink. =head3 truncate Arguments: Pathname, numeric offset. Returns an errno. Called to truncate a file, at the given offset. =head3 utime Arguments: Pathname, numeric actime, numeric modtime. Returns an errno. Called to change access/modification times for a file/directory/device/symlink. =head3 open Arguments: Pathname, numeric flags (which is an OR-ing of stuff like O_RDONLY and O_SYNC, constants you can import from POSIX), fileinfo hash reference. Returns an errno, a file handle (optional). The fileinfo hash reference contains information from the B C call which may be modified by the module. The only fileinfo fields presently supported are: direct_io (version 2.4 onwards) keep_cache (version 2.4 onwards) nonseekable (version 2.8 onwards) Your open() method needs only check if the operation is permitted for the given flags, and return 0 for success. Optionally a file handle may be returned, which will be passed to subsequent read, write, flush, fsync and release calls. =head3 read Arguments: Pathname, numeric requested size, numeric offset, file handle Returns a numeric errno, or a string scalar with up to $requestedsize bytes of data. Called in an attempt to fetch a portion of the file. =head3 write Arguments: Pathname, scalar buffer, numeric offset, file handle. You can use length($buffer) to find the buffersize. Returns length($buffer) if successful (number of bytes written). Called in an attempt to write (or overwrite) a portion of the file. Be prepared because $buffer could contain random binary data with NULs and all sorts of other wonderful stuff. =head3 statfs Arguments: none Returns any of the following: -ENOANO() or $namelen, $files, $files_free, $blocks, $blocks_avail, $blocksize or -ENOANO(), $namelen, $files, $files_free, $blocks, $blocks_avail, $blocksize =head3 flush Arguments: Pathname, file handle Returns an errno or 0 on success. Called to synchronise any cached data. This is called before the file is closed. It may be called multiple times before a file is closed. =head3 release Arguments: Pathname, numeric flags passed to open, file handle, flock_release flag (when built against FUSE 2.9 or later), lock owner ID (when built against FUSE 2.9 or later) Returns an errno or 0 on success. Called to indicate that there are no more references to the file. Called once for every file with the same pathname and flags as were passed to open. =head3 fsync Arguments: Pathname, numeric flags Returns an errno or 0 on success. Called to synchronise the file's contents. If flags is non-zero, only synchronise the user data. Otherwise synchronise the user and meta data. =head3 setxattr Arguments: Pathname, extended attribute's name, extended attribute's value, numeric flags (which is an OR-ing of XATTR_CREATE and XATTR_REPLACE Returns an errno or 0 on success. Called to set the value of the named extended attribute. If you wish to reject setting of a particular form of extended attribute name (e.g.: regexps matching user\..* or security\..*), then return - EOPNOTSUPP. If flags is set to XATTR_CREATE and the extended attribute already exists, this should fail with - EEXIST. If flags is set to XATTR_REPLACE and the extended attribute doesn't exist, this should fail with - ENOATTR. XATTR_CREATE and XATTR_REPLACE are provided by this module, but not exported by default. To import them: use Filesys::Fuse3 ':xattr'; or: use Filesys::Fuse3 ':all'; =head3 getxattr Arguments: Pathname, extended attribute's name Returns an errno, 0 if there was no value, or the extended attribute's value. Called to get the value of the named extended attribute. =head3 listxattr Arguments: Pathname Returns a list: 0 or more text strings (the extended attribute names), followed by a numeric errno (usually 0). =head3 removexattr Arguments: Pathname, extended attribute's name Returns an errno or 0 on success. Removes the named extended attribute (if present) from a file. =head3 opendir Arguments: Pathname of a directory Returns an errno, and a directory handle (optional) Called when opening a directory for reading. If special handling is required to open a directory, this operation can be implemented to handle that. =head3 readdir Arguments: Pathname of a directory, numeric offset, (optional) directory handle Returns a list of 0 or more entries, followed by a numeric errno (usually 0). The entries can be simple strings (filenames), or arrays containing an offset number, the filename, and optionally an array ref containing the stat values (as would be returned from getattr()). This is used to read entries from a directory. It can be used to return just entry names like getdir(), or can get a segment of the available entries, which requires using array refs and the 2- or 3-item form, with offset values starting from 1. If you wish to return the parameters to fill each entry's struct stat, but do not wish to do partial entry lists/entry counting, set the first element of each array to 0 always. Note that if this call is implemented, it overrides getdir() ALWAYS. =head3 releasedir Arguments: Pathname of a directory, (optional) directory handle Returns an errno or 0 on success Called when there are no more references to an opened directory. Called once for each pathname or handle passed to opendir(). Similar to release(), but for directories. Accepts a return value, but like release(), the response code will not propagate to any corresponding closedir() calls. =head3 fsyncdir Arguments: Pathname of a directory, numeric flags, (optional) directory handle Returns an errno or 0 on success. Called to synchronize any changes to a directory's contents. If flag is non-zero, only synchronize user data, otherwise synchronize user data and metadata. =head3 init Arguments: None. Returns (optionally) an SV to be passed as private_data via fuse_get_context(). =head3 destroy Arguments: (optional) private data SV returned from init(), if any. Returns nothing. =head3 access Arguments: Pathname, access mode flags Returns an errno or 0 on success. Determine if the user attempting to access the indicated file has access to perform the requested actions. The user ID can be determined by calling fuse_get_context(). See access(2) for more information. =head3 create Arguments: Pathname, create mask, open mode flags Returns errno or 0 on success, and (optional) file handle. Create a file with the path indicated, then open a handle for reading and/or writing with the supplied mode flags. Can also return a file handle like open() as part of the call. =head3 lock Arguments: Pathname, numeric command code, hashref containing lock parameters, (optional) file handle Returns errno or 0 on success Used to lock or unlock regions of a file. Locking is handled locally, but this allows (especially for networked file systems) for protocol-level locking semantics to also be employed, if any are available. See the Fuse documentation for more explanation of lock(). The needed symbols for the lock constants can be obtained by importing Fcntl. =head3 utimens Arguments: Pathname, last accessed time, last modified time Returns errno or 0 on success Like utime(), but allows time resolution down to the nanosecond. By default, times are passed as "numeric" (internally these are typically represented as "double"), so the sub-second portion is represented as fractions of a second. If you want times passed as arrays instead of floating point values, for higher precision, you should pass the C option to C. Note that if this call is implemented, it overrides utime() ALWAYS. =head3 bmap Arguments: Pathname, numeric blocksize, numeric block number Returns errno or 0 on success, and physical block number if successful Used to map a block number offset in a file to the physical block offset on the block device backing the file system. This is intended for filesystems that are stored on an actual block device, with the 'blkdev' option passed. =head3 ioctl Arguments: Pathname, ioctl command code, flags, data if ioctl op is a write, (optional) file handle Returns errno or 0 on success, and data if ioctl op is a read Used to handle ioctl() operations on files. See ioctl(2) for more information on the fine details of ioctl operation numbers. May need to h2ph system headers to get the necessary macros; keep in mind the macros are highly OS-dependent. Keep in mind that read and write are from the client perspective, so read from our end means data is going *out*, and write means data is coming *in*. It can be slightly confusing. =head3 poll Arguments: Pathname, poll handle ID (or undef if none), event mask, (optional) file handle Returns errno or 0 on success, and updated event mask on success Used to handle poll() operations on files. See poll(2) to learn more about event polling. Use IO::Poll to get the POLLIN, POLLOUT, and other symbols to describe the events which can happen on the filehandle. Save the poll handle ID to be passed to C and C functions, if it is not undef. Threading will likely be necessary for this operation to work. There is not an "out of band" data transfer channel provided as part of FUSE, so POLLPRI/POLLRDBAND/POLLWRBAND won't work. Poll handle is currently a read-only scalar; we are investigating a way to make this an object instead. =head3 write_buf Arguments: Pathname, offset, buffer vector, (optional) file handle. Write contents of buffer to an open file. Similar to the C method, but data is supplied in a generic buffer. Use fuse_buf_copy() to transfer data to the destination if necessary. =head3 read_buf Arguments: Pathname, size, offset, buffer vector, (optional) file handle. Store data from an open file in a buffer. Similar to the C method, but data is stored and returned in a generic buffer. No actual copying of data has to take place, the source file descriptor may simply be placed in the 'fd' member of the buffer access hash (and the 'flags' member OR'd with FUSE_BUF_IS_FD) for later retrieval. Also, if the FUSE_BUF_FD_SEEK constant is OR'd with 'flags', the 'pos' member should contain the offset (in bytes) to seek to in the file descriptor. If data is to be read, the read data should be placed in the 'mem' member of the buffer access hash, and the 'size' member should be updated if less data was read than requested. =head3 flock Arguments: pathname, (optional) file handle, unique lock owner ID, operation ID Perform BSD-style file locking operations. Operation ID will be one of LOCK_SH, LOCK_EX or LOCK_UN. Non-blocking lock requests will be indicated by having LOCK_NB OR'd into the value. For more information, see the flock(2) manpage. For the lock symbols, do: use Fcntl qw(flock); Locking is handled locally, but this allows (especially for networked file systems) for protocol-level locking semantics to also be employed, if any are available. =head3 fallocate Arguments: pathname, (optional) file handle, mode, offset, length Allocates space for an open file. This function ensures that required space is allocated for specified file. If this function returns success then any subsequent write request to specified range is guaranteed not to fail because of lack of space on the file system media. =head1 EXAMPLES There are a few example scripts in the examples/ subdirectory. These are: =over 4 =item example.pl A simple "Hello world" type of script =item loopback.pl A filesystem loopback-device. like fusexmp from the main FUSE dist, it simply recurses file operations into the real filesystem. Unlike fusexmp, it only re-shares files under the /tmp/test directory. =item rmount.pl An NFS-workalike which tunnels through SSH. It requires an account on some ssh server (obviously) with public-key authentication enabled (if you have to type in a password, you don't have this. See *man ssh_keygen* for more information). Copy rmount_remote.pl to your home directory on the remote machine, and create a subdir somewhere, and then run it like: ./rmount.pl host /remote/dir /local/dir =item rmount_remote.pl A ripoff of loopback.pl meant to be used as a backend for rmount.pl. =back =head1 BUGS =over 4 =item * fuse3 C, C, C callbacks are not implemented. =back =head1 AUTHOR Original Fuse author: Mark Glines, Emark@glines.orgE. Ported to fuse3 by Dominique Dumont, Edomi.dumont@free.fr =head1 SEE ALSO L, the L. =cut libfilesys-fuse3-perl-0.021/Fuse3.xs000077500000000000000000001471311516674026300172310ustar00rootroot00000000000000#define PERL_NO_GET_CONTEXT #include "EXTERN.h" #include "perl.h" #include "XSUB.h" #include #if defined(__linux__) || defined(__APPLE__) # include #else # define XATTR_CREATE 1 # define XATTR_REPLACE 2 #endif #if defined(__linux__) || defined(__sun__) # define STAT_SEC(st, st_xtim) ((st)->st_xtim.tv_sec) # define STAT_NSEC(st, st_xtim) ((st)->st_xtim.tv_nsec) #else # define STAT_SEC(st, st_xtim) ((st)->st_xtim##espec.tv_sec) # define STAT_NSEC(st, st_xtim) ((st)->st_xtim##espec.tv_nsec) #endif /* Implement a macro to handle multiple formats (integer, float, and array * containing seconds and nanoseconds). */ #define PULL_TIME(st, st_xtim, svp) \ { \ SV *sv = svp; \ if (SvROK(sv)) { \ AV *av = (AV *)SvRV(sv); \ if (SvTYPE((SV *)av) != SVt_PVAV) { \ Perl_croak_nocontext("Reference was not array ref"); \ } \ if (av_len(av) != 1) { \ Perl_croak_nocontext("Array of incorrect dimension"); \ } \ STAT_SEC(st, st_xtim) = SvIV(*(av_fetch(av, 0, FALSE))); \ STAT_NSEC(st, st_xtim) = SvIV(*(av_fetch(av, 1, FALSE))); \ } \ else if (SvNOK(sv) || SvIOK(sv) || SvPOK(sv)) { \ double tm = SvNV(sv); \ STAT_SEC(st, st_xtim) = (int)tm; \ STAT_NSEC(st, st_xtim) = (tm - (int)tm) * 1000000000; \ } \ else { \ Perl_croak_nocontext("Invalid data type passed"); \ } \ } /* Determine if threads support should be included */ #ifdef USE_ITHREADS # ifdef I_PTHREAD # define FUSE_USE_ITHREADS # else # warning "Sorry, I don't know how to handle ithreads on this architecture. Building non-threaded version" # endif #endif /* Global Data */ // ⚠️ This list must match the @names list defined in Fuse.pm enum callback_index { CB_IDX_GETATTR, CB_IDX_READLINK, CB_IDX_MKNOD, CB_IDX_MKDIR, CB_IDX_UNLINK, CB_IDX_RMDIR, CB_IDX_SYMLINK, CB_IDX_RENAME, CB_IDX_LINK, CB_IDX_CHMOD, CB_IDX_CHOWN, CB_IDX_TRUNCATE, CB_IDX_OPEN, CB_IDX_READ, CB_IDX_WRITE, CB_IDX_STATFS, CB_IDX_FLUSH, CB_IDX_RELEASE, CB_IDX_FSYNC, CB_IDX_SETXATTR, CB_IDX_GETXATTR, CB_IDX_LISTXATTR, CB_IDX_REMOVEXATTR, CB_IDX_OPENDIR, CB_IDX_READDIR, CB_IDX_RELEASEDIR, CB_IDX_FSYNCDIR, CB_IDX_INIT, CB_IDX_DESTROY, CB_IDX_ACCESS, CB_IDX_CREATE, CB_IDX_LOCK, CB_IDX_UTIMENS, CB_IDX_BMAP, CB_IDX_IOCTL, CB_IDX_POLL, CB_IDX_WRITE_BUF, CB_IDX_READ_BUF, CB_IDX_FLOCK, CB_IDX_FALLOCATE, N_CALLBACKS }; // ⚠️ This list must match the @otherargs list defined in Fuse.pm enum arg_index { ARG_IDX_DEBUG, ARG_IDX_THREADED, ARG_IDX_MOUNTPOINT, ARG_IDX_MOUNTOPTS, ARG_IDX_NULLPATH_OK, ARG_IDX_UTIMENS_AS_ARRAY, N_FLAGS }; typedef struct { SV *callback[N_CALLBACKS]; HV *handles; #ifdef USE_ITHREADS tTHX self; #endif int threaded; #ifdef USE_ITHREADS perl_mutex mutex; #endif int utimens_as_array; int nullpath_ok; } my_cxt_t; START_MY_CXT; #ifdef FUSE_USE_ITHREADS tTHX master_interp = NULL; #define CLONE_INTERP(parent) S_clone_interp(parent) tTHX S_clone_interp(tTHX parent) { dMY_CXT_INTERP(parent); if(MY_CXT.threaded) { MUTEX_LOCK(&MY_CXT.mutex); PERL_SET_CONTEXT(parent); dTHX; tTHX child = perl_clone(parent, CLONEf_CLONE_HOST | CLONEf_COPY_STACKS); MUTEX_UNLOCK(&MY_CXT.mutex); return child; } return NULL; } # define FUSE_CONTEXT_PRE dTHX; if(!aTHX) aTHX = CLONE_INTERP(master_interp); { dMY_CXT; dSP; # define FUSE_CONTEXT_POST } #else # define FUSE_CONTEXT_PRE dTHX; dMY_CXT; dSP; # define FUSE_CONTEXT_POST #endif #undef DEBUGf #if 0 #define DEBUGf(f, a...) fprintf(stderr, "%s:%d (%li): " f,__BASE_FILE__,__LINE__,sp-PL_stack_base ,##a ) #else #define DEBUGf(a...) #endif #define FH_KEY(fi) sv_2mortal(newSViv((fi)->fh)) #define FH_GETHANDLE(fi) S_fh_get_handle(aTHX_ aMY_CXT_ fi) #define FH_STOREHANDLE(fi,sv) S_fh_store_handle(aTHX_ aMY_CXT_ fi, sv) #define FH_RELEASEHANDLE(fi) S_fh_release_handle(aTHX_ aMY_CXT_ fi) SV *S_fh_get_handle(pTHX_ pMY_CXT_ struct fuse_file_info *fi) { SV *val; val = &PL_sv_undef; if(fi->fh != 0) { HE *he; if((he = hv_fetch_ent(MY_CXT.handles, FH_KEY(fi), 0, 0))) { val = HeVAL(he); SvGETMAGIC(val); } } return val; } void S_fh_release_handle(pTHX_ pMY_CXT_ struct fuse_file_info *fi) { if(fi->fh != 0) { (void)hv_delete_ent(MY_CXT.handles, FH_KEY(fi), G_DISCARD, 0); fi->fh = 0; } } void S_fh_store_handle(pTHX_ pMY_CXT_ struct fuse_file_info *fi, SV *sv) { if(SvOK(sv)) { #ifdef FUSE_USE_ITHREADS if(MY_CXT.threaded) { SvSHARE(sv); } #endif /* This seems to be screwing things up... */ // MAGIC *mg = (SvTYPE(sv) == SVt_PVMG) ? mg_find(sv, PERL_MAGIC_shared_scalar) : NULL; // fi->fh = mg ? PTR2IV(mg->mg_ptr) : PTR2IV(sv); fi->fh = PTR2IV(sv); if(hv_store_ent(MY_CXT.handles, FH_KEY(fi), SvREFCNT_inc(sv), 0) == NULL) { SvREFCNT_dec(sv); } SvSETMAGIC(sv); } } int _PLfuse_getattr(const char *file, struct stat *result, struct fuse_file_info *info) { int rv; FUSE_CONTEXT_PRE; DEBUGf("getattr begin: %s\n",file); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(sv_2mortal(newSVpv(file,strlen(file)))); PUTBACK; rv = call_sv(MY_CXT.callback[CB_IDX_GETATTR],G_ARRAY); SPAGAIN; if(rv != 13) { if(rv > 1) { fprintf(stderr,"inappropriate number of returned values from getattr\n"); rv = -ENOSYS; } else if(rv) rv = POPi; else rv = -ENOENT; } else { result->st_blocks = POPi; result->st_blksize = POPi; PULL_TIME(result, st_ctim, POPs); PULL_TIME(result, st_mtim, POPs); PULL_TIME(result, st_atim, POPs); result->st_size = POPn; // we pop double here to support files larger than 4Gb (long limit) result->st_rdev = POPi; result->st_gid = POPi; result->st_uid = POPi; result->st_nlink = POPi; result->st_mode = POPi; result->st_ino = POPi; result->st_dev = POPi; rv = 0; } FREETMPS; LEAVE; PUTBACK; DEBUGf("getattr end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_readlink(const char *file,char *buf,size_t buflen) { int rv; if(buflen < 1) return EINVAL; FUSE_CONTEXT_PRE; DEBUGf("readlink begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(sv_2mortal(newSVpv(file,0))); PUTBACK; rv = call_sv(MY_CXT.callback[CB_IDX_READLINK],G_SCALAR); SPAGAIN; if(!rv) rv = -ENOENT; else { SV *mysv = POPs; if(SvTYPE(mysv) == SVt_IV || SvTYPE(mysv) == SVt_NV) rv = SvIV(mysv); else { /* as a safer choice instead of strncpy()... */ snprintf(buf, buflen, "%s", SvPV_nolen(mysv)); rv = 0; } } FREETMPS; LEAVE; PUTBACK; DEBUGf("readlink end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_mknod (const char *file, mode_t mode, dev_t dev) { int rv; FUSE_CONTEXT_PRE; DEBUGf("mknod begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(sv_2mortal(newSVpv(file,0))); XPUSHs(sv_2mortal(newSViv(mode))); XPUSHs(sv_2mortal(newSViv(dev))); PUTBACK; rv = call_sv(MY_CXT.callback[CB_IDX_MKNOD],G_SCALAR); SPAGAIN; rv = (rv ? POPi : 0); FREETMPS; LEAVE; PUTBACK; DEBUGf("mknod end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_mkdir (const char *file, mode_t mode) { int rv; FUSE_CONTEXT_PRE; DEBUGf("mkdir begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(sv_2mortal(newSVpv(file,0))); XPUSHs(sv_2mortal(newSViv(mode))); PUTBACK; rv = call_sv(MY_CXT.callback[CB_IDX_MKDIR],G_SCALAR); SPAGAIN; rv = (rv ? POPi : 0); FREETMPS; LEAVE; PUTBACK; DEBUGf("mkdir end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_unlink (const char *file) { int rv; FUSE_CONTEXT_PRE; DEBUGf("unlink begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(sv_2mortal(newSVpv(file,0))); PUTBACK; rv = call_sv(MY_CXT.callback[CB_IDX_UNLINK],G_SCALAR); SPAGAIN; rv = (rv ? POPi : 0); FREETMPS; LEAVE; PUTBACK; DEBUGf("unlink end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_rmdir (const char *file) { int rv; FUSE_CONTEXT_PRE; DEBUGf("rmdir begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(sv_2mortal(newSVpv(file,0))); PUTBACK; rv = call_sv(MY_CXT.callback[CB_IDX_RMDIR],G_SCALAR); SPAGAIN; rv = (rv ? POPi : 0); FREETMPS; LEAVE; PUTBACK; DEBUGf("rmdir end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_symlink (const char *file, const char *new) { int rv; FUSE_CONTEXT_PRE; DEBUGf("symlink begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(sv_2mortal(newSVpv(file,0))); XPUSHs(sv_2mortal(newSVpv(new,0))); PUTBACK; rv = call_sv(MY_CXT.callback[CB_IDX_SYMLINK],G_SCALAR); SPAGAIN; rv = (rv ? POPi : 0); FREETMPS; LEAVE; PUTBACK; DEBUGf("symlink end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_rename (const char *file, const char *new, unsigned int flags) { int rv; FUSE_CONTEXT_PRE; DEBUGf("rename begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(sv_2mortal(newSVpv(file,0))); XPUSHs(sv_2mortal(newSVpv(new,0))); PUTBACK; rv = call_sv(MY_CXT.callback[CB_IDX_RENAME],G_SCALAR); SPAGAIN; rv = (rv ? POPi : 0); FREETMPS; LEAVE; PUTBACK; DEBUGf("rename end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_link (const char *file, const char *new) { int rv; FUSE_CONTEXT_PRE; DEBUGf("link begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(sv_2mortal(newSVpv(file,0))); XPUSHs(sv_2mortal(newSVpv(new,0))); PUTBACK; rv = call_sv(MY_CXT.callback[CB_IDX_LINK],G_SCALAR); SPAGAIN; rv = (rv ? POPi : 0); FREETMPS; LEAVE; PUTBACK; DEBUGf("link end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_chmod (const char *file, mode_t mode, struct fuse_file_info *fi) { int rv; FUSE_CONTEXT_PRE; DEBUGf("chmod begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(sv_2mortal(newSVpv(file,0))); XPUSHs(sv_2mortal(newSViv(mode))); PUTBACK; rv = call_sv(MY_CXT.callback[CB_IDX_CHMOD],G_SCALAR); SPAGAIN; rv = (rv ? POPi : 0); FREETMPS; LEAVE; PUTBACK; DEBUGf("chmod end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_chown (const char *file, uid_t uid, gid_t gid, struct fuse_file_info *fi) { int rv; FUSE_CONTEXT_PRE; DEBUGf("chown begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(sv_2mortal(newSVpv(file,0))); XPUSHs(sv_2mortal(newSViv(uid))); XPUSHs(sv_2mortal(newSViv(gid))); PUTBACK; rv = call_sv(MY_CXT.callback[CB_IDX_CHOWN],G_SCALAR); SPAGAIN; rv = (rv ? POPi : 0); FREETMPS; LEAVE; PUTBACK; DEBUGf("chown end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_truncate (const char *file, off_t off, struct fuse_file_info *fi) { int rv; #ifndef PERL_HAS_64BITINT char *temp; #endif FUSE_CONTEXT_PRE; DEBUGf("truncate begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(sv_2mortal(newSVpv(file,0))); #ifdef PERL_HAS_64BITINT XPUSHs(sv_2mortal(newSViv(off))); #else if (asprintf(&temp, "%llu", off) == -1) croak("Memory allocation failure!"); XPUSHs(sv_2mortal(newSVpv(temp, 0))); free(temp); #endif PUTBACK; rv = call_sv(MY_CXT.callback[CB_IDX_TRUNCATE],G_SCALAR); SPAGAIN; rv = (rv ? POPi : 0); FREETMPS; LEAVE; PUTBACK; DEBUGf("truncate end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_open (const char *file, struct fuse_file_info *fi) { int rv; int flags = fi->flags; HV *fihash; FUSE_CONTEXT_PRE; DEBUGf("open begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(sv_2mortal(newSVpv(file,0))); XPUSHs(sv_2mortal(newSViv(flags))); /* Create a hashref containing the details from fi * which we can look at or modify. */ fi->fh = 0; /* Ensure it starts with 0 - important if they don't set it */ fihash = newHV(); (void) hv_store(fihash, "direct_io", 9, newSViv(fi->direct_io), 0); (void) hv_store(fihash, "keep_cache", 10, newSViv(fi->keep_cache), 0); (void) hv_store(fihash, "nonseekable", 11, newSViv(fi->nonseekable), 0); XPUSHs(sv_2mortal(newRV_noinc((SV*) fihash))); /* All hashref things done */ PUTBACK; /* Open called with filename, flags */ rv = call_sv(MY_CXT.callback[CB_IDX_OPEN],G_ARRAY); SPAGAIN; if(rv) { if(rv > 1) { FH_STOREHANDLE(fi,POPs); } rv = POPi; } else rv = 0; if (rv == 0) { /* Success, so copy the file handle which they returned */ SV **svp; if ((svp = hv_fetch(fihash, "direct_io", 9, 0)) != NULL) fi->direct_io = SvIV(*svp); if ((svp = hv_fetch(fihash, "keep_cache", 10, 0)) != NULL) fi->keep_cache = SvIV(*svp); if ((svp = hv_fetch(fihash, "nonseekable", 11, 0)) != NULL) fi->nonseekable = SvIV(*svp); } FREETMPS; LEAVE; PUTBACK; DEBUGf("open end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_read (const char *file, char *buf, size_t buflen, off_t off, struct fuse_file_info *fi) { int rv; #ifndef PERL_HAS_64BITINT char *temp; #endif FUSE_CONTEXT_PRE; DEBUGf("read begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(file ? sv_2mortal(newSVpv(file,0)) : &PL_sv_undef); XPUSHs(sv_2mortal(newSViv(buflen))); #ifdef PERL_HAS_64BITINT XPUSHs(sv_2mortal(newSViv(off))); #else if (asprintf(&temp, "%llu", off) == -1) croak("Memory allocation failure!"); XPUSHs(sv_2mortal(newSVpv(temp, 0))); free(temp); #endif XPUSHs(FH_GETHANDLE(fi)); PUTBACK; rv = call_sv(MY_CXT.callback[CB_IDX_READ],G_SCALAR); SPAGAIN; if(!rv) rv = -ENOENT; else { SV *mysv = POPs; if(SvTYPE(mysv) == SVt_NV || SvTYPE(mysv) == SVt_IV) rv = SvIV(mysv); else { if(SvPOK(mysv)) { rv = SvCUR(mysv); } else { rv = 0; } if(rv > buflen) croak("read() handler returned more than buflen! (%i > %zu)",rv,buflen); if(rv) memcpy(buf,SvPV_nolen(mysv),rv); } } FREETMPS; LEAVE; PUTBACK; DEBUGf("read end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_write (const char *file, const char *buf, size_t buflen, off_t off, struct fuse_file_info *fi) { int rv; SV *sv; #ifndef PERL_HAS_64BITINT char *temp; #endif FUSE_CONTEXT_PRE; DEBUGf("write begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(file ? sv_2mortal(newSVpv(file,0)) : &PL_sv_undef); sv = newSV_type(SVt_PV); SvPV_set(sv, (char *)buf); SvLEN_set(sv, 0); SvCUR_set(sv, buflen); SvPOK_on(sv); SvREADONLY_on(sv); XPUSHs(sv_2mortal(sv)); #ifdef PERL_HAS_64BITINT XPUSHs(sv_2mortal(newSViv(off))); #else if (asprintf(&temp, "%llu", off) == -1) croak("Memory allocation failure!"); XPUSHs(sv_2mortal(newSVpv(temp, 0))); free(temp); #endif XPUSHs(FH_GETHANDLE(fi)); PUTBACK; rv = call_sv(MY_CXT.callback[CB_IDX_WRITE],G_SCALAR); SPAGAIN; rv = (rv ? POPi : 0); FREETMPS; LEAVE; PUTBACK; DEBUGf("write end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_statfs (const char *file, struct statvfs *st) { int rv; FUSE_CONTEXT_PRE; DEBUGf("statfs begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); PUTBACK; rv = call_sv(MY_CXT.callback[CB_IDX_STATFS],G_ARRAY); SPAGAIN; DEBUGf("statfs got %i params\n",rv); if(rv == 6 || rv == 7) { st->f_bsize = POPi; st->f_bfree = POPi; st->f_blocks = POPi; st->f_ffree = POPi; st->f_files = POPi; st->f_namemax = POPi; /* zero and fill-in other */ st->f_fsid = 0; st->f_flag = 0; st->f_frsize = st->f_bsize; st->f_bavail = st->f_bfree; st->f_favail = st->f_ffree; if(rv == 7) rv = POPi; else rv = 0; } else if(rv > 1) croak("inappropriate number of returned values from statfs"); else if(rv) rv = POPi; else rv = -ENOSYS; FREETMPS; LEAVE; PUTBACK; DEBUGf("statfs end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_flush (const char *file, struct fuse_file_info *fi) { int rv; FUSE_CONTEXT_PRE; DEBUGf("flush begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(file ? sv_2mortal(newSVpv(file,0)) : &PL_sv_undef); XPUSHs(FH_GETHANDLE(fi)); PUTBACK; rv = call_sv(MY_CXT.callback[CB_IDX_FLUSH],G_SCALAR); SPAGAIN; rv = (rv ? POPi : 0); FREETMPS; LEAVE; PUTBACK; DEBUGf("flush end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_release (const char *file, struct fuse_file_info *fi) { int rv; int flags = fi->flags; #if FUSE_VERSION >= 29 && !defined(PERL_HAS_64BITINT) char *temp; #endif FUSE_CONTEXT_PRE; DEBUGf("release begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(file ? sv_2mortal(newSVpv(file,0)) : &PL_sv_undef); XPUSHs(sv_2mortal(newSViv(flags))); XPUSHs(FH_GETHANDLE(fi)); XPUSHs(fi->flock_release ? sv_2mortal(newSViv(1)) : &PL_sv_undef); # ifdef PERL_HAS_64BITINT XPUSHs(sv_2mortal(newSViv(fi->lock_owner))); # else if (asprintf(&temp, "%llu", fi->lock_owner) == -1) croak("Memory allocation failure!"); XPUSHs(sv_2mortal(newSVpv(temp, 0))); # endif PUTBACK; rv = call_sv(MY_CXT.callback[CB_IDX_RELEASE],G_SCALAR); SPAGAIN; rv = (rv ? POPi : 0); FH_RELEASEHANDLE(fi); FREETMPS; LEAVE; PUTBACK; DEBUGf("release end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_fsync (const char *file, int datasync, struct fuse_file_info *fi) { int rv; int flags = fi->flags; FUSE_CONTEXT_PRE; DEBUGf("fsync begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(file ? sv_2mortal(newSVpv(file,0)) : &PL_sv_undef); XPUSHs(sv_2mortal(newSViv(flags))); XPUSHs(FH_GETHANDLE(fi)); PUTBACK; rv = call_sv(MY_CXT.callback[CB_IDX_FSYNC],G_SCALAR); SPAGAIN; rv = (rv ? POPi : 0); FREETMPS; LEAVE; PUTBACK; DEBUGf("fsync end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } #ifdef __APPLE__ int _PLfuse_setxattr (const char *file, const char *name, const char *buf, size_t buflen, int flags, uint32_t position) { #else int _PLfuse_setxattr (const char *file, const char *name, const char *buf, size_t buflen, int flags) { #endif int rv; FUSE_CONTEXT_PRE; DEBUGf("setxattr begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(sv_2mortal(newSVpv(file,0))); XPUSHs(sv_2mortal(newSVpv(name,0))); XPUSHs(sv_2mortal(newSVpvn(buf,buflen))); XPUSHs(sv_2mortal(newSViv(flags))); PUTBACK; rv = call_sv(MY_CXT.callback[CB_IDX_SETXATTR],G_SCALAR); SPAGAIN; rv = (rv ? POPi : 0); FREETMPS; LEAVE; PUTBACK; DEBUGf("setxattr end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } #ifdef __APPLE__ int _PLfuse_getxattr (const char *file, const char *name, char *buf, size_t buflen, uint32_t position) { #else int _PLfuse_getxattr (const char *file, const char *name, char *buf, size_t buflen) { #endif int rv; FUSE_CONTEXT_PRE; DEBUGf("getxattr begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(sv_2mortal(newSVpv(file,0))); XPUSHs(sv_2mortal(newSVpv(name,0))); PUTBACK; rv = call_sv(MY_CXT.callback[CB_IDX_GETXATTR],G_SCALAR); SPAGAIN; if(!rv) rv = -ENOENT; else { SV *mysv = POPs; rv = 0; if(SvTYPE(mysv) == SVt_NV || SvTYPE(mysv) == SVt_IV) rv = SvIV(mysv); else { if(SvPOK(mysv)) { rv = SvCUR(mysv); } else { rv = 0; } if ((rv > 0) && (buflen > 0)) { if(rv > buflen) rv = -ERANGE; else memcpy(buf,SvPV_nolen(mysv),rv); } } } FREETMPS; LEAVE; PUTBACK; DEBUGf("getxattr end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_listxattr (const char *file, char *list, size_t size) { int prv, rv; FUSE_CONTEXT_PRE; DEBUGf("listxattr begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(sv_2mortal(newSVpv(file,0))); PUTBACK; prv = call_sv(MY_CXT.callback[CB_IDX_LISTXATTR],G_ARRAY); SPAGAIN; if(!prv) rv = -ENOENT; else { char *p = list; int spc = size; int total_len = 0; rv = POPi; prv--; /* Always nul terminate */ if (list && (size > 0)) list[0] = '\0'; while (prv > 0) { SV *mysv = POPs; prv--; if (SvPOK(mysv)) { /* Copy nul too */ int s = SvCUR(mysv) + 1; total_len += s; if (p && (size > 0) && (spc >= s)) { memcpy(p,SvPV_nolen(mysv),s); p += s; spc -= s; } } } /* * If the Perl returned an error, return that. * Otherwise check that the buffer was big enough. */ if (rv == 0) { rv = total_len; if ((size > 0) && (size < total_len)) rv = -ERANGE; } } FREETMPS; LEAVE; PUTBACK; DEBUGf("listxattr end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_removexattr (const char *file, const char *name) { int rv; FUSE_CONTEXT_PRE; DEBUGf("removexattr begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(sv_2mortal(newSVpv(file,0))); XPUSHs(sv_2mortal(newSVpv(name,0))); PUTBACK; rv = call_sv(MY_CXT.callback[CB_IDX_REMOVEXATTR],G_SCALAR); SPAGAIN; rv = (rv ? POPi : 0); FREETMPS; LEAVE; PUTBACK; DEBUGf("removexattr end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_opendir(const char *file, struct fuse_file_info *fi) { int rv; FUSE_CONTEXT_PRE; DEBUGf("opendir begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(sv_2mortal(newSVpv(file,0))); fi->fh = 0; /* Ensure it starts with 0 - important if they don't set it */ PUTBACK; rv = call_sv(MY_CXT.callback[CB_IDX_OPENDIR], G_ARRAY); SPAGAIN; if (rv) { if (rv > 1) { FH_STOREHANDLE(fi, POPs); } rv = POPi; } else rv = 0; FREETMPS; LEAVE; PUTBACK; DEBUGf("opendir end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_readdir(const char *file, void *dirh, fuse_fill_dir_t dirfil, off_t off, struct fuse_file_info *fi, enum fuse_readdir_flags flags) { int prv = 0, rv; SV *sv, **svp, **swp; AV *av, *av2; struct stat st; bool st_filled = 0; #ifndef PERL_HAS_64BITINT char *temp; #endif FUSE_CONTEXT_PRE; DEBUGf("readdir begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(file ? sv_2mortal(newSVpv(file,0)) : &PL_sv_undef); #ifdef PERL_HAS_64BITINT XPUSHs(sv_2mortal(newSViv(off))); #else if (asprintf(&temp, "%llu", off) == -1) croak("Memory allocation failure!"); XPUSHs(sv_2mortal(newSVpv(temp, 0))); free(temp); #endif XPUSHs(FH_GETHANDLE(fi)); PUTBACK; prv = call_sv(MY_CXT.callback[CB_IDX_READDIR],G_ARRAY); SPAGAIN; if (prv) { /* Should yield the bottom of the current stack... */ swp = SP - prv + 1; rv = POPi; memset(&st, 0, sizeof(struct stat)); /* Sort of a hack to walk the stack in order, instead of reverse * order - trying to explain to potential users why they need to * reverse the order of this array would be confusing, at best. */ while (swp <= SP) { sv = *(swp++); if (!SvROK(sv) && SvPOK(sv)) /* Just a bare SV (probably a string; hopefully a string) */ dirfil(dirh, SvPVx_nolen(sv), NULL, 0,0); else if (SvROK(sv) && SvTYPE(av = (AV *)SvRV(sv)) == SVt_PVAV) { if (av_len(av) >= 2) { /* The third element of the array should be the args that * would otherwise go to getattr(); a lot of filesystems * will, or at least can, return that info as part of the * enumeration process... */ svp = av_fetch(av, 2, FALSE); if (SvROK(*svp) && SvTYPE(av2 = (AV *)SvRV(*svp)) == SVt_PVAV && av_len(av2) == 12) { st.st_dev = SvIV(*(av_fetch(av2, 0, FALSE))); st.st_ino = SvIV(*(av_fetch(av2, 1, FALSE))); st.st_mode = SvIV(*(av_fetch(av2, 2, FALSE))); st.st_nlink = SvIV(*(av_fetch(av2, 3, FALSE))); st.st_uid = SvIV(*(av_fetch(av2, 4, FALSE))); st.st_gid = SvIV(*(av_fetch(av2, 5, FALSE))); st.st_rdev = SvIV(*(av_fetch(av2, 6, FALSE))); st.st_size = SvNV(*(av_fetch(av2, 7, FALSE))); PULL_TIME(&st, st_atim, *(av_fetch(av2, 8, FALSE))); PULL_TIME(&st, st_mtim, *(av_fetch(av2, 9, FALSE))); PULL_TIME(&st, st_ctim, *(av_fetch(av2, 10, FALSE))); st.st_blksize = SvIV(*(av_fetch(av2, 11, FALSE))); st.st_blocks = SvIV(*(av_fetch(av2, 12, FALSE))); st_filled = 1; } else fprintf(stderr,"Extra SV didn't appear to be correct, ignoring\n"); /* For now if the element isn't what we want, just * quietly ignore it... */ } if (av_len(av) >= 1) { char *entryname = SvPVx_nolen(*(av_fetch(av, 1, FALSE))); off_t elemnum = SvNV(*(av_fetch(av, 0, FALSE))); enum fuse_fill_dir_flags fill_flags = (st_filled && (flags & FUSE_READDIR_PLUS)) ? FUSE_FILL_DIR_PLUS : 0; dirfil(dirh, entryname, st_filled ? &st : NULL, elemnum, fill_flags); } if (st_filled) { memset(&st, 0, sizeof(struct stat)); st_filled = 0; } } else fprintf(stderr, "ERROR: Unknown entry passed via readdir\n"); } SP -= prv - 1; } else { fprintf(stderr,"readdir() handler returned nothing!\n"); rv = -ENOSYS; } FREETMPS; LEAVE; PUTBACK; DEBUGf("readdir end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_releasedir(const char *file, struct fuse_file_info *fi) { int rv; FUSE_CONTEXT_PRE; DEBUGf("releasedir begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(file ? sv_2mortal(newSVpv(file,0)) : &PL_sv_undef); XPUSHs(FH_GETHANDLE(fi)); PUTBACK; rv = call_sv(MY_CXT.callback[CB_IDX_RELEASEDIR], G_SCALAR); SPAGAIN; rv = (rv ? POPi : 0); FH_RELEASEHANDLE(fi); FREETMPS; LEAVE; PUTBACK; DEBUGf("releasedir end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_fsyncdir(const char *file, int datasync, struct fuse_file_info *fi) { int rv; FUSE_CONTEXT_PRE; DEBUGf("fsyncdir begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(file ? sv_2mortal(newSVpv(file,0)) : &PL_sv_undef); XPUSHs(sv_2mortal(newSViv(datasync))); XPUSHs(FH_GETHANDLE(fi)); PUTBACK; rv = call_sv(MY_CXT.callback[CB_IDX_FSYNCDIR], G_SCALAR); SPAGAIN; rv = (rv ? POPi : 0); FREETMPS; LEAVE; PUTBACK; DEBUGf("fsyncdir end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } void *_PLfuse_init(struct fuse_conn_info *fc, struct fuse_config *cfg) { void *rv = NULL; int prv; FUSE_CONTEXT_PRE; cfg->nullpath_ok = MY_CXT.nullpath_ok; DEBUGf("init begin\n"); if (MY_CXT.callback[CB_IDX_INIT]) { ENTER; SAVETMPS; PUSHMARK(SP); PUTBACK; prv = call_sv(MY_CXT.callback[CB_IDX_INIT], G_SCALAR); SPAGAIN; if (prv) { rv = POPs; if (rv == &PL_sv_undef) rv = NULL; else rv = SvREFCNT_inc((SV *)rv); } FREETMPS; LEAVE; PUTBACK; } DEBUGf("init end: %p\n", rv); FUSE_CONTEXT_POST; return rv; } void _PLfuse_destroy(void *private_data) { FUSE_CONTEXT_PRE; DEBUGf("destroy begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(private_data ? (SV *)private_data : &PL_sv_undef); PUTBACK; call_sv(MY_CXT.callback[CB_IDX_DESTROY], G_VOID); SPAGAIN; if (private_data) SvREFCNT_dec((SV *)private_data); FREETMPS; LEAVE; PUTBACK; DEBUGf("destroy end\n"); FUSE_CONTEXT_POST; } int _PLfuse_access(const char *file, int mask) { int rv; FUSE_CONTEXT_PRE; DEBUGf("access begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(sv_2mortal(newSVpv(file,0))); XPUSHs(sv_2mortal(newSViv(mask))); PUTBACK; rv = call_sv(MY_CXT.callback[CB_IDX_ACCESS], G_SCALAR); SPAGAIN; rv = (rv ? POPi : 0); FREETMPS; LEAVE; PUTBACK; DEBUGf("access end: %d\n", rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_create(const char *file, mode_t mode, struct fuse_file_info *fi) { int rv; HV *fihash; FUSE_CONTEXT_PRE; DEBUGf("create begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(sv_2mortal(newSVpv(file,0))); XPUSHs(sv_2mortal(newSViv(mode))); XPUSHs(sv_2mortal(newSViv(fi->flags))); fi->fh = 0; /* Ensure it starts with 0 - important if they don't set it */ /* Create a hashref containing the details from fi * which we can look at or modify. */ fihash = newHV(); (void) hv_store(fihash, "direct_io", 9, newSViv(fi->direct_io), 0); (void) hv_store(fihash, "keep_cache", 10, newSViv(fi->keep_cache), 0); (void) hv_store(fihash, "nonseekable", 11, newSViv(fi->nonseekable), 0); XPUSHs(sv_2mortal(newRV_noinc((SV*) fihash))); /* All hashref things done */ PUTBACK; rv = call_sv(MY_CXT.callback[CB_IDX_CREATE], G_ARRAY); SPAGAIN; if (rv) { if (rv > 1) { FH_STOREHANDLE(fi,POPs); } rv = POPi; } else { fprintf(stderr, "create() handler returned nothing!\n"); rv = -ENOSYS; } if (rv == 0) { /* Success, so copy the file handle which they returned */ SV **svp; if ((svp = hv_fetch(fihash, "direct_io", 9, 0)) != NULL) fi->direct_io = SvIV(*svp); if ((svp = hv_fetch(fihash, "keep_cache", 10, 0)) != NULL) fi->keep_cache = SvIV(*svp); if ((svp = hv_fetch(fihash, "nonseekable", 11, 0)) != NULL) fi->nonseekable = SvIV(*svp); } FREETMPS; LEAVE; PUTBACK; DEBUGf("create end: %d\n",rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_lock(const char *file, struct fuse_file_info *fi, int cmd, struct flock *lockinfo) { int rv; HV *lihash; SV *sv; #ifndef PERL_HAS_64BITINT char *temp; #endif FUSE_CONTEXT_PRE; DEBUGf("lock begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(file ? sv_2mortal(newSVpv(file,0)) : &PL_sv_undef); XPUSHs(sv_2mortal(newSViv(cmd))); lihash = newHV(); if (lockinfo) { (void) hv_store(lihash, "l_type", 6, newSViv(lockinfo->l_type), 0); (void) hv_store(lihash, "l_whence", 8, newSViv(lockinfo->l_whence), 0); #ifdef PERL_HAS_64BITINT sv = newSViv(lockinfo->l_start); #else if (asprintf(&temp, "%llu", lockinfo->l_start) == -1) croak("Memory allocation failure!"); sv = newSVpv(temp, 0); free(temp); #endif (void) hv_store(lihash, "l_start", 7, sv, 0); #ifdef PERL_HAS_64BITINT sv = newSViv(lockinfo->l_len); #else if (asprintf(&temp, "%llu", lockinfo->l_len) == -1) croak("Memory allocation failure!"); sv = newSVpv(temp, 0); free(temp); #endif (void) hv_store(lihash, "l_len", 5, sv, 0); (void) hv_store(lihash, "l_pid", 5, newSViv(lockinfo->l_pid), 0); } XPUSHs(sv_2mortal(newRV_noinc((SV*) lihash))); XPUSHs(FH_GETHANDLE(fi)); PUTBACK; rv = call_sv(MY_CXT.callback[CB_IDX_LOCK],G_SCALAR); SPAGAIN; rv = (rv ? POPi : 0); if (lockinfo && !rv) { /* Need to copy back any altered values from the hash into * the struct... */ SV **svp; if ((svp = hv_fetch(lihash, "l_type", 6, 0)) != NULL) lockinfo->l_type = SvIV(*svp); if ((svp = hv_fetch(lihash, "l_whence", 8, 0)) != NULL) lockinfo->l_whence = SvIV(*svp); if ((svp = hv_fetch(lihash, "l_start", 7, 0)) != NULL) lockinfo->l_start = SvNV(*svp); if ((svp = hv_fetch(lihash, "l_len", 5, 0)) != NULL) lockinfo->l_len = SvNV(*svp); if ((svp = hv_fetch(lihash, "l_pid", 5, 0)) != NULL) lockinfo->l_pid = SvIV(*svp); } FREETMPS; LEAVE; PUTBACK; DEBUGf("lock end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_utimens(const char *file, const struct timespec tv[2], struct fuse_file_info *fi) { int rv; FUSE_CONTEXT_PRE; DEBUGf("utimens begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(sv_2mortal(newSVpv(file,0))); if (MY_CXT.utimens_as_array) { /* Pushing timespecs as 2-element arrays (if tv is present). */ AV *av; if (tv) { av = newAV(); av_push(av, newSViv(tv[0].tv_sec)); av_push(av, newSViv(tv[0].tv_nsec)); XPUSHs(sv_2mortal(newRV_noinc((SV *)av))); av = newAV(); av_push(av, newSViv(tv[1].tv_sec)); av_push(av, newSViv(tv[1].tv_nsec)); XPUSHs(sv_2mortal(newRV_noinc((SV *)av))); } else { XPUSHs(&PL_sv_undef); XPUSHs(&PL_sv_undef); } } else { /* Pushing timespecs as floating point (double) values. */ XPUSHs(tv ? sv_2mortal(newSVnv(tv[0].tv_sec + (tv[0].tv_nsec / 1000000000.0))) : &PL_sv_undef); XPUSHs(tv ? sv_2mortal(newSVnv(tv[1].tv_sec + (tv[1].tv_nsec / 1000000000.0))) : &PL_sv_undef); } PUTBACK; rv = call_sv(MY_CXT.callback[CB_IDX_UTIMENS],G_SCALAR); SPAGAIN; rv = (rv ? POPi : 0); FREETMPS; LEAVE; PUTBACK; DEBUGf("utimens end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_bmap(const char *file, size_t blocksize, uint64_t *idx) { int rv; #ifndef PERL_HAS_64BITINT char *temp; #endif FUSE_CONTEXT_PRE; DEBUGf("bmap begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(sv_2mortal(newSVpv(file,0))); XPUSHs(sv_2mortal(newSViv(blocksize))); #ifdef PERL_HAS_64BITINT XPUSHs(sv_2mortal(newSViv(*idx))); #else if (asprintf(&temp, "%llu", *idx) == -1) croak("Memory allocation failure!"); XPUSHs(sv_2mortal(newSVpv(temp, 0))); free(temp); #endif PUTBACK; rv = call_sv(MY_CXT.callback[CB_IDX_BMAP],G_ARRAY); SPAGAIN; if (rv > 0 && rv < 3) { if (rv == 2) *idx = POPn; rv = POPi; } else { fprintf(stderr, "bmap(): wrong number of values returned?\n"); rv = -ENOSYS; } FREETMPS; LEAVE; PUTBACK; DEBUGf("bmap end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } # ifndef __linux__ # define _IOC_SIZE(n) IOCPARM_LEN(n) # endif int _PLfuse_ioctl(const char *file, int cmd, void *arg, struct fuse_file_info *fi, unsigned int flags, void *data) { int rv; SV *sv = NULL; FUSE_CONTEXT_PRE; DEBUGf("ioctl begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(sv_2mortal(newSVpv(file,0))); /* I don't know why cmd is a signed int in the first place; * casting as unsigned so stupid tricks don't have to be done on * the perl side */ XPUSHs(sv_2mortal(newSVuv((unsigned int)cmd))); XPUSHs(sv_2mortal(newSViv(flags))); if (cmd & IOC_IN) XPUSHs(sv_2mortal(newSVpvn(data, _IOC_SIZE(cmd)))); else XPUSHs(&PL_sv_undef); XPUSHs(FH_GETHANDLE(fi)); PUTBACK; rv = call_sv(MY_CXT.callback[CB_IDX_IOCTL],G_ARRAY); SPAGAIN; if ((cmd & IOC_OUT) && (rv == 2)) { sv = POPs; rv--; } if (rv > 0) rv = POPi; if ((cmd & IOC_OUT) && !rv) { if (sv) { size_t len; char *rdata = SvPV(sv, len); if (len > _IOC_SIZE(cmd)) { fprintf(stderr, "ioctl(): returned data was too large for data area\n"); rv = -EFBIG; } else { memset(data, 0, _IOC_SIZE(cmd)); memcpy(data, rdata, len); } } else { fprintf(stderr, "ioctl(): ioctl was a read op, but no data was returned from call?\n"); rv = -EFAULT; } } FREETMPS; LEAVE; PUTBACK; DEBUGf("ioctl end: %i\n",rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_poll(const char *file, struct fuse_file_info *fi, struct fuse_pollhandle *ph, unsigned *reventsp) { int rv; SV *sv = NULL; FUSE_CONTEXT_PRE; DEBUGf("poll begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(sv_2mortal(newSVpv(file,0))); if (ph) { /* Still gotta figure out how to do this right... */ sv = newSViv(PTR2IV(ph)); SvREADONLY_on(sv); SvSHARE(sv); XPUSHs(sv); } else XPUSHs(&PL_sv_undef); XPUSHs(sv_2mortal(newSViv(*reventsp))); XPUSHs(FH_GETHANDLE(fi)); PUTBACK; rv = call_sv(MY_CXT.callback[CB_IDX_POLL],G_ARRAY); SPAGAIN; if (rv > 1) { *reventsp = POPi; rv--; } rv = (rv ? POPi : 0); FREETMPS; LEAVE; PUTBACK; DEBUGf("poll end: %i\n", rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_write_buf (const char *file, struct fuse_bufvec *buf, off_t off, struct fuse_file_info *fi) { int rv, i; HV *bvhash; AV *bvlist; SV *sv; #ifndef PERL_HAS_64BITINT char *temp; #endif FUSE_CONTEXT_PRE; DEBUGf("write_buf begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(file ? sv_2mortal(newSVpv(file,0)) : &PL_sv_undef); #ifdef PERL_HAS_64BITINT XPUSHs(sv_2mortal(newSViv(off))); #else if (asprintf(&temp, "%llu", off) == -1) croak("Memory allocation failure!"); XPUSHs(sv_2mortal(newSVpv(temp, 0))); free(temp); #endif bvlist = newAV(); for (i = 0; i < buf->count; i++) { bvhash = newHV(); sv = newSViv(buf->buf[i].size); (void) hv_store(bvhash, "size", 4, sv, 0); sv = newSViv(buf->buf[i].flags); (void) hv_store(bvhash, "flags", 5, sv, 0); sv = &PL_sv_undef; if (!(buf->buf[i].flags & FUSE_BUF_IS_FD)) { sv = newSV_type(SVt_PV); SvPV_set(sv, (char *)buf->buf[i].mem); SvLEN_set(sv, 0); SvCUR_set(sv, buf->buf[i].size); SvPOK_on(sv); SvREADONLY_on(sv); } (void) hv_store(bvhash, "mem", 3, sv, 0); sv = newSViv(buf->buf[i].fd); (void) hv_store(bvhash, "fd", 2, sv, 0); sv = newSViv(buf->buf[i].pos); (void) hv_store(bvhash, "pos", 3, sv, 0); av_push(bvlist, newRV((SV *)bvhash)); SvREFCNT_dec((SV*)bvhash); } XPUSHs(sv_2mortal(newRV_noinc((SV *)bvlist))); XPUSHs(FH_GETHANDLE(fi)); PUTBACK; rv = call_sv(MY_CXT.callback[CB_IDX_WRITE_BUF], G_SCALAR); SPAGAIN; rv = rv ? POPi : -ENOENT; FREETMPS; LEAVE; PUTBACK; DEBUGf("write_buf end: %i\n", rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_read_buf (const char *file, struct fuse_bufvec **bufp, size_t size, off_t off, struct fuse_file_info *fi) { int rv; HV *bvhash; AV *bvlist; struct fuse_bufvec *src; #ifndef PERL_HAS_64BITINT char *temp; #endif FUSE_CONTEXT_PRE; DEBUGf("read_buf begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(file ? sv_2mortal(newSVpv(file,0)) : &PL_sv_undef); XPUSHs(sv_2mortal(newSViv(size))); #ifdef PERL_HAS_64BITINT XPUSHs(sv_2mortal(newSViv(off))); #else if (asprintf(&temp, "%llu", off) == -1) croak("Memory allocation failure!"); XPUSHs(sv_2mortal(newSVpv(temp, 0))); free(temp); #endif bvlist = newAV(); bvhash = newHV(); (void) hv_store(bvhash, "size", 4, newSViv(size), 0); (void) hv_store(bvhash, "flags", 5, newSViv(0), 0); (void) hv_store(bvhash, "mem", 3, newSVpv("", 0), 0); (void) hv_store(bvhash, "fd", 2, newSViv(-1), 0); (void) hv_store(bvhash, "pos", 3, newSViv(0), 0); av_push(bvlist, newRV((SV *)bvhash)); XPUSHs(sv_2mortal(newRV_noinc((SV*) bvlist))); XPUSHs(FH_GETHANDLE(fi)); PUTBACK; rv = call_sv(MY_CXT.callback[CB_IDX_READ_BUF], G_SCALAR); SPAGAIN; if (!rv) rv = -ENOENT; else { SV **svp; int i; rv = POPi; if (rv < 0) goto READ_BUF_FAIL; src = malloc(sizeof(struct fuse_bufvec) + (av_len(bvlist) * sizeof(struct fuse_buf))); if (src == NULL) croak("Memory allocation failure!"); *src = FUSE_BUFVEC_INIT(0); src->count = av_len(bvlist) + 1; for (i = 0; i <= av_len(bvlist); i++) { svp = av_fetch(bvlist, i, 1); if (svp == NULL || *svp == NULL || !SvROK(*svp) || (bvhash = (HV *)SvRV(*svp)) == NULL || SvTYPE((SV *)bvhash) != SVt_PVHV) croak("Entry provided as part of bufvec was wrong!"); if ((svp = hv_fetch(bvhash, "size", 4, 0)) != NULL) src->buf[i].size = SvIV(*svp); if ((svp = hv_fetch(bvhash, "flags", 5, 0)) != NULL) src->buf[i].flags = SvIV(*svp); if (src->buf[i].flags & FUSE_BUF_IS_FD) { if ((svp = hv_fetch(bvhash, "fd", 2, 0)) != NULL) src->buf[i].fd = SvIV(*svp); else croak("FUSE_BUF_IS_FD passed but no fd!"); if (src->buf[i].flags & FUSE_BUF_FD_SEEK) { if ((svp = hv_fetch(bvhash, "pos", 3, 0)) != NULL) src->buf[i].fd = SvIV(*svp); else croak("FUSE_BUF_FD_SEEK passed but no pos!"); } } else { if ((svp = hv_fetch(bvhash, "mem", 3, 0)) != NULL) { src->buf[i].mem = SvPV_nolen(*svp); /* Should keep Perl from free()ing the memory * zone the SV points to, since it'll be * free()'d elsewhere at (potentially) any * time... */ SvLEN_set(*svp, 0); } } } *bufp = src; } READ_BUF_FAIL: FREETMPS; LEAVE; PUTBACK; DEBUGf("read_buf end: %i\n", rv); FUSE_CONTEXT_POST; return rv; } int _PLfuse_flock (const char *file, struct fuse_file_info *fi, int op) { int rv; #ifndef PERL_HAS_64BITINT char *temp; #endif FUSE_CONTEXT_PRE; DEBUGf("flock begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(file ? sv_2mortal(newSVpv(file,0)) : &PL_sv_undef); XPUSHs(FH_GETHANDLE(fi)); #ifdef PERL_HAS_64BITINT XPUSHs(sv_2mortal(newSViv(fi->lock_owner))); #else if (asprintf(&temp, "%llu", fi->lock_owner) == -1) croak("Memory allocation failure!"); XPUSHs(sv_2mortal(newSVpv(temp, 0))); #endif XPUSHs(sv_2mortal(newSViv(op))); PUTBACK; rv = call_sv(MY_CXT.callback[CB_IDX_FLOCK],G_SCALAR); SPAGAIN; rv = (rv ? POPi : 0); FREETMPS; LEAVE; PUTBACK; DEBUGf("flock end: %i\n", rv); FUSE_CONTEXT_POST; return rv; } #if FUSE_FOUND_MICRO_VER >= 1 || FUSE_FOUND_MAJOR_VER >= 3 int _PLfuse_fallocate (const char *file, int mode, off_t offset, off_t length, struct fuse_file_info *fi) { int rv; #ifndef PERL_HAS_64BITINT char *temp; #endif FUSE_CONTEXT_PRE; DEBUGf("fallocate begin\n"); ENTER; SAVETMPS; PUSHMARK(SP); XPUSHs(file ? sv_2mortal(newSVpv(file,0)) : &PL_sv_undef); XPUSHs(FH_GETHANDLE(fi)); XPUSHs(sv_2mortal(newSViv(mode))); #ifdef PERL_HAS_64BITINT XPUSHs(sv_2mortal(newSViv(offset))); #else if (asprintf(&temp, "%llu", offset) == -1) croak("Memory allocation failure!"); XPUSHs(sv_2mortal(newSVpv(temp, 0))); #endif #ifdef PERL_HAS_64BITINT XPUSHs(sv_2mortal(newSViv(length))); #else if (asprintf(&temp, "%llu", length) == -1) croak("Memory allocation failure!"); XPUSHs(sv_2mortal(newSVpv(temp, 0))); #endif PUTBACK; rv = call_sv(MY_CXT.callback[CB_IDX_FALLOCATE],G_SCALAR); SPAGAIN; rv = (rv ? POPi : 0); FREETMPS; LEAVE; PUTBACK; DEBUGf("fallocate end: %i\n", rv); FUSE_CONTEXT_POST; return rv; } #endif /* FUSE_FOUND_MICRO_VER >= 1 || FUSE_FOUND_MAJOR_VER >= 3 */ MODULE = Filesys::Fuse3 PACKAGE = Filesys::Fuse3 PROTOTYPES: DISABLE BOOT: MY_CXT_INIT; #ifdef USE_ITHREADS MY_CXT.self = aTHX; #endif void CLONE(...) PREINIT: #ifdef USE_ITHREADS int i; dTHX; #endif CODE: #ifdef USE_ITHREADS MY_CXT_CLONE; tTHX parent = MY_CXT.self; MY_CXT.self = my_perl; { CLONE_PARAMS *clone_param; clone_param = Perl_clone_params_new(parent, aTHX); for(i=0;iuid), 0); (void) hv_store(hash, "gid", 3, newSViv(fc->gid), 0); (void) hv_store(hash, "pid", 3, newSViv(fc->pid), 0); if (fc->private_data) (void) hv_store(hash, "private", 7, fc->private_data, 0); (void) hv_store(hash, "umask", 5, newSViv(fc->umask), 0); RETVAL = newRV_noinc((SV*)hash); } else { XSRETURN_UNDEF; } OUTPUT: RETVAL void fuse_version() PPCODE: int gimme = GIMME_V; if (gimme == G_SCALAR) XPUSHs(sv_2mortal(newSVpvf("%d.%d", FUSE_MAJOR_VERSION, FUSE_MINOR_VERSION))); else if (gimme == G_ARRAY) { #ifdef FUSE_FOUND_MICRO_VER XPUSHs(sv_2mortal(newSViv(FUSE_FOUND_MAJOR_VER))); XPUSHs(sv_2mortal(newSViv(FUSE_FOUND_MINOR_VER))); XPUSHs(sv_2mortal(newSViv(FUSE_FOUND_MICRO_VER))); #else XPUSHs(sv_2mortal(newSViv(FUSE_MAJOR_VERSION))); XPUSHs(sv_2mortal(newSViv(FUSE_MINOR_VERSION))); XPUSHs(sv_2mortal(newSViv(0))); #endif } SV * XATTR_CREATE() CODE: RETVAL = newSViv(XATTR_CREATE); OUTPUT: RETVAL SV * XATTR_REPLACE() CODE: RETVAL = newSViv(XATTR_REPLACE); OUTPUT: RETVAL #ifdef __linux__ SV * UTIME_NOW() CODE: RETVAL = newSViv(UTIME_NOW); OUTPUT: RETVAL SV * UTIME_OMIT() CODE: RETVAL = newSViv(UTIME_OMIT); OUTPUT: RETVAL #endif /* defined(__linux__) */ SV * FUSE_BUF_IS_FD() CODE: RETVAL = newSViv(FUSE_BUF_IS_FD); OUTPUT: RETVAL SV * FUSE_BUF_FD_SEEK() CODE: RETVAL = newSViv(FUSE_BUF_FD_SEEK); OUTPUT: RETVAL SV * FUSE_BUF_FD_RETRY() CODE: RETVAL = newSViv(FUSE_BUF_FD_RETRY); OUTPUT: RETVAL ssize_t fuse_buf_copy(...) PREINIT: struct fuse_bufvec *dst = NULL, *src = NULL; AV *av_src, *av_dst; HV *hv; SV **svp, *sv; int i; INIT: if (items != 2) { fprintf(stderr, "fuse_buf_copy needs dst and src\n"); XSRETURN_UNDEF; } CODE: sv = ST(0); if (!(SvROK(sv) && SvTYPE(av_dst = (AV *)SvRV(sv)) == SVt_PVAV)) croak("Argument supplied was not arrayref!"); sv = ST(1); if (!(SvROK(sv) && SvTYPE(av_src = (AV *)SvRV(sv)) == SVt_PVAV)) croak("Argument supplied was not arrayref!"); dst = malloc(sizeof(struct fuse_bufvec) + (av_len(av_dst) * sizeof(struct fuse_buf))); if (dst == NULL) croak("Memory allocation failure!"); *dst = FUSE_BUFVEC_INIT(0); dst->count = av_len(av_dst) + 1; for (i = 0; i <= av_len(av_dst); i++) { svp = av_fetch(av_dst, i, 1); if (svp == NULL || *svp == NULL || !SvROK(*svp) || (hv = (HV *)SvRV(*svp)) == NULL || SvTYPE((SV *)hv) != SVt_PVHV) croak("Entry provided as part of bufvec was wrong!"); if ((svp = hv_fetch(hv, "size", 4, 0)) != NULL) dst->buf[i].size = SvIV(*svp); if ((svp = hv_fetch(hv, "flags", 5, 0)) != NULL) dst->buf[i].flags = SvIV(*svp); if (dst->buf[i].flags & FUSE_BUF_IS_FD) { if ((svp = hv_fetch(hv, "fd", 2, 0)) != NULL) dst->buf[i].fd = SvIV(*svp); else croak("FUSE_BUF_IS_FD passed but no fd!"); if (dst->buf[i].flags & FUSE_BUF_FD_SEEK) { if ((svp = hv_fetch(hv, "pos", 3, 0)) != NULL) dst->buf[i].fd = SvIV(*svp); else croak("FUSE_BUF_FD_SEEK passed but no pos!"); } } else { if ((svp = hv_fetch(hv, "mem", 3, 0)) != NULL) { if ((dst->buf[i].mem = malloc(dst->buf[i].size)) == NULL) croak("Memory allocation failure!"); } } } src = malloc(sizeof(struct fuse_bufvec) + (av_len(av_src) * sizeof(struct fuse_buf))); if (src == NULL) croak("Memory allocation failure!"); *src = FUSE_BUFVEC_INIT(0); src->count = av_len(av_src) + 1; for (i = 0; i <= av_len(av_src); i++) { svp = av_fetch(av_src, i, 1); if (svp == NULL || *svp == NULL || !SvROK(*svp) || (hv = (HV *)SvRV(*svp)) == NULL || SvTYPE((SV *)hv) != SVt_PVHV) croak("Entry provided as part of bufvec was wrong!"); if ((svp = hv_fetch(hv, "size", 4, 0)) != NULL) src->buf[i].size = SvIV(*svp); if ((svp = hv_fetch(hv, "flags", 5, 0)) != NULL) src->buf[i].flags = SvIV(*svp); if (src->buf[i].flags & FUSE_BUF_IS_FD) { if ((svp = hv_fetch(hv, "fd", 2, 0)) != NULL) src->buf[i].fd = SvIV(*svp); else croak("FUSE_BUF_IS_FD passed but no fd!"); if (src->buf[i].flags & FUSE_BUF_FD_SEEK) { if ((svp = hv_fetch(hv, "pos", 3, 0)) != NULL) src->buf[i].fd = SvIV(*svp); else croak("FUSE_BUF_FD_SEEK passed but no pos!"); } } else { if ((svp = hv_fetch(hv, "mem", 3, 0)) != NULL) { src->buf[i].mem = SvPV_nolen(*svp); SvLEN_set(*svp, 0); } } } RETVAL = fuse_buf_copy(dst, src, 0); if (RETVAL > 0) { for (i = 0; i < dst->count; i++) { svp = av_fetch(av_dst, i, 1); if (svp == NULL || *svp == NULL || !SvROK(*svp) || (hv = (HV *)SvRV(*svp)) == NULL || SvTYPE((SV *)hv) != SVt_PVHV) croak("Entry provided as part of bufvec was wrong!"); if (!(dst->buf[i].flags & FUSE_BUF_IS_FD)) { sv = newSV_type(SVt_PV); SvPV_set(sv, (char *)dst->buf[i].mem); SvLEN_set(sv, dst->buf[i].size); SvCUR_set(sv, dst->buf[i].size); SvPOK_on(sv); SvREADONLY_on(sv); (void) hv_store(hv, "mem", 3, sv, 0); } } } free(dst); free(src); OUTPUT: RETVAL void perl_fuse3_main(...) PREINIT: struct fuse_operations fops; struct fuse *fuse_handle; int i, debug; char *mountpoint; char *mountopts; struct fuse_args args = FUSE_ARGS_INIT(0, NULL); dMY_CXT; INIT: if(items != N_CALLBACKS + N_FLAGS) { fprintf(stderr,"Perl<->C inconsistency or internal error\n"); XSRETURN_UNDEF; } memset(&fops, 0, sizeof(struct fuse_operations)); CODE: debug = SvIV(ST(ARG_IDX_DEBUG)); MY_CXT.threaded = SvIV(ST(ARG_IDX_THREADED)); MY_CXT.handles = (HV*)(sv_2mortal((SV*)(newHV()))); if(MY_CXT.threaded) { #ifdef FUSE_USE_ITHREADS master_interp = aTHX; MUTEX_INIT(&MY_CXT.mutex); SvSHARE((SV*)(MY_CXT.handles)); #else fprintf(stderr,"FUSE warning: Your script has requested multithreaded " "mode, but your perl was not built with a supported " "thread model. Threads are disabled.\n"); MY_CXT.threaded = 0; #endif } mountpoint = SvPV_nolen(ST(ARG_IDX_MOUNTPOINT)); mountopts = SvPV_nolen(ST(ARG_IDX_MOUNTOPTS)); MY_CXT.nullpath_ok = SvIV(ST(ARG_IDX_NULLPATH_OK)); MY_CXT.utimens_as_array = SvIV(ST(ARG_IDX_UTIMENS_AS_ARRAY)); for(i=0;iSUPER::test_via_harness($perl, $tests); s/PERL_DL_NONLAZY=1//g if $^O eq 'darwin'; return $_; } sub test_via_script { my($self, $perl, $tests) = @_; local $_ = $self->SUPER::test_via_script($perl, $tests); s/PERL_DL_NONLAZY=1//g if $^O eq 'darwin'; return $_; } package main; # 'can_run' courtesy of Module::Install sub can_run { my ($cmd) = @_; my $_cmd = $cmd; return $_cmd if (-x $_cmd or $_cmd = MM->maybe_command($_cmd)); for my $dir ((split /$Config::Config{path_sep}/, $ENV{PATH}), '.') { next if $dir eq ''; my $abs = File::Spec->catfile($dir, $cmd); return $abs if (-x $abs or $abs = MM->maybe_command($abs)); } return; } # Fix Cygwin bug on maybe_command() - also courtesy of Module::Install; if ( $^O eq 'cygwin' ) { require ExtUtils::MM_Cygwin; require ExtUtils::MM_Win32; if ( ! defined(&ExtUtils::MM_Cygwin::maybe_command) ) { *ExtUtils::MM_Cygwin::maybe_command = sub { my ($self, $file) = @_; if ($file =~ m{^/cygdrive/}i and ExtUtils::MM_Win32->can('maybe_command')) { ExtUtils::MM_Win32->maybe_command($file); } else { ExtUtils::MM_Unix->maybe_command($file); } } } } my $fuse = "fuse3"; my $command; if ( can_run('pkg-config') ) { $command = 'pkg-config'; } elsif ( can_run('ppkg-config') ) { # we use flags that are only available after 0.08626 my $pkg_version = `ppkg-config --real-version`; if ($pkg_version =~ /Version: (\d+\.\d+)/) { if ($1 >= 0.08626) { $command = 'ppkg-config'; } else { die "Outdated PkgConfig ($1). Please upgrade to 0.08626 or later.\n"; } } } else { die "Cannot build. Please install pkg-config or the PkgConfig perl module before continuing.\n"; } chomp(my $fusever = `$command --modversion $fuse 2> /dev/null`); # Required for refuse on NetBSD if (!$fusever && $^O eq 'netbsd') { chomp($fusever = `fusermount -V`); $fusever =~ s/^.*?version:\s+//; } unless ($fusever) { # make CPANPLUS happy and don't report errors if fuse isn't installed my $explanation; if ($^O eq 'linux') { if (-e '/etc/debian_version') { $explanation = 'You need to install "libfuse3-dev" to provide build support for this module'; } elsif (-e '/etc/redhat-release') { $explanation = 'You need to install "fuse-devel" to provide build support for this module'; } else { $explanation = 'I don\'t know your Linux distribution, but please install the FUSE libraries and headers to build this module'; } } elsif ($^O eq 'freebsd') { $explanation = 'You need to install the "fusefs-libs" package from ports to support this module'; } elsif ($^O eq 'netbsd') { $explanation = 'Could not find librefuse? Maybe install "perfuse" from pkgsrc, or upgrade to newer NetBSD'; } elsif ($^O eq 'darwin') { $explanation = 'Please install OSXFUSE from http://osxfuse.github.com/ or via homebrew (brew install osxfuse)'; } elsif ($^O eq 'solaris') { open(my $motd, '<', '/etc/motd'); my $line = <$motd>; if ($line =~ /^OpenIndiana /) { $explanation = 'Please enable the \'sfe\' repository, and install the libfuse package'; } else { $explanation = 'Don\'t know how to enable FUSE support on this Solaris flavor'; } } elsif ($^O eq 'openbsd') { $explanation = 'Please see README for information about OpenBSD FUSE support'; } else { $explanation = 'There is no FUSE support for your platform to our knowledge, sorry'; } die("Cannot build for platform: $^O\n", $explanation, "\n"); } if ($fusever && $fusever + 0 < 2.6) { die "FUSE API is ", $fusever, ", must be 2.6 or later\n"; } else { warn "fuse version found: ", $fusever, "\n"; } chomp(my $inc = `$command --cflags-only-I $fuse 2> /dev/null`); chomp(my $libs = `$command --libs-only-L $fuse 2> /dev/null`); $libs .= ' '; chomp($libs .= `$command --libs-only-l $fuse 2> /dev/null` || (($^O eq 'netbsd') ? '-lrefuse' : '-lfuse')); # Needed for Fuse on OS X 10.6, due to 10.6 and up always using the 64-bit # inode structs; unfortunately MacFuse doesn't just do the right thing # on its own. (Not applicable for OSXFUSE; it uses a new SONAME, so we # don't have to worry about conflicts/compatibility, it "just works".) if ($^O eq 'darwin' && (uname())[2] =~ /^1[01]\./) { $libs =~ s/-lfuse\b/-lfuse_ino64/; } chomp(my $def = '-Wall -DFUSE_USE_VERSION=30 ' . `$command --cflags-only-other $fuse 2> /dev/null` || '-D_FILE_OFFSET_BITS=64'); chomp($def .= `$command --libs-only-other $fuse 2> /dev/null`); $def .= ' -DPERL_HAS_64BITINT' if $Config{'use64bitint'}; $def .= ' -DUSING_LIBREFUSE' if $libs =~ m{-lrefuse\b}; # As a feature was added in a micro version update (fallocate(), # specifically), we need to know the micro version level, and there's # nothing in the headers that supplies it, so I'm gonna ghetto this # up. my ($major, $minor, $micro) = split(m{\.}, $fusever); if ($^O eq 'openbsd') { # Note: This is a hack. Unfortunately OpenBSD's FUSE lies in its # pkgconfig and claims to be FUSE 2.8, when it only supplies the # FUSE 2.6 API. This breaks the module, so I'm going to work around # that for now... $minor = 6; } if (!defined $micro) { $micro = 0; } $def .= ' -DFUSE_FOUND_MAJOR_VER=' . $major; $def .= ' -DFUSE_FOUND_MINOR_VER=' . $minor; $def .= ' -DFUSE_FOUND_MICRO_VER=' . $micro; WriteMakefile( 'NAME' => 'Filesys::Fuse3', 'VERSION_FROM' => 'Fuse3.pm', # finds $VERSION 'PREREQ_PM' => { # e.g., Module::Name => 1.1 'Lchown' => 0, 'Filesys::Statvfs' => 0, 'Unix::Mknod' => 0, }, ABSTRACT_FROM => 'Fuse3.pm', # retrieve abstract from module AUTHOR => 'Dominique Dumont ', 'LICENSE' => 'LGPL_2_1', META_MERGE => { resources => { license => 'http://www.gnu.org/licenses/lgpl-2.1.html', bugtracker => 'http://github.com/dod38fr/perl-filesys-fuse3/issues', repository => 'http://github.com/dod38fr/perl-filesys-fuse3' }, }, 'LIBS' => $libs, # e.g., '-lm' 'DEFINE' => $def, # e.g., '-DHAVE_SOMETHING' 'OPTIMIZE' => '-g -ggdb', # Insert -I. if you add *.h files later: 'INC' => $inc, # e.g., '-I/usr/include/other' # Un-comment this if you add C files to link with later: 'OBJECT' => 'Fuse3$(OBJ_EXT)', # link all the C files too ); sub MY::postamble { return <<'MAKE_MORE'; cpan: make clean rm -f Filesys-Fuse3-*.tar.gz perl Makefile.PL make dist make disttest @echo @echo -n "Upload" Filesys-Fuse3-*.tar.gz "to CPAN? [y/N]:" @read upload; \ if [ "$$upload" = "y" ] ; then \ cpan-upload -verbose Filesys-Fuse3-*.tar.gz; \ fi MAKE_MORE }; # vim: ts=4 ai et hls libfilesys-fuse3-perl-0.021/README.md000066400000000000000000000155001516674026300171360ustar00rootroot00000000000000Filesys::Fuse3 Perl bindings ================== This module lets you implement filesystems in Perl, through the [FUSE](http://fuse.sourceforge.net) (Filesystem in USErspace) kernel/lib interface. ```perl use Filesys::Fuse3; Filesys::Fuse3::main( mountpoint => '/mnt/my_fs', threaded => 0, debug => 1, getattr => sub { ... }, # fetches attributes, like 'stat' readdir => sub { ... }, # obtains directory listings open => sub { ... }, # opens files statfs => sub { ... }, # returns filesystem data read => sub { ... }, # reads file contents # there are many more you can implement! ); ``` See [Filesys::Fuse3's main documentation](https://metacpan.org/pod/distribution/Filesys/Fuse3/Fuse3.pm) for more details. #### Installation #### This module requires the FUSE C library and the FUSE kernel module, both available at http://fuse.sourceforge.net. There are pre-built packages for FUSE in major operating systems: **Debian:** `sudo apt-get install libfuse3-dev` **RedHat:** `sudo yum install fuse-devel` **OSX:** install [OSXFUSE](http://osxfuse.github.com) manually or via homebrew `brew install osxfuse` **Solaris:** enable the 'sfe' repository, then install `libfuse` FUSE is also available on the main BSD flavours, but please see the notes below for extra information: **FreeBSD:** install `fusefs-libs` from ports **NetBSD:** install `librefuse` or `perfuse` from pkgsrc **OpenBSD:** (see below) If you intend to use FUSE in threaded mode, you need a version of perl compiled with USE_ITHREADS. Then, you need to use threads and threads::shared. After installing the external libraries, you can install the Filesys::Fuse3 module using you favorite CPAN tool. For example: cpanm Filesys::Fuse3 Or manually, by downloading, unpacking and typing: perl Makefile.PL make make test make install #### EXAMPLES #### We have bundled a few example scripts in the examples/ subdirectory. These are: * example.pl, a simple "Hello world" type of script * loopback.pl, a filesystem loopback-device. Like fusexmp from the main FUSE dist, it simply recurses file operations into the real filesystem. However, unlike fusexmp, it only re-shares files under the /tmp/test directory. * rmount.pl, an NFS-workalike which tunnels through SSH. It requires an account on some ssh server (obviously) with public-key authentication enabled (if you have to type in a password, you don't have this. See *man ssh_keygen* for more information). Copy rmount_remote.pl to your home directory on the remote machine, and create a subdir somewhere, and then run it like: ./rmount.pl host /remote/dir /local/dir * rmount_remote.pl, a ripoff of loopback.pl meant to be used as a backend for rmount.pl. ### Happy FUSEing! ### #### Notes for BSD users #### On NetBSD, there is a potential issue with readdir() only being called once when using librefuse. However, currently using Perfuse causes other issues (readlink() drops the last character from the read link path, and the block count in stat() is incorrect). We will be addressing these concerns with the appropriate developers in the near future. If you are using Perfuse on NetBSD, you should do the following (as root): cat >> /etc/sysctl.conf <<_EOT_ kern.sbmax=2621440 net.inet.tcp.sendbuf_max=2621440 net.inet6.tcp6.sendbuf_max=2621440 _EOT_ sysctl -f /etc/sysctl.conf Perfuse uses TCP sockets, and needs large send buffers. On NetBSD and FreeBSD, extended attributes do not work. These are specifically related to the FUSE implementations on those platforms. Normally you can not mount FUSE filesystems as non-root users on FreeBSD and NetBSD. They can allow non-root users to mount FUSE filesystems, but instead of changing the mode of /dev/fuse or /bin/fusermount, you need to use sysctl to allow user mounts. For FreeBSD, this involves (as root): sysctl -w vfs.usermount=1 pw usermod -G operator And on NetBSD (also as root): sysctl -w vfs.generic.usermount=1 chmod 0660 /dev/putter usermod -G wheel #### Notes for OpenBSD in particular #### While it still has some known issues, FUSE has actually made its way onto OpenBSD. As of this writing, OpenBSD includes a BSD-licensed reimplementation of libfuse, and their own fuse kernel driver. It is available at better OpenBSD mirrors everywhere. Keep in mind that if you want thread support (some Fuse filesystems do require it), the Perl build in OpenBSD 5.6 base does *not* support threads. Assuming you're not an OpenBSD ninja (or completely insane), I don't recommend trying to build your own and install it over the system Perl, because that can have bad repercussions. Use something like [perlbrew](http://perlbrew.pl) if you want a threaded Perl: perlbrew install perl- -Dusethreads \ --as perl-_WITH_THREADS For the tests, I recommend installing devel/p5-Lchown, devel/p5-Filesys-Statvfs, devel/p5-Unix-Mknod and devel/p5-Test-Pod from OpenBSD ports. (Or use the 'cpan' command from your Perlbrew-installed version to install those modules, if you want thread support like I was talking about above.) Now, in your perl-fuse distribution, run: perl Makefile.PL make You'll probably need to 'make test' as root. If you want to run your FUSE filesystem as non-root, run the following (as root): sysctl kern.usermount=1 chmod 0660 /dev/fuse0 Now, you should be able to run 'make test'. Yes, there are a few test failures. No, those actually aren't our fault. Here are some things you should know about the state of FUSE on OpenBSD (the developer, Sylvestre Gallon, has been made aware of these): * There is a bug if a file is created in the fuse filesystem and goes away, then you create another file of the same name via FUSE and try to do utime(). Not sure if it's just utime() or if other things trip it too, but I discovered that via playing around. I *THINK* it's a vnode caching problem. * There is also a known issue with access() if all parent directories of a path haven't been explicitly accessed first. * There is a known issue with readdir() when supplying numbered dirents to support progressive readdir(). * You should probably implement all of chown(), chmod(), truncate(), and utime() and/or utimens(). The kernel driver will mask out future setattr() requests if it gets ENOSYS from ANY of these. Oops. #### COPYRIGHT AND LICENCE #### This is contributed to the FUSE project by Mark Glines , and is therefore subject to the same license and copyright as FUSE itself. It is released under LGPL 2.1. Please see the AUTHORS and COPYING files from the FUSE distribution for more information. libfilesys-fuse3-perl-0.021/examples/000077500000000000000000000000001516674026300174745ustar00rootroot00000000000000libfilesys-fuse3-perl-0.021/examples/example.pl000077500000000000000000000060621516674026300214730ustar00rootroot00000000000000#!/usr/bin/perl -w use strict; use Data::Dumper; #use blib; use Filesys::Fuse3 qw(fuse_get_context); use POSIX qw(ENOENT EISDIR EINVAL); my (%files) = ( '.' => { type => 0040, mode => 0755, ctime => time()-1000 }, a => { cont => "File 'a'.\n", type => 0100, mode => 0755, ctime => time()-2000 }, b => { cont => "This is file 'b'.\n", type => 0100, mode => 0644, ctime => time()-1000 }, me => { size => 45, type => 0100, mode => 0644, ctime => time()-1000 }, ); sub filename_fixup { my ($file) = shift; $file =~ s,^/,,; $file = '.' unless length($file); return $file; } sub e_getattr { my ($file) = filename_fixup(shift); $file =~ s,^/,,; $file = '.' unless length($file); return -ENOENT() unless exists($files{$file}); my ($size) = exists($files{$file}{cont}) ? length($files{$file}{cont}) : 0; $size = $files{$file}{size} if exists $files{$file}{size}; my ($modes) = ($files{$file}{type}<<9) + $files{$file}{mode}; my ($dev, $ino, $rdev, $blocks, $gid, $uid, $nlink, $blksize) = (0,0,0,1,0,0,1,1024); my ($atime, $ctime, $mtime); $atime = $ctime = $mtime = $files{$file}{ctime}; # 2 possible types of return values: #return -ENOENT(); # or any other error you care to #print(join(",",($dev,$ino,$modes,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks)),"\n"); return ($dev,$ino,$modes,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks); } sub e_readdir { # return as many text filenames as you like, followed by the retval. print((scalar keys %files)."\n"); return (keys %files),0; } sub e_open { # VFS sanity check; it keeps all the necessary state, not much to do here. my $file = filename_fixup(shift); my ($flags, $fileinfo) = @_; print("open called $file, $flags, $fileinfo\n"); return -ENOENT() unless exists($files{$file}); return -EISDIR() if $files{$file}{type} & 0040; my $fh = [ rand() ]; print("open ok (handle $fh)\n"); return (0, $fh); } sub e_read { # return an error numeric, or binary/text string. (note: 0 means EOF, "0" will # give a byte (ascii "0") to the reading program) my ($file) = filename_fixup(shift); my ($buf, $off, $fh) = @_; print "read from $file, $buf \@ $off\n"; print "file handle:\n", Dumper($fh); return -ENOENT() unless exists($files{$file}); if(!exists($files{$file}{cont})) { return -EINVAL() if $off > 0; my $context = fuse_get_context(); return sprintf("pid=0x%08x uid=0x%08x gid=0x%08x\n",@$context{'pid','uid','gid'}); } return -EINVAL() if $off > length($files{$file}{cont}); return 0 if $off == length($files{$file}{cont}); return substr($files{$file}{cont},$off,$buf); } sub e_statfs { return 255, 1, 1, 1, 1, 2 } # If you run the script directly, it will run fusermount, which will in turn # re-run this script. Hence the funky semantics. my ($mountpoint) = ""; $mountpoint = shift(@ARGV) if @ARGV; Filesys::Fuse3::main( mountpoint=>$mountpoint, getattr=>"main::e_getattr", readdir =>"main::e_readdir", open =>"main::e_open", statfs =>"main::e_statfs", read =>"main::e_read", threaded=>0 ); libfilesys-fuse3-perl-0.021/examples/example_t.pl000077500000000000000000000047731516674026300220250ustar00rootroot00000000000000#!/usr/bin/perl -w use strict; use threads; use threads::shared; use Filesys::Fuse3; use POSIX qw(ENOENT EISDIR EINVAL); my (%files) = ( '.' => { type => 0040, mode => 0755, ctime => time()-1000 }, a => { cont => "File 'a'.\n", type => 0100, mode => 0755, ctime => time()-2000 }, b => { cont => "This is file 'b'.\n", type => 0100, mode => 0644, ctime => time()-1000 }, ); sub filename_fixup { my ($file) = shift; $file =~ s,^/,,; $file = '.' unless length($file); return $file; } sub e_getattr { my ($file) = filename_fixup(shift); $file =~ s,^/,,; $file = '.' unless length($file); return -ENOENT() unless exists($files{$file}); my ($size) = exists($files{$file}{cont}) ? length($files{$file}{cont}) : 0; my ($modes) = ($files{$file}{type}<<9) + $files{$file}{mode}; my ($dev, $ino, $rdev, $blocks, $gid, $uid, $nlink, $blksize) = (0,0,0,1,0,0,1,1024); my ($atime, $ctime, $mtime); $atime = $ctime = $mtime = $files{$file}{ctime}; # 2 possible types of return values: #return -ENOENT(); # or any other error you care to #print(join(",",($dev,$ino,$modes,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks)),"\n"); return ($dev,$ino,$modes,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks); } sub e_getdir { # return as many text filenames as you like, followed by the retval. print((scalar keys %files)."\n"); return (keys %files),0; } sub e_open { # VFS sanity check; it keeps all the necessary state, not much to do here. my ($file) = filename_fixup(shift); print("open called\n"); return -ENOENT() unless exists($files{$file}); return -EISDIR() unless exists($files{$file}{cont}); print("open ok\n"); return 0; } sub e_read { # return an error numeric, or binary/text string. (note: 0 means EOF, "0" will # give a byte (ascii "0") to the reading program) my ($file) = filename_fixup(shift); my ($buf,$off) = @_; return -ENOENT() unless exists($files{$file}); return -EINVAL() if $off > length($files{$file}{cont}); return 0 if $off == length($files{$file}{cont}); return substr($files{$file}{cont},$off,$buf); } sub e_statfs { return 255, 1, 1, 1, 1, 2 } # If you run the script directly, it will run fusermount, which will in turn # re-run this script. Hence the funky semantics. my ($mountpoint) = ""; $mountpoint = shift(@ARGV) if @ARGV; Filesys::Fuse3::main( mountpoint=>$mountpoint, getattr=>"main::e_getattr", getdir =>"main::e_getdir", open =>"main::e_open", statfs =>"main::e_statfs", read =>"main::e_read", threaded=>1 ); libfilesys-fuse3-perl-0.021/examples/filter_attr_fs.pl000077500000000000000000000144751516674026300230560ustar00rootroot00000000000000#!/usr/bin/perl -w # filter_attr_t.pl # Loopback fs that shows only files with a particular xattr # (c) Reuben Thomas 29/11/2007-5/1/2008, based on example code from Fuse package use strict; #use blib; use Filesys::Fuse3; use File::ExtAttr ':all'; use IO::File; use POSIX qw(ENOENT ENOSYS EEXIST EPERM O_RDONLY O_RDWR O_APPEND O_CREAT O_ACCMODE); use Fcntl qw(S_ISBLK S_ISCHR S_ISFIFO SEEK_SET); # Debug flag my $debug = 0; # Global settings my ($tag, $real_root, $mountpoint); sub debug { print STDERR shift if $debug ne 0; } my $can_syscall = eval { require 'syscall.ph'; # for SYS_mknod and SYS_lchown }; if (!$can_syscall && open my $fh, '<', '/usr/include/sys/syscall.h') { my %sys = do { local $/ = undef; <$fh> =~ m/\#define \s+ (\w+) \s+ (\d+)/gxms; }; close $fh; if ($sys{SYS_mknod} && $sys{SYS_lchown}) { *SYS_mknod = sub { $sys{SYS_mknod} }; *SYS_lchown = sub { $sys{SYS_lchown} }; $can_syscall = 1; } } sub tagged { my ($file) = @_; $file =~ s|/$||; my $ret = getfattr($file, $tag); debug("tagged: $file $tag " . defined($ret) . "\n"); return $ret; } sub tag { return setfattr(shift, $tag, ""); } sub detag { return delfattr(shift, $tag); } sub append_root { return $real_root . shift; } sub err { my ($err) = @_; return $err ? 0 : -$!; } sub x_getattr { debug("x_getattr "); my ($file) = append_root(shift); return -ENOENT() unless tagged($file); my (@list) = lstat($file); return -$! unless @list; return @list; } sub x_readlink { debug("x_readlink "); return readlink(append_root(shift)); } sub x_getdir { debug("x_getdir "); my ($dirname) = append_root(shift); return -ENOENT() unless tagged($dirname) && opendir(DIRHANDLE, $dirname); my (@files) = readdir(DIRHANDLE); closedir(DIRHANDLE); my @psifiles = grep {tagged("$dirname/$_")} @files; return (@psifiles, 0); } sub x_mknod { my ($file, $modes, $dev) = @_; return -ENOSYS() if !$can_syscall; debug("x_mknod "); $file = append_root($file); return -EEXIST() if -e $file && !tagged($file); $! = 0; syscall(&SYS_mknod, $file, $modes, $dev); return -$! if $! != 0; return err(tag($file)); } sub x_mkdir { debug("x_mkdir "); my ($name, $perm) = @_; $name = append_root($name); debug("$name"); my $ret = err(mkdir $name, $perm); return $ret if $ret != 0; return err(tag($name)); } sub x_open { my ($file) = append_root(shift); my ($mode) = shift; my $accmode = $mode & O_ACCMODE; debug("x_open $accmode " . O_ACCMODE . " " . O_WRONLY . " " . O_RDWR . " "); if ($accmode == O_WRONLY || $accmode == O_RDWR) { return -EEXIST() if -e $file && !tagged($file); } else { return -ENOENT() unless tagged($file); } return -$! unless sysopen(FILE, $file, $mode); close(FILE); return 0; } sub x_read { debug("x_read "); my ($file, $bufsize, $off) = @_; my ($rv) = -ENOSYS(); my ($handle) = new IO::File; $file = append_root($file); return -ENOENT() unless tagged($file); my ($fsize) = -s $file; return -ENOSYS() unless open($handle, $file); if(seek($handle, $off, SEEK_SET)) { read($handle, $rv, $bufsize); } return $rv; } sub x_write { debug("x_write "); my ($file, $buf, $off) = @_; my ($rv); $file = append_root($file); return -ENOENT() unless tagged($file); my ($fsize) = -s $file; return -ENOSYS() unless open(FILE, '+<', $file); if ($rv = seek(FILE, $off, SEEK_SET)) { $rv = print(FILE $buf); } $rv = -ENOSYS() unless $rv; close(FILE); return length($buf); } sub x_unlink { debug("x_unlink "); my ($file) = append_root(shift); return -ENOENT() unless tagged($file); return err(detag($file)); } sub x_symlink { debug("x_symlink "); my ($old) = shift; my ($new) = append_root(shift); return -EEXIST() if -e $new && !tagged($new); return err(symlink($old, $new)); } sub x_rename { debug("x_rename "); my ($old) = append_root(shift); my ($new) = append_root(shift); return -ENOENT() unless tagged($old); return -EEXIST() unless !-e $new || tagged($new); my ($err) = rename($old, $new) ? 0 : -ENOENT(); return $err; } sub x_link { debug("x_link "); my ($old) = append_root(shift); my ($new) = append_root(shift); return -ENOENT() unless tagged($old); return -EEXIST() unless !-e $new || tagged($new); return err(link($old, $new)); } sub x_chown { return -ENOSYS() if !$can_syscall; debug("x_chown "); my ($fn) = append_root(shift); return -ENOENT() unless tagged($fn); my ($uid, $gid) = @_; # perl's chown() does not chown symlinks, it chowns the symlink's # target. It fails when the link's target doesn't exist, because # the stat64() syscall fails. # This causes error messages when unpacking symlinks in tarballs. my ($err) = syscall(&SYS_lchown, $fn, $uid, $gid, $fn) ? -$! : 0; return $err; } sub x_chmod { debug("x_chmod "); my ($fn) = append_root(shift); return -ENOENT() unless tagged($fn); my ($mode) = shift; return err(chmod($mode, $fn)); } sub x_truncate { debug("x_truncate "); my ($fn) = append_root(shift); return -ENOENT() unless tagged($fn); return err(truncate($fn, shift)); } sub x_utime { debug("x_utime "); my ($fn) = append_root($_[0]); return -ENOENT() unless tagged($fn); return err(utime($_[1], $_[2], $fn)); } sub x_rmdir { debug("x_rmdir "); my $dir = append_root(shift); return -ENOENT() unless tagged($dir); return err(detag($dir)); } sub x_statfs { debug("x_statfs\n"); my $name = append_root(shift); my($bsize, $frsize, $blocks, $bfree, $bavail, $files, $ffree, $favail, $fsid, $basetype, $flag, $namemax, $fstr) = statvfs($real_root) || return -$!; return ($namemax, $files, $ffree, $blocks, $bavail, $bsize); } # If you run the script directly, it will run fusermount, which will in turn # re-run this script. Hence the funky semantics. # Parse command-line arguments $mountpoint = ""; if (@ARGV) { $tag = shift(@ARGV); $real_root = shift(@ARGV); $mountpoint = shift(@ARGV); } # Start up FUSE Filesys::Fuse3::main( mountpoint=>$mountpoint, # debug => 1, getattr =>"main::x_getattr", readlink=>"main::x_readlink", getdir =>"main::x_getdir", mknod =>"main::x_mknod", mkdir =>"main::x_mkdir", unlink =>"main::x_unlink", rmdir =>"main::x_rmdir", symlink =>"main::x_symlink", rename =>"main::x_rename", link =>"main::x_link", chmod =>"main::x_chmod", chown =>"main::x_chown", truncate=>"main::x_truncate", utime =>"main::x_utime", open =>"main::x_open", read =>"main::x_read", write =>"main::x_write", statfs =>"main::x_statfs", ); libfilesys-fuse3-perl-0.021/examples/fioc.pl000077500000000000000000000107441516674026300207620ustar00rootroot00000000000000#!/usr/bin/env perl # fioc.pl: A Perl conversion of the fioc example IOCTL server program # from the FUSE distribution. I've endeavored to stay pretty close # structure-wise to the C version, while using Perl-specific features. # I wrote this to provide a way to verify my ioctl() wrapper # implementation would work properly. So far, it seems to, and it will # interoperate with the C client as well. use strict; no strict qw(refs); use threads; use threads::shared; use Carp; local $SIG{'__WARN__'} = \&Carp::cluck; use Filesys::Fuse3 qw(:all); use Fcntl qw(:mode); use POSIX; my $fioc_size :shared = 0; use constant FIOC_NAME => 'fioc'; my $fioc_buf :shared = ''; use constant FIOC_NONE => 0; use constant FIOC_ROOT => 1; use constant FIOC_FILE => 2; if ($^O eq 'linux') { require 'linux/ioctl.ph'; } else { require 'sys/ioccom.ph'; } our %sizeof = ('size_t' => length(pack('L!'))); sub FIOC_GET_SIZE { _IOR(ord 'E', 0, 'size_t'); } sub FIOC_SET_SIZE { _IOW(ord 'E', 1, 'size_t'); } sub TCGETS { 0x5401; } sub fioc_resize { my ($size) = @_; print 'called ', (caller(0))[3], "\n"; return 0 if $size == $fioc_size; if ($size < $fioc_size) { $fioc_buf = substr($fioc_buf, 0, $size); } else { $fioc_buf .= "\0" x ($size - $fioc_size); } $fioc_size = $size; return 0; } sub fioc_expand { my ($size) = @_; print 'called ', (caller(0))[3], "\n"; if ($size > $fioc_size) { return fioc_resize($size); } return 0; } sub fioc_file_type { my ($path) = @_; print 'called ', (caller(0))[3], "\n"; return FIOC_ROOT if $path eq '/'; return FIOC_FILE if $path eq '/' . FIOC_NAME; return FIOC_NONE; } sub fioc_getattr { my ($path) = @_; print 'called ', (caller(0))[3], "\n"; my @stbuf = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); $stbuf[4] = $<; $stbuf[5] = (split(/\s+/, $())[0]; $stbuf[8] = $stbuf[9] = time(); my $type = fioc_file_type($path); if ($type == FIOC_ROOT) { $stbuf[2] = S_IFDIR | 0755; $stbuf[3] = 2; } elsif ($type == FIOC_FILE) { $stbuf[2] = S_IFREG | 0644; $stbuf[3] = 1; $stbuf[7] = $fioc_size; } else { return -&ENOENT; } return @stbuf; } sub fioc_open { my ($path, $flags, $info) = @_; print 'called ', (caller(0))[3], "\n"; return 0 if fioc_file_type($path) != FIOC_NONE; return -&ENOENT; } sub fioc_read { my ($path, $size, $offset) = @_; print 'called ', (caller(0))[3], "\n"; return -&EINVAL if fioc_file_type($path) != FIOC_FILE; return q{} if $offset > $fioc_size; if ($size > $fioc_size - $offset) { $size - $fioc_size - $offset; } return substr($fioc_buf, $offset, $size); } sub fioc_write { my ($path, $data, $offset) = @_; print 'called ', (caller(0))[3], "\n"; lock($fioc_buf); return -&EINVAL if fioc_file_type($path) != FIOC_FILE; return -&ENOMEM if fioc_expand($offset + length($data)); substr($fioc_buf, $offset, length($data), $data); return length($data); } sub fioc_truncate { my ($path, $size) = @_; print 'called ', (caller(0))[3], "\n"; lock($fioc_buf); return -&EINVAL if fioc_file_type($path) != FIOC_FILE; return fioc_resize($size); } sub fioc_readdir { my ($path, $offset) = @_; print 'called ', (caller(0))[3], "\n"; return -&EINVAL if fioc_file_type($path) != FIOC_ROOT; return ('.', '..', FIOC_NAME, 0); } sub fioc_ioctl { my ($path, $cmd, $flags, $data) = @_; print 'called ', (caller(0))[3], "\n"; return -&EINVAL if fioc_file_type($path) != FIOC_FILE; return -&ENOSYS if $flags & FUSE_IOCTL_COMPAT; if ($cmd == FIOC_GET_SIZE) { return(0, pack('L!', $fioc_size)); } elsif ($cmd == FIOC_SET_SIZE) { lock($fioc_buf); fioc_resize(unpack('L!', $data)); return 0; } elsif ($cmd == TCGETS) { # perl sends TCGETS as part of calling isatty() on opening a file; # this appears to be a more canonical answer return -&ENOTTY; } return -&EINVAL; } croak("Filesys::Fuse3 doesn't have ioctl") unless Filesys::Fuse3::fuse_version() >= 2.8; Filesys::Fuse3::main( 'mountpoint' => $ARGV[0], 'getattr' => 'main::fioc_getattr', 'readdir' => 'main::fioc_readdir', 'truncate' => 'main::fioc_truncate', 'open' => 'main::fioc_open', 'read' => 'main::fioc_read', 'write' => 'main::fioc_write', 'ioctl' => 'main::fioc_ioctl', 'threaded' => 1); libfilesys-fuse3-perl-0.021/examples/fioclient.pl000077500000000000000000000032711516674026300220130ustar00rootroot00000000000000#!/usr/bin/env perl # fioclient.pl: A Perl version of the fioclient IOCTL client example from # the FUSE distribution. use strict; no strict qw(refs); use Carp; local $SIG{'__WARN__'} = \&Carp::cluck; use Fcntl qw(:mode); use Errno qw(:POSIX); use POSIX; if ($^O eq 'linux') { require 'linux/ioctl.ph'; } else { require 'sys/ioccom.ph'; } our %sizeof = ('size_t' => length(pack('L!'))); sub FIOC_GET_SIZE { _IOR(ord 'E', 0, 'size_t'); } sub FIOC_SET_SIZE { _IOW(ord 'E', 1, 'size_t'); } sub usage { print <<'_EOT_'; Usage: fioclient.pl FIOC_FILE COMMAND COMMANDS s [SIZE] : get size if SIZE is omitted, set size otherwise r SIZE [OFF] : read SIZE bytes @ OFF (default 0) and output to stdout w SIZE [OFF] : write SIZE bytes @ OFF (default 0) from stdin _EOT_ exit(1); } usage() if scalar(@ARGV) < 2; open(my $file, '+<', $ARGV[0]) or usage(); if ($ARGV[1] eq 's') { if (!defined $ARGV[2]) { my $size; my $rv = ioctl($file, FIOC_GET_SIZE, $size); if (!defined($rv) || $rv != 0) { croak($!); } printf("\%u\n", unpack('L!', $size)); } else { my $rv = ioctl($file, FIOC_SET_SIZE, pack('L!', $ARGV[2])); if (!defined($rv) || $rv != 0) { croak($!); } } } elsif ($ARGV[1] eq 'r' || $ARGV[1] eq 'w') { usage() unless defined $ARGV[2]; my $size = $ARGV[2]; my $off = 0; if (defined $ARGV[3]) { $off = $ARGV[3]; } seek($file, SEEK_SET, $off); if ($ARGV[1] eq 'r') { read($file, my $data, $size); print $data; } else { read(STDIN, my $data, $size); print $file $data; } } else { usage(); } close($file); libfilesys-fuse3-perl-0.021/examples/fsel.pl000077500000000000000000000127201516674026300207670ustar00rootroot00000000000000#!/usr/bin/env perl use strict; no strict qw(refs); use threads; use threads::shared; use Carp; local $SIG{'__WARN__'} = \&Carp::cluck; use Filesys::Fuse3 qw(:all); use Fcntl qw(:mode); use POSIX; use IO::Poll qw(POLLIN); use Time::HiRes qw(sleep); use Getopt::Long; # $fsel_open_mask is used to limit the number of opens to 1 per file. This # uses the file index (0-F) as $fh, as poll support requires a unique handle # per open file. Lifting this would require more complete open file # management. my $fsel_open_mask :shared = 0; # Maximum "file" size. use constant FSEL_CNT_MAX => 10; use constant FSEL_FILES => 16; # Used only as a lock for $fsel_poll_notify_mask and @fsel_cnt. my $fsel_mutex :shared; # Mask indicating what FDs have poll notifications waiting. my $fsel_poll_notify_mask :shared = 0; # Poll notification handles. my @fsel_poll_handle :shared; # Number of bytes for each "file". my @fsel_cnt :shared; # Initialize all byte counts. map { $fsel_cnt[$_] = 0 } (0 .. (FSEL_FILES - 1)); sub fsel_path_index { my ($path) = @_; print 'called ', (caller(0))[3], "\n"; return -1 if $path !~ m{^/([0-9A-F])$}; return hex($1); } sub fsel_getattr { my ($path) = @_; print 'called ', (caller(0))[3], "\n"; my @stbuf = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); if ($path eq '/') { @stbuf[2, 3] = (S_IFDIR | 0555, 2); return @stbuf; } my $idx = fsel_path_index($path); return -&ENOENT if $idx < 0; @stbuf[2, 3, 7] = (S_IFREG | 0444, 1, $fsel_cnt[$idx]); return @stbuf; } sub fsel_readdir { my ($path, $offset) = @_; print 'called ', (caller(0))[3], "\n"; return -&ENOENT if $path ne '/'; return('.', '..', map { sprintf('%X', $_) } (0 .. (FSEL_FILES - 1)), 0); } sub fsel_open { my ($path, $flags, $info) = @_; print 'called ', (caller(0))[3], "\n"; my $idx = fsel_path_index($path); return -&ENOENT if $idx < 0; return -&EACCES if $flags & O_ACCMODE != O_RDONLY; return -&EBUSY if $fsel_open_mask & (1 << $idx); $fsel_open_mask |= (1 << $idx); # fsel files are nonseekable somewhat pipe-like files which get filled # up periodically by the producer thread, and consumed on read. Tell # FUSE to do this right. @{$info}{'direct_io', 'nonseekable'} = (1, 1); return (0, $idx); } sub fsel_release { my ($path, $flags, $fh) = @_; print 'called ', (caller(0))[3], "\n"; $fsel_open_mask &= ~(1 << $fh); return 0; } sub fsel_read { my ($path, $size, $offset, $fh) = @_; print 'called ', (caller(0))[3], "\n"; lock($fsel_mutex); if ($fsel_cnt[$fh] < $size) { $size = $fsel_cnt[$fh]; } printf("READ \%X transferred=\%u cnt=\%u\n", $fh, $size, $fsel_cnt[$fh]); $fsel_cnt[$fh] -= $size; return(chr($fh) x $size); } our $polled_zero :shared = 0; sub fsel_poll { my ($path, $ph, $revents, $fh) = @_; print 'called ', (caller(0))[3], ", path = \"$path\", fh = $fh, revents = $revents\n"; lock($fsel_mutex); if ($ph) { my $oldph = $fsel_poll_handle[$fh]; pollhandle_destroy($oldph) if $oldph; $fsel_poll_notify_mask |= (1 << $fh); $fsel_poll_handle[$fh] = $ph; } if ($fsel_cnt[$fh]) { $revents |= POLLIN; printf("POLL \%X cnt=\%u polled_zero=\%u\n", $fh, $fsel_cnt[$fh], $polled_zero); $polled_zero = 0; } else { $polled_zero++; } return(0, $revents); } sub fsel_producer { print 'called ', (caller(0))[3], "\n"; local $SIG{'KILL'} = sub { threads->exit(); }; my ($tv, $idx, $nr) = (0.25, 0, 1); while (1) { { my ($i, $t); lock($fsel_mutex); # This is the main producer loop which is executed every 250 # msec. On each iteration, it adds one byte to 1, 2 or 4 files # and sends a poll notification if a poll handle is present. for (($i, $t) = (0, $idx); $i < $nr; $i++, $t = (($t + int(FSEL_FILES / $nr)) % FSEL_FILES)) { next if $fsel_cnt[$t] == FSEL_CNT_MAX; $fsel_cnt[$t]++; if ($fsel_poll_notify_mask & (1 << $t)) { printf("NOTIFY \%X\n", $t); my $ph = $fsel_poll_handle[$t]; notify_poll($ph); pollhandle_destroy($ph); $fsel_poll_notify_mask &= ~(1 << $t); $fsel_poll_handle[$t] = undef; } } $idx = ($idx + 1) % FSEL_FILES; if ($idx == 0) { # Cycle through 1, 2 and 4. $nr = ($nr * 2) % 7; } } sleep($tv); } } croak("Filesys::Fuse3 doesn't have poll") unless Filesys::Fuse3::fuse_version() >= 2.8; my %fuseargs = ( 'getattr' => 'main::fsel_getattr', 'readdir' => 'main::fsel_readdir', 'open' => 'main::fsel_open', 'release' => 'main::fsel_release', 'read' => 'main::fsel_read', 'poll' => 'main::fsel_poll', ); GetOptions( 'use-threads' => sub { print STDERR "Warning: Filesys::Fuse3 currently has bugs related to threading which may cause misbehavior\n"; $fuseargs{'threaded'} = 1; }, 'debug' => sub { $fuseargs{'debug'} = 1; } ) || croak("Malformed options passed"); $fuseargs{'mountpoint'} = $ARGV[0]; my $thread = threads->create(\&fsel_producer); Filesys::Fuse3::main(%fuseargs); $thread->kill('KILL'); $thread->join(); libfilesys-fuse3-perl-0.021/examples/fselclient.pl000077500000000000000000000014021516674026300221610ustar00rootroot00000000000000#!/usr/bin/env perl use strict; no strict qw(refs); use Carp; local $SIG{'__WARN__'} = \&Carp::cluck; use IO::Poll qw(POLLIN); use Fcntl; use constant FSEL_FILES => 16; my @fds; foreach my $i (0 .. (FSEL_FILES - 1)) { sysopen($fds[$i], $ARGV[0] . '/' . sprintf('%X', $i), O_RDONLY) or croak($!); } my $poll = new IO::Poll; foreach my $fd (@fds) { $poll->mask($fd, POLLIN); } while (1) { my $rc = $poll->poll(); croak($!) if $rc < 0; foreach my $i (0 .. (FSEL_FILES - 1)) { if (!$poll->events($fds[$i])) { print '_: '; next; } printf('%X:', $i); $rc = sysread($fds[$i], my $buf, 4096); croak($!) if !defined($rc); printf('%02d ', $rc); } print "\n"; } libfilesys-fuse3-perl-0.021/examples/loopback.pl000077500000000000000000000223141516674026300216300ustar00rootroot00000000000000#!/usr/bin/perl -w use strict; use Carp (); local $SIG{'__WARN__'} = \&Carp::cluck; my $has_threads = 0; eval { require threads; require threads::shared; 1; } and do { $has_threads = 1; threads->import(); threads::shared->import(); }; my $has_Filesys__Statvfs = 0; eval { require Filesys::Statvfs; 1; } and do { $has_Filesys__Statvfs = 1; Filesys::Statvfs->import(); }; my $use_lchown = 0; eval { require Lchown; 1; } and do { $use_lchown = 1; Lchown->import(); }; my $has_mknod = 0; eval { require Unix::Mknod; 1; } and do { $has_mknod = 1; Unix::Mknod->import(); }; use blib; use Filesys::Fuse3; use IO::File; use POSIX qw(ENOTDIR ENOENT ENOSYS EEXIST EPERM O_RDONLY O_RDWR O_APPEND O_CREAT setsid); use Fcntl qw(S_ISBLK S_ISCHR S_ISFIFO SEEK_SET S_ISREG S_ISFIFO S_IMODE S_ISCHR S_ISBLK S_ISSOCK); use Getopt::Long; my %extraopts = ( 'threaded' => 0, 'debug' => 0 ); my($use_real_statfs, $pidfile, $logfile); GetOptions( 'use-threads' => sub { if ($has_threads) { $extraopts{'threaded'} = 1; } }, 'debug' => sub { $extraopts{'debug'} = 1; }, 'use-real-statfs' => \$use_real_statfs, 'pidfile=s' => \$pidfile, 'logfile=s' => \$logfile, ) || die('Error parsing options'); sub fixup { return "/tmp/fusetest-" . $ENV{LOGNAME} . shift } sub x_getattr { my ($file) = fixup(shift); my (@list) = lstat($file); return -$! unless @list; return @list; } sub x_getdir { my ($dirname) = fixup(shift); unless(opendir(DIRHANDLE,$dirname)) { return -ENOENT(); } my (@files) = readdir(DIRHANDLE); closedir(DIRHANDLE); return (@files, 0); } # FUSE3 uses readdir instead of getdir sub x_readdir { x_getdir(@_) } sub x_open { my ($file) = fixup(shift); my ($mode) = shift; return -$! unless sysopen(FILE,$file,$mode); close(FILE); return 0; } sub x_release { my ($file) = fixup(shift); return 0; } sub x_read { my ($file,$bufsize,$off) = @_; my ($rv) = -ENOSYS(); my ($handle) = new IO::File; return -ENOENT() unless -e ($file = fixup($file)); my ($fsize) = -s $file; return -ENOSYS() unless open($handle,$file); if(seek($handle,$off,SEEK_SET)) { read($handle,$rv,$bufsize); } return $rv; } sub x_read_buf { my ($file, $size, $off, $bufvec) = @_; my $rv = 0; my ($handle) = new IO::File; return -ENOENT() unless -e ($file = fixup($file)); my ($fsize) = -s $file; return -ENOSYS() unless open($handle,$file); if(seek($handle,$off,SEEK_SET)) { $rv = $bufvec->[0]{'size'} = read($handle,$bufvec->[0]{'mem'},$size); } return $rv; } sub x_write { my ($file,$buf,$off) = @_; my ($rv); return -ENOENT() unless -e ($file = fixup($file)); my ($fsize) = -s $file; return -ENOSYS() unless open(FILE,'+<',$file); if($rv = seek(FILE,$off,SEEK_SET)) { $rv = print(FILE $buf); } $rv = -ENOSYS() unless $rv; close(FILE); return length($buf); } sub x_write_buf { my ($file,$off,$bufvec) = @_; my ($rv); return -ENOENT() unless -e ($file = fixup($file)); my ($fsize) = -s $file; return -ENOSYS() unless open(FILE,'+<',$file); # If by some chance we get a non-contiguous buffer, or an FD-based # buffer (or both!), then copy all of it into one contiguous buffer. if ($#$bufvec > 0 || $bufvec->[0]{flags} & &Filesys::Fuse3::FUSE_BUF_IS_FD()) { my $single = [ { flags => 0, fd => -1, mem => undef, pos => 0, size => Filesys::Fuse3::fuse_buf_size($bufvec), } ]; Filesys::Fuse3::fuse_buf_copy($single, $bufvec); $bufvec = $single; } if($rv = seek(FILE,$off,SEEK_SET)) { $rv = print(FILE $bufvec->[0]{mem}); } $rv = -ENOSYS() unless $rv; close(FILE); return $rv; } sub err { return (-shift || -$!) } sub x_readlink { return readlink(fixup(shift)); } sub x_unlink { return unlink(fixup(shift)) ? 0 : -$!; } sub x_symlink { print "symlink\n"; return symlink(shift,fixup(shift)) ? 0 : -$!; } sub x_rename { my ($old) = fixup(shift); my ($new) = fixup(shift); my ($err) = rename($old,$new) ? 0 : -ENOENT(); return $err; } sub x_link { return link(fixup(shift),fixup(shift)) ? 0 : -$! } sub x_chown { my ($fn) = fixup(shift); local $!; print "nonexistent $fn\n" unless -e $fn; my ($uid,$gid) = @_; if( $use_lchown ){ lchown($uid, $gid, $fn); }else{ chown($uid, $gid, $fn); } return -$!; } sub x_chmod { my ($fn) = fixup(shift); my ($mode) = shift; my ($err) = chmod($mode,$fn) ? 0 : -$!; return $err; } sub x_truncate { return truncate(fixup(shift),shift) ? 0 : -$! ; } sub x_utime { return utime($_[1],$_[2],fixup($_[0])) ? 0:-$!; } # FUSE3 uses utimens instead of utime; times arrive as floats (seconds since epoch) sub x_utimens { my ($file, $atime, $mtime) = @_; $file = fixup($file); return -ENOENT() unless -e $file; return utime(int($atime), int($mtime), $file) ? 0 : -$!; } sub x_mkdir { my ($name, $perm) = @_; return 0 if mkdir(fixup($name),$perm); return -$!; } sub x_rmdir { return 0 if rmdir fixup(shift); return -$!; } sub x_create { my ($file, $modes, $flags) = @_; printf(STDERR "x_create(): file: \"\%s\"; modes: \%o; flags: \%o\n", $file, $modes, $flags); $file = fixup($file); open(FILE, '>', $file) || return -$!; print FILE ''; close(FILE); chmod S_IMODE($modes), $file; return 0; } sub x_mknod { # since this is called for ALL files, not just devices, I'll do some checks # and possibly run the real mknod command. my ($file, $modes, $dev) = @_; $file = fixup($file); undef $!; if (S_ISREG($modes)) { open(FILE, '>', $file) || return -$!; print FILE ''; close(FILE); chmod S_IMODE($modes), $file; return 0; } elsif (S_ISFIFO($modes)) { my ($rv) = POSIX::mkfifo($file, S_IMODE($modes)); return $rv ? 0 : -POSIX::errno(); } elsif (S_ISCHR($modes) || S_ISBLK($modes)) { if($has_mknod){ Unix::Mknod::mknod($file, $modes, $dev); return -$!; }else{ return -POSIX::errno(); } } # S_ISSOCK maybe should be handled; however, for our test it should # not really matter. else { return -&ENOSYS; } return -$!; } # kludge sub x_statfs { if ($has_Filesys__Statvfs && $use_real_statfs) { (my($bsize, $frsize, $blocks, $bfree, $bavail, $files, $ffree, $favail, $flag, $namemax) = statvfs('/tmp')) || return -$!; return ($namemax, $files, $ffree, $blocks, $bavail, $bsize); } return 255,1000000,500000,1000000,500000,4096; } # Required for some edge cases where a simple fork() won't do. # from http://perldoc.perl.org/perlipc.html#Complete-Dissociation-of-Child -from-Parent sub daemonize { chdir("/") || die "can't chdir to /: $!"; open(STDIN, '<', '/dev/null') || die "can't read /dev/null: $!"; if ($logfile) { open(STDOUT, '>', $logfile) || die "can't open logfile: $!"; } else { open(STDOUT, '>', '/dev/null') || die "can't write to /dev/null: $!"; } defined(my $pid = fork()) || die "can't fork: $!"; exit if $pid; # non-zero now means I am the parent (setsid() != -1) || die "Can't start a new session: $!"; open(STDERR, '>&', \*STDOUT) || die "can't dup stdout: $!"; if ($pidfile) { open(PIDFILE, '>', $pidfile); print PIDFILE $$, "\n"; close(PIDFILE); } } my ($mountpoint) = ''; if (@ARGV){ $mountpoint = shift(@ARGV) } else { print <<'_EOT_'; Usage: loopback.pl [options] Options: --debug Turn on debugging (verbose) output --use-threads Use threads --use-real-statfs Use real stat command against /tmp or generic values --pidfile Create a file at the provided path containing PID --logfile Direct stdout/stderr to file instead of /dev/null _EOT_ exit; } if (! -d $mountpoint) { print STDERR "ERROR: attempted to mount to non-directory\n"; return -&ENOTDIR } daemonize(); Filesys::Fuse3::main( 'mountpoint' => $mountpoint, 'getattr' => 'main::x_getattr', 'readlink' => 'main::x_readlink', 'readdir' => 'main::x_readdir', 'create' => 'main::x_create', 'mknod' => 'main::x_mknod', 'mkdir' => 'main::x_mkdir', 'unlink' => 'main::x_unlink', 'rmdir' => 'main::x_rmdir', 'symlink' => 'main::x_symlink', 'rename' => 'main::x_rename', 'link' => 'main::x_link', 'chmod' => 'main::x_chmod', 'chown' => 'main::x_chown', 'truncate' => 'main::x_truncate', 'utimens' => 'main::x_utimens', 'open' => 'main::x_open', 'release' => 'main::x_release', 'read' => 'main::x_read', 'read_buf' => 'main::x_read_buf', 'write' => 'main::x_write', 'write_buf' => 'main::x_write_buf', 'statfs' => 'main::x_statfs', %extraopts, ); # vim: ts=4 ai et hls libfilesys-fuse3-perl-0.021/examples/readdir.pl000077500000000000000000000056711516674026300214570ustar00rootroot00000000000000#!/usr/bin/perl -w use strict; #use blib; use Filesys::Fuse3 qw(fuse_get_context); use POSIX qw(ENOENT EISDIR EINVAL); my (%files) = ( '.' => { type => 0040, mode => 0755, ctime => time()-1000 }, a => { cont => "File 'a'.\n", type => 0100, mode => 0755, ctime => time()-2000 }, b => { cont => "This is file 'b'.\n", type => 0100, mode => 0644, ctime => time()-1000 }, me => { size => 45, type => 0100, mode => 0644, ctime => time()-1000 }, ); sub filename_fixup { my ($file) = shift; $file =~ s,^/,,; $file = '.' unless length($file); return $file; } sub e_getattr { my ($file) = filename_fixup(shift); return -ENOENT() unless exists($files{$file}); my ($size) = exists($files{$file}{cont}) ? length($files{$file}{cont}) : 0; $size = $files{$file}{size} if exists $files{$file}{size}; my ($modes) = ($files{$file}{type}<<9) + $files{$file}{mode}; my ($dev, $ino, $rdev, $blocks, $gid, $uid, $nlink, $blksize) = (0,0,0,1,0,0,1,1024); my ($atime, $ctime, $mtime); $atime = $ctime = $mtime = $files{$file}{ctime}; # 2 possible types of return values: #return -ENOENT(); # or any other error you care to #print(join(",",($dev,$ino,$modes,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks)),"\n"); return ($dev,$ino,$modes,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks); } sub e_readdir { my ($path,$offset) = @_; print "readdir $path $offset\n"; my $stridelen = 9; my $last = $offset + $stridelen; my @a = keys %files; if ($last > $#a) { $last = $#a; } my @ents = map { [ $_, $a[$_] ] } $offset .. $last; return @ents, $offset < $#a ? 0 : -ENOENT(); } sub e_open { # VFS sanity check; it keeps all the necessary state, not much to do here. my ($file) = filename_fixup(shift); print("open called\n"); return -ENOENT() unless exists($files{$file}); return -EISDIR() if $files{$file}{type} & 0040; print("open ok\n"); return 0; } sub e_read { # return an error numeric, or binary/text string. (note: 0 means EOF, "0" will # give a byte (ascii "0") to the reading program) my ($file) = filename_fixup(shift); my ($buf,$off) = @_; return -ENOENT() unless exists($files{$file}); if(!exists($files{$file}{cont})) { return -EINVAL() if $off > 0; my $context = fuse_get_context(); return sprintf("pid=0x%08x uid=0x%08x gid=0x%08x\n",@$context{'pid','uid','gid'}); } return -EINVAL() if $off > length($files{$file}{cont}); return 0 if $off == length($files{$file}{cont}); return substr($files{$file}{cont},$off,$buf); } sub e_statfs { return 255, 1, 1, 1, 1, 2 } # If you run the script directly, it will run fusermount, which will in turn # re-run this script. Hence the funky semantics. my ($mountpoint) = ""; $mountpoint = shift(@ARGV) if @ARGV; Filesys::Fuse3::main( mountpoint=>$mountpoint, getattr=>"main::e_getattr", # getdir =>"main::e_getdir", readdir=>"main::e_readdir", open =>"main::e_open", statfs =>"main::e_statfs", read =>"main::e_read", threaded=>0 ); libfilesys-fuse3-perl-0.021/examples/rmount.pl000077500000000000000000000036021516674026300213610ustar00rootroot00000000000000#!/usr/bin/perl -w # This example needs some work before it can support threads. use strict; use Net::SSH 'sshopen2'; use IPC::Open2; use Filesys::Fuse3; use Data::Dumper; my $port; if($ARGV[-1]=~/^--/){ $port = pop(@ARGV); $port =~ s/--port=//; } my ($host, $dir, $mount) = @ARGV; if(!defined($mount)) { $mount = $dir; if($host =~ /^(.*):(.*)$/) { ($host,$dir) = ($1,$2); } else { die "usage: $0 user\@host remotedir mountpoint [--port=]\n". "or : $0 user\@host:remotedir mountpoint [--port=]\n"; } } `umount $mount` unless -d $mount; die "mountpoint $mount isn't a directory!\n" unless -d $mount; my (%args) = (mountpoint => $mount); map { my ($str) = $_; $args{$str} = sub { netlink($str,@_) } } qw(getattr getdir open read write readlink unlink rmdir symlink rename link chown chmod truncate utime mkdir rmdir mknod statfs); sub connect_remote { push(@Net::SSH::ssh_options, "-p $port") if $port; sshopen2($host, *READER, *WRITER, "./rmount_remote.pl $dir") or die "ssh: $!\n"; select WRITER; $| = 1; select STDOUT; } $SIG{CHLD} = sub { use POSIX ":sys_wait_h"; my $kid; do { $kid = waitpid(-1,WNOHANG); } until $kid < 1; }; connect_remote; sub netlink { my ($str) = Dumper(\@_)."\n"; $str = sprintf("%08i\n%s",length($str),$str); while(1) { # retry as necessary my ($VAR1); $VAR1 = undef; eval { local $SIG{ALRM} = sub { die "timeout\n" }; alarm 10; print WRITER $str; my ($len, $data); $data = ''; if(sysread(READER,$len,9) == 9) { sysread(READER,$data,$len-length($data),length($data)) while(length($data) < $len); eval $data; } alarm 0; }; if(defined $VAR1) { return wantarray ? @{$VAR1} : $$VAR1[0]; } print STDERR "failed to send command; reconnecting ssh\n"; close(READER); close(WRITER); connect_remote(); } } Filesys::Fuse3::main(%args); netlink("bye"); close(READER); close(WRITER); libfilesys-fuse3-perl-0.021/examples/rmount_remote.pl000077500000000000000000000071131516674026300227350ustar00rootroot00000000000000#!/usr/bin/perl use strict; use IO::File; use POSIX qw(ENOENT ENOSYS EEXIST EPERM O_RDONLY O_RDWR O_APPEND O_CREAT); use Fcntl qw(S_ISBLK S_ISCHR S_ISFIFO SEEK_SET); use Data::Dumper; require 'syscall.ph'; # for SYS_mknod and SYS_lchown my ($rootdir) = @ARGV; # strip leading and trailing slashes $rootdir = $1 if($rootdir =~ /^\/?(.*)\/?$/); sub fixup { return "/$rootdir" . shift } sub x_getattr { my ($file) = fixup(shift); my (@list) = lstat($file); return -$! unless @list; return @list; } sub x_getdir { my ($dirname) = fixup(shift); unless(opendir(DIRHANDLE,$dirname)) { return -ENOENT(); } my (@files) = readdir(DIRHANDLE); closedir(DIRHANDLE); return (@files, 0); } sub x_open { my ($file) = fixup(shift); my ($mode) = shift; return -$! unless sysopen(FILE,$file,$mode); close(FILE); return 0; } sub x_read { my ($file,$bufsize,$off) = @_; my ($rv) = -ENOSYS(); my ($handle) = new IO::File; return -ENOENT() unless -e ($file = fixup($file)); my ($fsize) = -s $file; return -ENOSYS() unless open($handle,$file); if(seek($handle,$off,SEEK_SET)) { read($handle,$rv,$bufsize); } return $rv; } sub x_write { my ($file,$buf,$off) = @_; my ($rv); return -ENOENT() unless -e ($file = fixup($file)); my ($fsize) = -s $file; return -ENOSYS() unless open(FILE,'+<',$file); if($rv = seek(FILE,$off,SEEK_SET)) { $rv = print(FILE $buf); } $rv = -ENOSYS() unless $rv; close(FILE); return length($buf); } sub err { return (-shift || -$!) } sub x_readlink { return readlink(fixup(shift) ); } sub x_unlink { return unlink(fixup(shift)) ? 0 : -$!; } sub x_rmdir { return err(rmdir(fixup(shift)) ); } sub x_symlink { print "symlink\n"; return symlink(shift,fixup(shift)) ? 0 : -$!; } sub x_rename { my ($old) = fixup(shift); my ($new) = fixup(shift); my ($err) = rename($old,$new) ? 0 : -ENOENT(); return $err; } sub x_link { return link(fixup(shift),fixup(shift)) ? 0 : -$! } sub x_chown { my ($fn) = fixup(shift); print "nonexistent $fn\n" unless -e $fn; my ($uid,$gid) = @_; # perl's chown() does not chown symlinks, it chowns the symlink's # target. it fails when the link's target doesn't exist, because # the stat64() syscall fails. # this causes error messages when unpacking symlinks in tarballs. my ($err) = syscall(&SYS_lchown,$fn,$uid,$gid,$fn) ? -$! : 0; return $err; } sub x_chmod { my ($fn) = fixup(shift); my ($mode) = shift; my ($err) = chmod($mode,$fn) ? 0 : -$!; return $err; } sub x_truncate { return truncate(fixup(shift),shift) ? 0 : -$! ; } sub x_utime { return utime($_[1],$_[2],fixup($_[0])) ? 0:-$!; } sub x_mkdir { my ($name, $perm) = @_; return 0 if mkdir(fixup($name),$perm); return -$!; } sub x_rmdir { return 0 if rmdir fixup(shift); return -$!; } sub x_mknod { # since this is called for ALL files, not just devices, I'll do some checks # and possibly run the real mknod command. my ($file, $modes, $dev) = @_; $file = fixup($file); $! = 0; syscall(&SYS_mknod,$file,$modes,$dev); return -$!; } # kludge sub x_statfs {return 255,1000000,500000,1000000,500000,4096} $| = 1; my ($len); while(read(STDIN,$len,9) == 9) { chomp $len; my ($data,$VAR1,@args); eval { $SIG{ALRM} = sub { die "timeout\n"}; $data = ""; alarm 5; read(STDIN,$data,$len-length($data),length($data)) while(length($data) < $len); alarm 0; }; die $@ if $@; eval $data; @args = @{$VAR1}; my $cmd = shift(@args); exit 0 if $cmd eq "bye"; die "cannot find command $cmd\n" unless exists($main::{"x_$cmd"}); @args = $main::{"x_$cmd"}(@args); $cmd = Dumper(\@args)."\n"; $cmd = sprintf("%08i\n%s",length($cmd),$cmd); print $cmd; } libfilesys-fuse3-perl-0.021/test.pl000066400000000000000000000003751516674026300171770ustar00rootroot00000000000000#!/usr/bin/perl BEGIN { $ENV{HARNESS_IGNORE_EXITCODE} = 1; } use Test::Harness qw(&runtests $verbose); $verbose=0; die "cannot find test directory!" unless -d "test"; my (@files) = ; runtests("test/s/mount.t",sort(@files),"test/s/umount.t"); libfilesys-fuse3-perl-0.021/test/000077500000000000000000000000001516674026300166355ustar00rootroot00000000000000libfilesys-fuse3-perl-0.021/test/chmod.t000066400000000000000000000005031516674026300201120ustar00rootroot00000000000000#!/usr/bin/perl use test::helper qw($_real $_point); use Test::More; plan tests => 4; chdir($_point); open($file, '>', 'file'); print $file "frog\n"; close($file); ok(chmod(0644,"file"),"set unexecutable"); ok(!-x "file","unexecutable"); ok(chmod(0755,"file"),"set executable"); ok(-x "file","executable"); unlink("file"); libfilesys-fuse3-perl-0.021/test/chown.t000066400000000000000000000007441516674026300201450ustar00rootroot00000000000000#!/usr/bin/perl use test::helper qw($_real $_point); use Test::More; use English; plan tests => 4; my (@stat); chdir($_point); open($file, '>', 'file'); print $file "frog\n"; close($file); SKIP: { skip('Need root to give away ownership', 4) unless ($UID == 0); ok(chown(0,0,"file"),"set 0,0"); @stat = stat("file"); ok($stat[4] == 0 && $stat[5] == 0,"0,0"); ok(chown(1,1,"file"),"set 1,1"); @stat = stat("file"); ok($stat[4] == 1 && $stat[5] == 1,"1,1"); } unlink("file"); libfilesys-fuse3-perl-0.021/test/getattr.t000066400000000000000000000032111516674026300204710ustar00rootroot00000000000000#!/usr/bin/perl use test::helper qw($_real $_point); use Test::More; use Data::Dumper; plan tests => 203; $scale = 1024 * 1024 * 1024; if ($^O eq 'darwin') { $scale = 1024 * 1024; } sub test_file { my $size = shift; my ($a, $b) = ("$_real/wibble-$size","$_point/wibble-$size"); # diag "test $size Gb file"; open(my $fh, '>', $a) || die "can't open $b: $!"; seek($fh, $size * $scale, 0); print $fh ' '; close($fh); # diag "size $b = ",-s $b, " $a = ", -s $a; is(-A "$a", -A "$b", '-A'); # 1 is(-B "$a", -B "$b", '-B'); # 2 is(-C "$a", -C "$b", '-C'); # 3 is(-M "$a", -M "$b", '-M'); # 4 is(-O "$a", -O "$b", '-O'); # 5 is(-R "$a", -R "$b", '-R'); # 6 is(-S "$a", -S "$b", '-S'); # 7 is(-T "$a", -T "$b", '-T'); # 8 is(-W "$a", -W "$b", '-W'); # 9 is(-X "$a", -X "$b", '-X'); # 10 is(-b "$a", -b "$b", '-b'); # 11 is(-c "$a", -c "$b", '-c'); # 12 is(-d "$a", -d "$b", '-d'); # 13 is(-e "$a", -e "$b", '-e'); # 14 is(-f "$a", -f "$b", '-f'); # 15 is(-g "$a", -g "$b", '-g'); # 16 is(-k "$a", -k "$b", '-k'); # 17 is(-l "$a", -l "$b", '-l'); # 18 is(-o "$a", -o "$b", '-o'); # 19 is(-p "$a", -p "$b", '-p'); # 20 is(-r "$a", -r "$b", '-r'); # 21 is(-s "$a", -s "$b", '-s'); # 22 is(-t "$a", -t "$b", '-t'); # 23 is(-u "$a", -u "$b", '-u'); # 24 is(-w "$a", -w "$b", '-w'); # 25 is(-x "$a", -x "$b", '-x'); # 26 is(-z "$a", -z "$b", '-z'); # 27 my (@astat, @bstat); @astat = stat("$a"); @bstat = stat("$b"); # dev, inode and blksize can legally change @astat = @astat[2..10,12]; @bstat = @bstat[2..10,12]; is(join(" ",@astat),join(" ",@bstat),"stat()"); ok( unlink($a), 'unlink' ); } test_file( $_ ) foreach ( 1, 2, 4, 8, 16, 32, 64 ); libfilesys-fuse3-perl-0.021/test/getdir.t000066400000000000000000000016271516674026300203060ustar00rootroot00000000000000#!/usr/bin/perl use test::helper qw($_real $_point); use Test::More; my (@names) = qw(abc def ghi jkl mno pqr stu jlk sfdaljk sdfakjlsdfa kjldsf kjl;sdf akjl;asdf klj;asdf lkjsdflkjsdfkjlsdfakjsdfakjlsadfkjl;asdfklj;asdfkjl;asdfklj;asdfkjl;asdfkjlasdflkj;sadf); @names = sort(@names); plan tests => 2 * scalar @names; chdir($_real); # create entries foreach $fname (@names) { open($file, '>', $fname); close($file); } # make sure they exist in real dir opendir(REAL,$_real); my (@ents) = readdir(REAL); closedir(REAL); @ents = sort(@ents); map { shift(@ents) while($ents[0] eq '.' || $ents[0] eq '..'); is(shift(@ents),$_,"ent $_") } @names; # make sure they exist in fuse dir opendir(POINT,$_point); @ents = readdir(POINT); closedir(POINT); @ents = sort(@ents); map { shift(@ents) while($ents[0] eq '.' || $ents[0] eq '..'); is(shift(@ents),$_,"ent $_") } @names; # remove them map { unlink } @names; libfilesys-fuse3-perl-0.021/test/helper.pm000066400000000000000000000022021516674026300204460ustar00rootroot00000000000000#!/usr/bin/perl package # avoid cpan indexing test::helper; use strict; use Exporter; use Config; use POSIX qw(WEXITSTATUS); our ($VERSION, @ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS); @ISA = "Exporter"; @EXPORT_OK = qw($_loop $_opts $_point $_pidfile $_real $_logfile); my $tmp = -d '/private' ? '/private/tmp' : '/tmp'; our $_loop = 'examples/loopback.pl'; our $_point = "$tmp/fusemnt-".$ENV{LOGNAME}; our $_pidfile = $ENV{'PWD'} . "/test/s/mounted.pid"; our $_real = "$tmp/fusetest-".$ENV{LOGNAME}; our $_opts = ''; our $_logfile = "$tmp/fusemnt.log"; $_opts = ' --pidfile ' . $_pidfile; $_opts .= ' --logfile ' . $_logfile; $_opts .= $Config{useithreads} ? ' --use-threads' : ''; if($0 !~ qr|s/u?mount\.t$|) { my ($reject) = 1; if(open my $fh, '<', $_pidfile) { my $pid = do {local $/ = undef; <$fh>}; close $fh; if(kill 0, $pid) { my $pattern = $^O eq 'solaris' ? qr{^$_point on }m : qr{on (?:/private)?$_point }; if(`mount` =~ $pattern) { $reject = 0; } else { kill 1, $pid; } } } system("ls $_point >/dev/null"); $reject = 1 if (POSIX::WEXITSTATUS($?)); die "not properly mounted\n" if $reject; } 1; libfilesys-fuse3-perl-0.021/test/link.t000066400000000000000000000007421516674026300177620ustar00rootroot00000000000000#!/usr/bin/perl use test::helper qw($_real $_point); use Test::More; plan tests => 8; chdir($_point); open($file, '>', 'womble'); print $file "hippity\n"; close($file); ok(-f "womble","exists"); ok(!-f "rabbit","target file doesn't exist"); is(-s "womble",8,"right size"); ok(link("womble","rabbit"),"link"); ok(-f "womble","old file exists"); ok(-f "rabbit","target file exists"); is(-s "womble",8,"right size"); is(-s "rabbit",8,"right size"); unlink("womble"); unlink("rabbit"); libfilesys-fuse3-perl-0.021/test/mkdir.t000066400000000000000000000003521516674026300201300ustar00rootroot00000000000000#!/usr/bin/perl use test::helper qw($_real $_point); use Test::More; plan tests => 3; chdir($_point); ok(mkdir("dir"),"mkdir"); ok(-d "dir","dir exists"); chdir($_real); ok(-d "dir","dir really exists"); chdir($_point); rmdir("dir"); libfilesys-fuse3-perl-0.021/test/mknod.t000066400000000000000000000032361516674026300201360ustar00rootroot00000000000000#!/usr/bin/perl use test::helper qw($_real $_point); use Test::More; plan tests => 24; use English; use Unix::Mknod qw(:all); use Fcntl qw(S_IFCHR S_IFBLK); use POSIX; my (@stat); chdir($_point); ok(open($file, '>', 'reg'),"create normal file"); close($file); ok(defined mkfifo($_point.'/fifo', 0600),"create fifo"); chdir($_real); ok(-e "reg" ,"normal file exists"); ok(-e "fifo","fifo exists"); ok(-f "reg" ,"normal file is normal file"); ok(-p "fifo","fifo is fifo"); SKIP: { skip('Need root to mknod devices', 8) unless ($UID == 0); chdir($_point); ok(!mknod($_point.'/chr', 0600|S_IFCHR, makedev(2,3)),"create chrdev"); ok(!mknod($_point.'/blk', 0600|S_IFBLK, makedev(2,3)),"create blkdev"); chdir($_real); ok(-e "chr" ,"chrdev exists"); ok(-e "blk" ,"blkdev exists"); skip('mknod() is just pretend under fakeroot(1)', 4) if exists $ENV{FAKEROOTKEY}; ok(-c "chr" ,"chrdev is chrdev"); ok(-b "blk" ,"blkdev is blkdev"); @stat = stat("chr"); is($stat[6],makedev(2,3),"chrdev has right major,minor"); @stat = stat("blk"); is($stat[6],makedev(2,3),"blkdev has right major,minor"); } chdir($_point); ok(-e "reg" ,"normal file exists"); ok(-e "fifo","fifo exists"); ok(-f "reg" ,"normal file is normal file"); ok(-p "fifo","fifo is fifo"); SKIP: { skip('Need root to mknod devices', 6) unless ($UID == 0); ok(-e "chr" ,"chrdev exists"); ok(-e "blk" ,"blkdev exists"); ok(-c "chr" ,"chrdev is chrdev"); ok(-b "blk" ,"blkdev is blkdev"); @stat = stat("chr"); is($stat[6],makedev(2,3),"chrdev has right major,minor"); @stat = stat("blk"); is($stat[6],makedev(2,3),"blkdev has right major,minor"); } map { unlink } qw(reg chr blk fifo); libfilesys-fuse3-perl-0.021/test/open.t000066400000000000000000000003561516674026300177670ustar00rootroot00000000000000#!/usr/bin/perl use test::helper qw($_real $_point); use Test::More; plan tests => 1; chdir($_real); open($file, '>', 'file'); print $file "frog\n"; close($file); chdir($_point); ok(open(FILE,"file"),"open"); close(FILE); unlink("file"); libfilesys-fuse3-perl-0.021/test/pod.t000066400000000000000000000002561516674026300176070ustar00rootroot00000000000000#!/usr/bin/perl use strict; use warnings; use Test::More; eval "use Test::Pod 1.00"; plan skip_all => "Test::Pod 1.00 required for testing POD" if $@; all_pod_files_ok(); libfilesys-fuse3-perl-0.021/test/read.t000066400000000000000000000005211516674026300177330ustar00rootroot00000000000000#!/usr/bin/perl use test::helper qw($_real $_point); use Test::More; plan tests => 3; chdir($_real); open($file, '>','file'); print $file "frog\n"; close($file); chdir($_point); ok(open(FILE,"file"),"open"); my ($data) = ; close(FILE); is(length($data),5,"right amount read"); is($data,"frog\n","right data read"); unlink("file"); libfilesys-fuse3-perl-0.021/test/readlink.t000066400000000000000000000004541516674026300206160ustar00rootroot00000000000000#!/usr/bin/perl use test::helper qw($_point $_real); use Test::More; plan tests => 4; chdir($_real); ok(symlink("abc","def"),"OS supports symlinks"); is(readlink("def"),"abc","OS supports symlinks"); chdir($_point); ok(-l "def","symlink exists"); is(readlink("def"),"abc","readlink"); unlink("def"); libfilesys-fuse3-perl-0.021/test/rename.t000066400000000000000000000005541516674026300202750ustar00rootroot00000000000000#!/usr/bin/perl use test::helper qw($_real $_point); use Test::More; plan tests => 5; chdir($_point); open($file, '>', 'frog'); print $file "hippity\n"; close($file); ok(-f "frog","exists"); ok(!-f "toad","target file doesn't exist"); ok(rename("frog","toad"),"rename"); ok(!-f "frog","old file doesn't exist"); ok(-f "toad","target file exists"); unlink("toad"); libfilesys-fuse3-perl-0.021/test/rmdir.t000066400000000000000000000004541516674026300201420ustar00rootroot00000000000000#!/usr/bin/perl use test::helper qw($_real $_point); use Test::More; plan tests => 5; chdir($_real); ok(mkdir("dir"),"mkdir"); ok(-d "dir","dir really exists"); chdir($_point); ok(-d "dir","dir exists"); rmdir("dir"); ok(! -d "dir","dir removed"); chdir($_real); ok(! -d "dir","dir really removed"); libfilesys-fuse3-perl-0.021/test/s/000077500000000000000000000000001516674026300170775ustar00rootroot00000000000000libfilesys-fuse3-perl-0.021/test/s/mount.t000066400000000000000000000024721516674026300204330ustar00rootroot00000000000000#!/usr/bin/perl -w use test::helper qw($_point $_loop $_opts $_real $_pidfile $_logfile); use strict; use Errno qw(:POSIX); use Test::More tests => 3; sub is_mounted { my $diag = -e '/proc/mounts' ? `cat /proc/mounts` : ($^O eq 'linux' ? `/bin/mount` : ($^O eq 'solaris' ? `/usr/sbin/mount` : `/sbin/mount`)); my $pattern = $^O eq 'solaris' ? qr{^$_point }m : qr{ (?:/private)?$_point }; return $diag =~ $pattern; } ok(!is_mounted(),"not already mounted"); ok(-f $_loop,"loopback exists"); mkdir $_point; mkdir $_real; diag "mounting $_loop to $_point with $_opts"; open REALSTDOUT, '>&STDOUT'; open REALSTDERR, '>&STDERR'; open STDOUT, '>', '/dev/null'; open STDERR, '>&', \*STDOUT; system("perl -Iblib/lib -Iblib/arch $_loop $_opts $_point"); open STDOUT, '>&', \*REALSTDOUT; open STDERR, '>&', \*REALSTDERR; my ($success, $count) = (0,0); while ($count++ < 50 && !$success) { select(undef, undef, undef, 0.1); ($success) = is_mounted(); } ok( $success, "mount succeeded" ); system("rm -rf $_real"); if ($success) { diag "mounted in " . $count/10 . " secs"; mkdir($_real); } else { if (-e $_logfile) { my $errors = `cat $_logfile`; diag "error mounting $_loop:\n$errors"; unlink $_logfile; } if (-e $_pidfile) { kill('INT',`cat $_pidfile`); unlink($_pidfile); } } libfilesys-fuse3-perl-0.021/test/s/umount.t000066400000000000000000000004771516674026300206230ustar00rootroot00000000000000#!/usr/bin/perl use test::helper qw($_point $_real $_pidfile); use strict; use Test::More tests => 1; use POSIX qw(WEXITSTATUS); system("fusermount -u $_point"); if(POSIX::WEXITSTATUS($?) != 0) { system("umount $_point"); } ok(POSIX::WEXITSTATUS($?) == 0,"unmount"); system("rm -rf $_real $_pidfile"); rmdir($_point); libfilesys-fuse3-perl-0.021/test/statfs.t000066400000000000000000000012671516674026300203340ustar00rootroot00000000000000#!/usr/bin/perl use test::helper qw($_real $_point); use Test::More; use Config; use Filesys::Statvfs; if ($^O eq 'netbsd') { # Ignoring the f_namelen field; NetBSD's statvfs1(2) syscall doesn't # seem to handle f_namelen right for PUFFS-based filesystems. Not our # failure, and mostly irrelevant. plan tests => 6; } else { plan tests => 7; } ok(my @list = (statvfs($_point))[1,2,3,5,6,9]); diag "statfs: ",join(', ', @list); is(shift(@list),4096,'block size'); is(shift(@list),1000000,'blocks'); is(shift(@list),500000,'blocks free'); is(shift(@list),1000000,'files'); is(shift(@list),500000,'files free'); unless ($^O eq 'netbsd') { is(shift(@list),255,'namelen'); } libfilesys-fuse3-perl-0.021/test/symlink.t000066400000000000000000000013371516674026300205140ustar00rootroot00000000000000#!/usr/bin/perl use test::helper qw($_point $_real); use Test::More; plan tests => 6; chdir($_point); ok(symlink("abc","def"),"symlink created"); ok(-l "def","symlink exists"); is(readlink("def"),"abc","it worked"); chdir($_real); ok(-l "def","symlink really exists"); is(readlink("def"),"abc","really worked"); unlink("def"); # bug: doing a 'cp -a' on a directory which contains a symlink # reports an error mkdir("dira"); open($file, '>', 'dira/filea'); close($file); symlink('filea', 'dira/fileb'); my $cp = 'cp -a'; if ($^O eq 'netbsd' || $^O eq 'openbsd') { $cp = 'cp -R'; } is(system($cp . " dira dirb")>>8,0,$cp); map { unlink($_) } ('dira/filea', 'dira/fileb', 'dirb/filea', 'dirb/fileb'); map { rmdir($_) } ('dira', 'dirb'); libfilesys-fuse3-perl-0.021/test/test-template000066400000000000000000000001351516674026300213470ustar00rootroot00000000000000#!/usr/bin/perl use test::helper qw($_real $_point); use Test::More; plan tests => 1; ok(1); libfilesys-fuse3-perl-0.021/test/truncate.t000066400000000000000000000005311516674026300206460ustar00rootroot00000000000000#!/usr/bin/perl use test::helper qw($_real $_point); use Test::More; plan tests => 5; chdir($_point); open($file, '>', 'womble'); print $file "hippity\n"; close($file); ok(-f "womble","exists"); is(-s "womble",8,"right size"); ok(truncate("womble",4),"truncate"); ok(-f "womble","file exists"); is(-s "womble",4,"right size"); unlink("womble"); libfilesys-fuse3-perl-0.021/test/unlink.t000066400000000000000000000005261516674026300203250ustar00rootroot00000000000000#!/usr/bin/perl use test::helper qw($_real $_point); use Test::More; plan tests => 4; chdir($_point); open($file, '>', 'file'); close($file); ok(-f "file","file exists"); chdir($_real); ok(-f "file","file really exists"); chdir($_point); unlink("file"); ok(! -f "file","file unlinked"); chdir($_real); ok(! -f "file","file really unlinked"); libfilesys-fuse3-perl-0.021/test/utime.t000066400000000000000000000004711516674026300201470ustar00rootroot00000000000000#!/usr/bin/perl use test::helper qw($_real $_point); use Test::More; plan tests => 3; my (@stat); chdir($_real); open($file, '>', 'file'); print($file "frog\n"); close($file); chdir($_point); ok(utime(1,2,"file"),"set utime"); @stat = stat("file"); is($stat[8],1,"atime"); is($stat[9],2,"mtime"); unlink("file"); libfilesys-fuse3-perl-0.021/test/write.t000066400000000000000000000025071516674026300201600ustar00rootroot00000000000000#!/usr/bin/perl use test::helper qw($_real $_point); use Test::More; plan tests => 20; my ($data); chdir($_point); undef $/; # slurp it all # create file open($file, '>', 'writefile'); print $file "frogbing\n"; close($file); # fetch contents of file ok(open(FILE,"writefile"),"open"); $data = ; close(FILE); is(length($data),9,"right amount read"); is($data,"frogbing\n","right data read"); # overwrite part ok(open(FILE,'+<',"writefile"),"open"); ok(seek(FILE,2,0),"seek"); ok(print(FILE "ib"),"print"); close(FILE); # fetch contents of file ok(open(FILE,"writefile"),"open"); $data = ; close(FILE); is(length($data),9,"right amount read"); is($data,"fribbing\n","right data read"); # overwrite part, append some ok(open(FILE,'+<',"writefile"),"open"); ok(seek(FILE,7,0),"seek"); ok(print(FILE "gle"),"print"); close(FILE); # fetch contents of file ok(open(FILE,"writefile"),"open"); $data = ; close(FILE); is(length($data),10,"right amount read"); is($data,"fribbingle","right data read"); # overwrite file with shorter data ok(open(FILE,'>',"writefile"),"open write"); ok(print(FILE "foobar"),"print"); close(FILE); # fetch contents of file ok(open(FILE,"writefile"),"open read"); $data = ; close(FILE); is(length($data),6,"right amount read"); is($data,"foobar","right data read"); # kill file unlink("writefile");