zfs_vnops.c revision c8c24165eb6f3690a1b5b8877619e069efe5199b
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
*/
/* Portions Copyright 2007 Jeremy Teo */
/* Portions Copyright 2010 Robert Milkowski */
#include <sys/sysmacros.h>
#include <sys/resource.h>
#include <sys/vfs_opreg.h>
#include <sys/pathname.h>
#include <sys/zfs_ioctl.h>
#include <sys/dmu_objset.h>
#include <sys/zfs_ctldir.h>
#include <sys/zfs_fuid.h>
#include <sys/zfs_rlock.h>
#include <sys/extdirent.h>
/*
* Programming rules.
*
* Each vnode op performs some logical unit of work. To do this, the ZPL must
* properly lock its in-core state, create a DMU transaction, do the work,
* record this work in the intent log (ZIL), commit the DMU transaction,
* and wait for the intent log to commit if it is a synchronous operation.
* Moreover, the vnode ops must work in both normal and log replay context.
* The ordering of events is important to avoid deadlocks and references
* to freed memory. The example below illustrates the following Big Rules:
*
* (1) A check must be made in each zfs thread for a mounted file system.
* This is done avoiding races using ZFS_ENTER(zfsvfs).
* A ZFS_EXIT(zfsvfs) is needed before all returns. Any znodes
* must be checked with ZFS_VERIFY_ZP(zp). Both of these macros
* can return EIO from the calling function.
*
* (2) VN_RELE() should always be the last thing except for zil_commit()
* (if necessary) and ZFS_EXIT(). This is for 3 reasons:
* can be freed, so the zp may point to freed memory. Second, the last
* reference will call zfs_zinactive(), which may induce a lot of work --
* pushing cached pages (which acquires range locks) and syncing out
* cached atime changes. Third, zfs_zinactive() may require a new tx,
* which could deadlock the system if you were already holding one.
* If you must call VN_RELE() within a tx then use VN_RELE_ASYNC().
*
* (3) All range locks must be grabbed before calling dmu_tx_assign(),
* as they can span dmu_tx_assign() calls.
*
* (4) Always pass TXG_NOWAIT as the second argument to dmu_tx_assign().
* This is critical because we don't want to block while holding locks.
* Note, in particular, that if a lock is sometimes acquired before
* the tx assigns, and sometimes after (e.g. z_lock), then failing to
* use a non-blocking assign can deadlock the system. The scenario:
*
* Thread A has grabbed a lock before calling dmu_tx_assign().
* Thread B is in an already-assigned tx, and blocks for this lock.
* Thread A calls dmu_tx_assign(TXG_WAIT) and blocks in txg_wait_open()
* forever, because the previous txg can't quiesce until B's tx commits.
*
* If dmu_tx_assign() returns ERESTART and zfsvfs->z_assign is TXG_NOWAIT,
* then drop all locks, call dmu_tx_wait(), and try again.
*
* (5) If the operation succeeded, generate the intent log entry for it
* before dropping locks. This ensures that the ordering of events
* in the intent log matches the order in which they actually occurred.
* During ZIL replay the zfs_log_* functions will update the sequence
* number to indicate the zil transaction has replayed.
*
* (6) At the end of each vnode op, the DMU tx must always commit,
* regardless of whether there were any errors.
*
* (7) After dropping all locks, invoke zil_commit(zilog, seq, foid)
* to ensure that synchronous semantics are provided when necessary.
*
* In general, this is how things should be ordered in each vnode op:
*
* ZFS_ENTER(zfsvfs); // exit if unmounted
* top:
* zfs_dirent_lock(&dl, ...) // lock directory entry (may VN_HOLD())
* rw_enter(...); // grab any other locks you need
* tx = dmu_tx_create(...); // get DMU tx
* dmu_tx_hold_*(); // hold each object you might modify
* error = dmu_tx_assign(tx, TXG_NOWAIT); // try to assign
* if (error) {
* rw_exit(...); // drop locks
* zfs_dirent_unlock(dl); // unlock directory entry
* VN_RELE(...); // release held vnodes
* if (error == ERESTART) {
* dmu_tx_wait(tx);
* dmu_tx_abort(tx);
* goto top;
* }
* dmu_tx_abort(tx); // abort DMU tx
* ZFS_EXIT(zfsvfs); // finished in zfs
* return (error); // really out of space
* }
* error = do_real_work(); // do whatever this VOP does
* if (error == 0)
* zfs_log_*(...); // on success, make ZIL entry
* dmu_tx_commit(tx); // commit DMU tx -- error or not
* rw_exit(...); // drop locks
* zfs_dirent_unlock(dl); // unlock directory entry
* VN_RELE(...); // release held vnodes
* zil_commit(zilog, seq, foid); // synchronous when necessary
* ZFS_EXIT(zfsvfs); // finished in zfs
* return (error); // done, report error
*/
/* ARGSUSED */
static int
{
return (EPERM);
}
return (EACCES);
}
}
/* Keep a count of the synchronous opens in the znode */
return (0);
}
/* ARGSUSED */
static int
{
/*
* Clean up any locks held by this process on the vp.
*/
/* Decrement the synchronous opens in the znode */
return (0);
}
/*
* Lseek support for finding holes (cmd == _FIO_SEEK_HOLE) and
*/
static int
{
int error;
return (ENXIO);
}
if (cmd == _FIO_SEEK_HOLE)
else
/* end of file? */
/*
* Handle the virtual hole at the end of file.
*/
if (hole) {
return (0);
}
return (ENXIO);
}
return (error);
return (error);
}
/* ARGSUSED */
static int
{
int error;
switch (com) {
case _FIOFFS:
/*
* The following two ioctls are used by bfu. Faking out,
* necessary to avoid bfu errors.
*/
case _FIOGDIO:
case _FIOSDIO:
return (0);
case _FIO_SEEK_DATA:
case _FIO_SEEK_HOLE:
return (EFAULT);
if (error)
return (error);
return (EFAULT);
return (0);
}
return (ENOTTY);
}
/*
* Utility functions to map and unmap a single physical page. These
* are used to manage the mappable copies of ZFS file data, and therefore
*/
{
if (kpm_enable)
return (hat_kpm_mapin(pp, 0));
(caddr_t)-1));
}
void
{
if (kpm_enable) {
} else {
}
}
/*
* When a file is memory mapped, we must keep the IO data synchronized
* between the DMU cache and the memory mapped pages. What this means:
*
* On Write: If we find a memory mapped page, we write to *both*
* the page and the dmu buffer.
*/
static void
{
}
off = 0;
}
}
/*
* When a file is memory mapped, we must keep the IO data synchronized
* between the DMU cache and the memory mapped pages. What this means:
*
* On Read: We "read" preferentially from memory mapped pages,
* else we default from the dmu buffer.
*
* NOTE: We will always "break up" the IO into PAGESIZE uiomoves when
* the file is memory mapped.
*/
static int
{
int error = 0;
} else {
}
off = 0;
if (error)
break;
}
return (error);
}
/*
* Read bytes from specified file into supplied buffer.
*
* IN: vp - vnode of file to be read from.
* uio - structure supplying read location, range info,
* and return buffer.
* ioflag - SYNC flags; used to provide FRSYNC semantics.
* cr - credentials of caller.
* ct - caller context
*
* OUT: uio - updated offset and range, buffer filled.
*
* RETURN: 0 if success
* error code if failure
*
* Side Effects:
* vp - atime updated if byte count > 0
*/
/* ARGSUSED */
static int
{
int error;
return (EACCES);
}
/*
* Validate file offset
*/
return (EINVAL);
}
/*
* Fasttrack empty reads
*/
return (0);
}
/*
* Check for mandatory locks
*/
return (error);
}
}
/*
* If we're in FRSYNC mode, sync out this znode before reading it.
*/
/*
* Lock the range against changes.
*/
/*
* If we are reading past end-of-file we can skip
* to the end; but we might still need to set atime.
*/
error = 0;
goto out;
}
int nblk;
} else {
nblk = 1;
}
if (vn_has_cached_data(vp)) {
/*
* For simplicity, we always allocate a full buffer
* even if we only expect to read a portion of a block.
*/
while (--nblk >= 0) {
(void) dmu_xuio_add(xuio,
}
}
}
while (n > 0) {
if (vn_has_cached_data(vp))
else
if (error) {
/* convert checksum errors into IO errors */
break;
}
n -= nbytes;
}
out:
return (error);
}
/*
* Write the bytes to a file.
*
* IN: vp - vnode of file to be written to.
* uio - structure supplying write location, range info,
* and data buffer.
* ioflag - FAPPEND flag set if in append mode.
* cr - credentials of caller.
*
* OUT: uio - updated offset and range.
*
* RETURN: 0 if success
* error code if failure
*
* Timestamps:
* vp - ctime|mtime updated if byte count > 0
*/
/* ARGSUSED */
static int
{
int error;
int i_iov = 0;
int write_eof;
int count = 0;
/*
* Fasttrack empty write
*/
n = start_resid;
if (n == 0)
return (0);
limit = MAXOFFSET_T;
/*
* If immutable or not appending then return EPERM
*/
return (EPERM);
}
/*
* Validate file offset
*/
if (woff < 0) {
return (EINVAL);
}
/*
* Check for mandatory locks before calling zfs_range_lock()
* in order to prevent a deadlock with locks set via fcntl().
*/
return (error);
}
/*
* Pre-fault the pages to ensure slow (eg NFS) pages
* don't hold up txg.
* Skip this if uio contains loaned arc_buf.
*/
else
uio_prefaultpages(n, uio);
/*
* If in append mode, set the io offset pointer to eof.
*/
/*
* Obtain an appending range lock to guarantee file append
* semantics. We reset the write offset once we have the lock.
*/
/*
* We overlocked the file because this write will cause
* the file block size to increase.
* Note that zp_size cannot change with this lock held.
*/
}
} else {
/*
* Note that if the file block size will change as a result of
* this write, then this range lock will lock the entire file
* so that we can re-write the block safely.
*/
}
return (EFBIG);
}
/* Will this write extend the file length? */
/*
* Write the file in reasonable size chunks. Each chunk is written
* in a separate transaction; this keeps the intent log records small
* and allows us to do more fine-grained space accounting.
*/
while (n > 0) {
break;
}
i_iov++;
/*
* This write covers a full block. "Borrow" a buffer
* from the dmu so that we can fill it before we enter
* a transaction. This avoids the possibility of
* holding up the transaction if the data copy hangs
* up on a pagefault (e.g., from an NFS server mapping).
*/
break;
}
}
/*
* Start a transaction.
*/
if (error) {
goto again;
}
break;
}
/*
* If zfs_range_lock() over-locked we grow the blocksize
* and then reduce the lock range. This will only happen
* on the first iteration since zfs_range_reduce() will
* shrink down r_len to the appropriate size.
*/
} else {
}
}
/*
* XXX - should we really limit each write to z_max_blksz?
* Perhaps we should use SPA_MAXBLOCKSIZE chunks?
*/
} else {
/*
* If this is not a full block write, but we are
* extending the file past EOF and this data starts
* block-aligned, use assign_arcbuf(). Otherwise,
* write via dmu_write().
*/
} else {
}
}
}
/*
* If we made no progress, we're done. If we made even
* partial progress, update the znode and ZIL accordingly.
*/
if (tx_bytes == 0) {
break;
}
/*
* privileged and at least one of the excute bits is set.
*
* It would be nice to to this after all writes have
* to another app after the partial write is committed.
*
*/
(S_IXUSR >> 6))) != 0 &&
}
B_TRUE);
/*
* Update the file size (zp_size) if it has changed;
* account for possible concurrent updates.
*/
uio->uio_loffset);
}
if (error != 0)
break;
n -= nbytes;
}
/*
* If we're in replay mode, or we made no progress, return error.
* Otherwise, it's at least a partial write, so it's successful.
*/
return (error);
}
return (0);
}
void
{
/*
* Release the vnode asynchronously as we currently have the
* txg stopped from syncing.
*/
}
#ifdef DEBUG
static int zil_fault_io = 0;
#endif
/*
* Get data to generate a TX_WRITE intent log record.
*/
int
{
int error = 0;
/*
* Nothing to do if the file has been removed
*/
return (ENOENT);
if (zp->z_unlinked) {
/*
* Release the vnode asynchronously as we currently have the
* txg stopped from syncing.
*/
return (ENOENT);
}
/*
* Write records come in two flavors: immediate and indirect.
* For small writes it's cheaper to store the data with the
* log record (immediate); for large writes it's cheaper to
* sync the data and get a pointer to it (indirect) so that
* we don't have to write the data twice.
*/
/* test for truncation needs to be done while range locked */
} else {
}
} else { /* indirect write */
/*
* Have to lock the whole block to ensure when it's
* written out and it's checksum is being calculated
* that no one can change the data. We need to re-check
* blocksize after we get the lock in case it's changed!
*/
for (;;) {
break;
}
/* test for truncation needs to be done while range locked */
#ifdef DEBUG
if (zil_fault_io) {
zil_fault_io = 0;
}
#endif
if (error == 0)
if (error == 0) {
zfs_get_done, zgd);
/*
* On success, we need to wait for the write I/O
* initiated by dmu_sync() to complete before we can
* release this dbuf. We will finish everything up
* in the zfs_get_done() callback.
*/
if (error == 0)
return (0);
error = 0;
}
}
}
return (error);
}
/*ARGSUSED*/
static int
{
int error;
if (flag & V_ACE_MASK)
else
return (error);
}
/*
* If vnode is for a device return a specfs vnode instead.
*/
static int
{
int error = 0;
}
return (error);
}
/*
* Lookup an entry in a directory, or an extended attribute directory.
* If it exists, return a held vnode reference for it.
*
* IN: dvp - vnode of directory to search.
* nm - name of entry to lookup.
* pnp - full pathname to lookup [UNUSED].
* flags - LOOKUP_XATTR set if looking for an attribute.
* rdir - root directory vnode [UNUSED].
* cr - credentials of caller.
* ct - caller context
* direntflags - directory lookup flags
* realpnp - returned pathname.
*
* OUT: vpp - vnode of located entry, NULL if not found.
*
* RETURN: 0 if success
* error code if failure
*
* Timestamps:
* NA
*/
/* ARGSUSED */
static int
{
int error = 0;
/* fast path */
return (ENOTDIR);
return (EIO);
}
if (!error) {
return (0);
}
return (error);
} else {
if (tvp) {
if (error) {
return (error);
}
if (tvp == DNLC_NO_VNODE) {
return (ENOENT);
} else {
}
}
}
}
if (flags & LOOKUP_XATTR) {
/*
* If the xattr property is off, refuse the lookup request.
*/
return (EINVAL);
}
/*
* We don't allow recursive attributes..
* Maybe someday we will.
*/
return (EINVAL);
}
return (error);
}
/*
* Do we have permission to get into attribute directory?
*/
}
return (error);
}
return (ENOTDIR);
}
/*
* Check accessibility of directory.
*/
return (error);
}
return (EILSEQ);
}
if (error == 0)
return (error);
}
/*
* Attempt to create a new entry in a directory. If the entry
* already exists, truncate the file if permissible, else return
* an error. Return the vp of the created or trunc'd file.
*
* IN: dvp - vnode of directory to put new file entry in.
* name - name of new file entry.
* vap - attributes of new file.
* excl - flag indicating exclusive or non-exclusive mode.
* mode - mode to open file with.
* cr - credentials of caller.
* flag - large file flag [UNUSED].
* ct - caller context
* vsecp - ACL to be set
*
* OUT: vpp - vnode of created or trunc'd entry.
*
* RETURN: 0 if success
* error code if failure
*
* Timestamps:
* dvp - ctime|mtime updated if new entry created
* vp - ctime|mtime always, atime if new
*/
/* ARGSUSED */
static int
{
int error;
/*
* If we have an ephemeral id, ACL, or XVATTR then
* make sure file system is at proper version
*/
if (ksid)
else
return (EINVAL);
return (EILSEQ);
}
return (error);
}
}
top:
if (*name == '\0') {
/*
* Null component name refers to the directory itself.
*/
error = 0;
} else {
/* possible VN_HOLD(zp) */
int zflg = 0;
if (flag & FIGNORECASE)
if (error) {
return (error);
}
}
/*
* Create a new file object and update the directory
* to reference it.
*/
goto out;
}
/*
* We only support the creation of regular files in
* extended attribute directories.
*/
goto out;
}
goto out;
goto out;
}
if (fuid_dirtied)
}
if (error) {
goto top;
}
return (error);
}
if (fuid_dirtied)
if (flag & FIGNORECASE)
} else {
/*
* A directory entry already exists for this name.
*/
/*
* Can't truncate an existing file if in exclusive mode.
*/
goto out;
}
/*
* Can't open a directory for writing.
*/
goto out;
}
/*
* Verify requested access to file.
*/
goto out;
}
/*
* Truncate regular files if requested.
*/
/* we can't hold any locks when calling zfs_freesp() */
if (error == 0) {
}
}
}
out:
if (dl)
if (error) {
if (zp)
} else {
}
return (error);
}
/*
* Remove an entry from a directory.
*
* IN: dvp - vnode of directory to remove entry from.
* name - name of entry to remove.
* cr - credentials of caller.
* ct - caller context
* flags - case flags
*
* RETURN: 0 if success
* error code if failure
*
* Timestamps:
* dvp - ctime|mtime
* vp - ctime (if nlink > 0)
*/
uint64_t null_xattr = 0;
/*ARGSUSED*/
static int
int flags)
{
int error;
if (flags & FIGNORECASE) {
}
top:
/*
* Attempt to lock directory; fail if entry doesn't exist.
*/
if (realnmp)
return (error);
}
goto out;
}
/*
* Need to use rmdir for removing directories.
*/
goto out;
}
if (realnmp)
else
/*
* We may delete the znode now, or we may put it in the unlinked set;
* it depends on whether we're the last link, and on whether there are
* other holds on the vnode. So we dmu_tx_hold() the right things to
* allow for either case.
*/
if (may_delete_now) {
toobig =
/* if the file is too big, only hold_free a token amount */
}
/* are there any extended attributes? */
if (xattr_obj) {
}
/* are there any additional acls */
/* charge as an update -- would be nice not to charge at all */
if (error) {
goto top;
}
if (realnmp)
return (error);
}
/*
* Remove the directory entry.
*/
if (error) {
goto out;
}
if (unlinked) {
&xattr_obj_unlinked, sizeof (xattr_obj_unlinked));
}
if (delete_now) {
if (xattr_obj_unlinked) {
else
}
} else if (unlinked) {
}
if (flags & FIGNORECASE)
out:
if (realnmp)
if (!delete_now)
if (xzp)
return (error);
}
/*
* Create a new directory and insert it into dvp using the name
* provided. Return a pointer to the inserted directory.
*
* IN: dvp - vnode of directory to add subdir to.
* dirname - name of new directory.
* vap - attributes of new directory.
* cr - credentials of caller.
* ct - caller context
* vsecp - ACL to be set
*
* OUT: vpp - vnode of created directory.
*
* RETURN: 0 if success
* error code if failure
*
* Timestamps:
* dvp - ctime|mtime updated
* vp - ctime|mtime|atime updated
*/
/*ARGSUSED*/
static int
{
int error;
/*
* If we have an ephemeral id, ACL, or XVATTR then
* make sure file system is at proper version
*/
if (ksid)
else
return (EINVAL);
return (EINVAL);
}
return (EILSEQ);
}
if (flags & FIGNORECASE)
return (error);
}
}
return (error);
}
/*
* First make sure the new directory doesn't exist.
*
* Existence is checked first to make sure we don't return
* EACCES instead of EEXIST which can cause some applications
* to fail.
*/
top:
return (error);
}
return (error);
}
return (EDQUOT);
}
/*
* Add a new entry to the directory.
*/
if (fuid_dirtied)
}
if (error) {
goto top;
}
return (error);
}
/*
* Create new node.
*/
if (fuid_dirtied)
/*
* Now put new name in parent dir.
*/
if (flags & FIGNORECASE)
return (0);
}
/*
* Remove a directory subdir entry. If the current working
* directory is the same as the subdir to be removed, the
* remove will fail.
*
* IN: dvp - vnode of directory to remove from.
* name - name of directory to be removed.
* cwd - vnode of current working directory.
* cr - credentials of caller.
* ct - caller context
* flags - case flags
*
* RETURN: 0 if success
* error code if failure
*
* Timestamps:
* dvp - ctime|mtime updated
*/
/*ARGSUSED*/
static int
{
int error;
if (flags & FIGNORECASE)
top:
/*
* Attempt to lock directory; fail if entry doesn't exist.
*/
return (error);
}
goto out;
}
goto out;
}
goto out;
}
/*
* Grab a lock on the directory to make sure that noone is
* trying to add (or lookup) entries while we are removing it.
*/
/*
* Grab a lock on the parent pointer to make sure we play well
* with the treewalk and directory rename code.
*/
if (error) {
goto top;
}
return (error);
}
if (error == 0) {
if (flags & FIGNORECASE)
}
out:
return (error);
}
/*
* Read as many directory entries as will fit into the provided
* buffer from the given directory cursor position (specified in
* the uio structure.
*
* IN: vp - vnode of directory to read.
* uio - structure supplying read location, range info,
* and return buffer.
* cr - credentials of caller.
* ct - caller context
* flags - case flags
*
* OUT: uio - updated offset and range, buffer filled.
* eofp - set to true if end-of-file detected.
*
* RETURN: 0 if success
* error code if failure
*
* Timestamps:
* vp - atime updated
*
* Note that the low 4 bits of the cookie returned by zap is always zero.
* This allows us to use the low range for "special" directory entries:
* We use 0 for '.', and 1 for '..'. If this is the root of the filesystem,
* we use the offset 2 for the '.zfs' directory.
*/
/* ARGSUSED */
static int
{
int local_eof;
int outcount;
int error;
return (error);
}
/*
* If we are not given an eof variable,
* use a local one.
*/
/*
* Check for valid iov_len.
*/
return (EINVAL);
}
/*
* Quit if directory has been removed (posix)
*/
return (0);
}
error = 0;
/*
* Initialize the iterator cursor.
*/
if (offset <= 3) {
/*
* Start iteration from the beginning of the directory.
*/
} else {
/*
* The offset is a serialized cursor.
*/
}
/*
* Get space to change directory entries into fs independent format.
*/
} else {
}
/*
* If this VFS supports the system attribute view interface; and
* we're looking at an extended attribute directory; and we care
* about normalization conflicts on this vfs; then we must check
* for normalization conflicts with the sysattr name space.
*/
(flags & V_RDDIR_ENTFLAGS);
/*
* Transform to file-system independent format
*/
outcount = 0;
while (outcount < bytes_wanted) {
/*
* Special case `.', `..', and `.zfs'.
*/
if (offset == 0) {
} else if (offset == 1) {
} else {
/*
* Grab next entry.
*/
break;
else
goto update;
}
"entry, obj = %lld, offset = %lld\n",
goto update;
}
/*
* MacOS X can extract the object type here such as:
* uint8_t type = ZFS_DIRENT_TYPE(zap.za_first_integer);
*/
}
}
if (flags & V_RDDIR_ACCFILTER) {
/*
* If we have no access at all, don't include
* this entry in the returned information
*/
goto skip_entry;
goto skip_entry;
}
}
if (flags & V_RDDIR_ENTFLAGS)
else
/*
* Will this entry fit in the buffer?
*/
/*
* Did we manage to fit anything in the buffer?
*/
if (!outcount) {
goto update;
}
break;
}
if (flags & V_RDDIR_ENTFLAGS) {
/*
* Add extended flag entry:
*/
/* NOTE: ed_off is the offset for the *next* entry */
ED_CASE_CONFLICT : 0;
} else {
/*
* Add normal entry:
*/
/* NOTE: d_off is the offset for the *next* entry */
}
/* Prefetch znode */
if (prefetch)
/*
* Move to the next entry, fill in the previous offset.
*/
} else {
offset += 1;
}
}
/*
* Reset the pointer.
*/
}
error = 0;
return (error);
}
static int
{
/*
* Regardless of whether this is required for standards conformance,
* this is the logical behavior when fsync() is called on a file with
* dirty pages. We use B_ASYNC since the ZIL transactions are already
* going to be pushed out as part of the zil_commit().
*/
}
return (0);
}
/*
* Get the requested file attributes and place them in the provided
* vattr structure.
*
* IN: vp - vnode of file.
* vap - va_mask identifies requested attributes.
* If AT_XVATTR set, then optional attrs are requested
* flags - ATTR_NOACLCHECK (CIFS server context)
* cr - credentials of caller.
* ct - caller context
*
* OUT: vap - attribute values.
*
* RETURN: 0 (always succeeds)
*/
/* ARGSUSED */
static int
{
int error = 0;
int count = 0;
return (error);
}
/*
* If ACL is trivial don't bother looking for ACE_READ_ATTRIBUTES.
* Also, if we are the owner don't bother, since owner should
* always be allowed to read basic attributes of file.
*/
skipaclchk, cr)) {
return (error);
}
}
/*
* Return all attributes. It's cheaper to provide the answer
* than to determine whether we were asked the question.
*/
else
/*
* Add in any requested optional attributes and the create time.
* Also set the corresponding bits in the returned attribute bitmap.
*/
xoap->xoa_archive =
}
xoap->xoa_readonly =
}
xoap->xoa_system =
}
xoap->xoa_hidden =
}
xoap->xoa_nounlink =
}
}
}
xoap->xoa_nodump =
}
xoap->xoa_opaque =
}
}
}
}
}
}
}
/*
* Block size hasn't been set; suggest maximal I/O transfers.
*/
}
return (0);
}
/*
* Set the file attributes to the values contained in the
* vattr structure.
*
* IN: vp - vnode of file to be modified.
* vap - new attribute values.
* If AT_XVATTR set, then optional attrs are being set
* flags - ATTR_UTIME set if non-default time values provided.
* - ATTR_NOACLCHECK (CIFS context only).
* cr - credentials of caller.
* ct - caller context
*
* RETURN: 0 if success
* error code if failure
*
* Timestamps:
* vp - ctime updated, mtime updated if size changed.
*/
/* ARGSUSED */
static int
{
int trim_mask = 0;
int need_policy = FALSE;
int count = 0, xattr_count = 0;
if (mask == 0)
return (0);
return (EINVAL);
/*
* that file system is at proper version level
*/
return (EINVAL);
}
return (EISDIR);
}
return (EINVAL);
}
/*
* If this is an xvattr_t, then get a pointer to the structure of
* optional attributes. If this is NULL, then we have a vattr_t.
*/
/*
* Immutable files can only alter immutable bit and atime
*/
return (EPERM);
}
return (EPERM);
}
/*
* Verify timestamps doesn't overflow 32 bits.
* ZFS can handle large timestamps, but 32bit syscalls can't
* handle times greater than 2039. This check should be removed
* once large timestamps are fully supported.
*/
return (EOVERFLOW);
}
}
top:
/* Can this be moved to before the top label? */
return (EROFS);
}
/*
* First validate permissions
*/
if (err) {
return (err);
}
/*
* XXX - Note, we are not providing any open
* mode flags here (like FNDELAY), so we may
* block if there are locks present... this
* should be addressed in openat().
*/
/* XXX - would it be OK to generate a log record here? */
if (err) {
return (err);
}
}
skipaclchk, cr);
}
int take_owner;
int take_group;
/*
* NOTE: even if a new mode is being set,
*/
/*
* Take ownership or chgrp to group we are a member of
*/
/*
* If both AT_UID and AT_GID are set then take_owner and
* take_group must both be set in order to allow taking
* ownership.
*
* Otherwise, send the check through secpolicy_vnode_setattr()
*
*/
skipaclchk, cr) == 0) {
/*
*/
} else {
need_policy = TRUE;
}
} else {
need_policy = TRUE;
}
}
/*
* Update xvattr mask to include only those attributes
* that are actually changing.
*
* the bits will be restored prior to actually setting
* the attributes so the caller thinks they were set.
*/
if (xoap->xoa_appendonly !=
need_policy = TRUE;
} else {
}
}
if (xoap->xoa_nounlink !=
need_policy = TRUE;
} else {
}
}
if (xoap->xoa_immutable !=
need_policy = TRUE;
} else {
}
}
if (xoap->xoa_nodump !=
need_policy = TRUE;
} else {
}
}
if (xoap->xoa_av_modified !=
need_policy = TRUE;
} else {
}
}
xoap->xoa_av_quarantined) ||
need_policy = TRUE;
} else {
}
}
return (EPERM);
}
if (need_policy == FALSE &&
need_policy = TRUE;
}
}
if (err) {
return (err);
}
} else {
need_policy = TRUE;
}
}
if (need_policy) {
/*
* If trim_mask is set then take ownership
* has been granted or write_acl is present and user
* has the ability to modify mode. In that case remove
* UID|GID and or MODE from mask so that
* secpolicy_vnode_setattr() doesn't revoke it.
*/
if (trim_mask) {
}
if (err) {
return (err);
}
if (trim_mask)
}
/*
* secpolicy_vnode_setattr, or take ownership may have
* changed va_mask
*/
sizeof (xattr_obj));
if (xattr_obj) {
if (err)
goto out2;
}
goto out2;
}
}
goto out2;
}
}
}
goto out;
/*
* Are we upgrading ACL from old V0 format
* to V1 format?
*/
ZNODE_ACL_VERSION(zp) ==
ZFS_EXTERNAL_ACL(zp), 0,
0, aclp->z_acl_bytes);
} else {
aclp->z_acl_bytes);
}
0, aclp->z_acl_bytes);
}
} else {
else
}
if (attrzp) {
}
if (fuid_dirtied)
if (err) {
goto out;
}
count = 0;
/*
* Set each attribute requested.
* We group settings according to the locks they need to acquire.
*
* Note: you cannot set ctime directly, although it will be
* updated as a side-effect of calling this function.
*/
if (attrzp)
if (attrzp) {
sizeof (new_uid));
}
}
if (attrzp) {
sizeof (new_gid));
}
}
}
if (attrzp) {
}
}
}
if (attrzp)
}
}
B_TRUE);
} else if (mask != 0) {
B_TRUE);
if (attrzp) {
}
}
/*
* Do this after setting timestamps to prevent timestamp
* update from toggling bit
*/
/*
* restore trimmed off masks
* so that return masks can be set for caller.
*/
}
}
}
}
}
}
}
if (fuid_dirtied)
if (mask != 0)
out:
xattr_count, tx);
}
if (attrzp)
if (aclp)
if (fuidp) {
}
if (err) {
goto top;
} else {
}
out2:
return (err);
}
typedef struct zfs_zlock {
} zfs_zlock_t;
/*
* Drop locks and release vnodes that were held by zfs_rename_lock().
*/
static void
{
}
}
/*
* Search back through the directory tree, using the ".." entries.
* Lock each directory in the chain to prevent concurrent renames.
* Fail any attempt to move a directory into one of its own descendants.
* XXX - z_parent_lock can overlap with map or grow locks
*/
static int
{
/*
* First pass write-locks szp and compares to zp->z_id.
* Later passes read-lock zp and compare to zp->z_parent.
*/
do {
/*
* Another thread is renaming in this path.
* Note that if we are a WRITER, we don't have any
* parent_locks held yet.
*/
/*
* Drop our locks and restart
*/
continue;
} else {
/*
* Wait for other thread to drop its locks
*/
}
}
return (EINVAL);
return (0);
if (error)
return (error);
}
return (0);
}
/*
* Move an entry from the provided source directory to the target
* directory. Change the entry name as indicated.
*
* IN: sdvp - Source directory containing the "old entry".
* snm - Old entry name.
* tdvp - Target directory to contain the "new entry".
* tnm - New entry name.
* cr - credentials of caller.
* ct - caller context
* flags - case flags
*
* RETURN: 0 if success
* error code if failure
*
* Timestamps:
* sdvp,tdvp - ctime|mtime updated
*/
/*ARGSUSED*/
static int
{
int error = 0;
int zflg = 0;
/*
* Make sure we have the real vp for the target directory.
*/
return (EXDEV);
}
return (EILSEQ);
}
if (flags & FIGNORECASE)
top:
/*
* This is to prevent the creation of links into attribute space
* See the comment in zfs_link() for why this is considered bad.
*/
return (EINVAL);
}
/*
* Lock source and target directory entries. To prevent deadlock,
* a lock ordering must be defined. We lock the directory with
* the smallest object id first, or if it's a tie, the one with
* the lexically first name.
*/
cmp = -1;
cmp = 1;
} else {
/*
* First compare the two name arguments without
* considering any case folding.
*/
if (cmp == 0) {
/*
* POSIX: "If the old argument and the new argument
* both refer to links to the same existing file,
* the rename() function shall return successfully
* and perform no other action."
*/
return (0);
}
/*
* If the file system is case-folding, then we may
* have some more checking to do. A case-folding file
* system is either supporting mixed case sensitivity
* access or is completely case-insensitive. Note
* that the file system is always case preserving.
*
* In mixed sensitivity mode case sensitive behavior
* is the default. FIGNORECASE must be used to
* explicitly request case insensitive behavior.
*
* If the source and target names provided differ only
* by case (e.g., a request to rename 'tim' to 'Tim'),
* we will treat this as a special case in the
* case-insensitive mode: as long as the source name
* is an exact match, we will allow this to proceed as
* a name-change request.
*/
flags & FIGNORECASE)) &&
&error) == 0) {
/*
* case preserving rename request, require exact
* name matches
*/
}
}
/*
* If the source and destination directories are the same, we should
* grab the z_name_lock of that directory only once.
*/
}
if (cmp < 0) {
} else {
}
if (serr) {
/*
* Source entry invalid or not there.
*/
if (!terr) {
if (tzp)
}
return (serr);
}
if (terr) {
return (terr);
}
/*
* Must have write access at the source to remove the old entry
* and write access at the target to create the new entry.
* Note that if target and source are the same, this can be
* done in a single check.
*/
goto out;
/*
* Check to make sure rename is valid.
* Can't do a move like this: /usr/a/b to /usr/a/b/c/d
*/
goto out;
}
/*
* Does target exist?
*/
if (tzp) {
/*
* Source and target must be the same type.
*/
goto out;
}
} else {
goto out;
}
}
/*
* POSIX dictates that when the source and target
* entries refer to the same file object, rename
* must do nothing and exit without error.
*/
error = 0;
goto out;
}
}
if (tzp)
/*
* notify the target directory if it is not the same
* as source directory.
*/
}
}
if (tzp) {
}
if (error) {
if (tzp)
goto top;
}
return (error);
}
if (tzp) /* Attempt to remove the existing target */
if (error == 0) {
if (error == 0) {
/* Update path information for the target vnode */
}
}
out:
if (tzp)
return (error);
}
/*
* Insert the indicated symbolic reference entry into the directory.
*
* IN: dvp - Directory to contain new symbolic link.
* link - Name for new symlink entry.
* vap - Attributes of new entry.
* target - Target path of new symlink.
* cr - credentials of caller.
* ct - caller context
* flags - case flags
*
* RETURN: 0 if success
* error code if failure
*
* Timestamps:
* dvp - ctime|mtime updated
*/
/*ARGSUSED*/
static int
{
int error;
return (EILSEQ);
}
if (flags & FIGNORECASE)
if (len > MAXPATHLEN) {
return (ENAMETOOLONG);
}
return (error);
}
top:
/*
* Attempt to lock directory; fail if entry already exists.
*/
if (error) {
return (error);
}
return (error);
}
return (EDQUOT);
}
}
if (fuid_dirtied)
if (error) {
goto top;
}
return (error);
}
/*
* Create a new object for the symlink.
* for version 4 ZPL datsets the symlink will be an SA attribute
*/
if (fuid_dirtied)
else
/*
* Insert the new object into the directory.
*/
if (flags & FIGNORECASE)
return (error);
}
/*
* Return, in the buffer contained in the provided uio structure,
* the symbolic path referred to by vp.
*
* IN: vp - vnode of symbolic link.
* uoip - structure to contain the link path.
* cr - credentials of caller.
* ct - caller context
*
* OUT: uio - structure to contain the link path.
*
* RETURN: 0 if success
* error code if failure
*
* Timestamps:
* vp - atime updated
*/
/* ARGSUSED */
static int
{
int error;
else
return (error);
}
/*
* Insert a new entry into directory tdvp referencing svp.
*
* IN: tdvp - Directory to contain new entry.
* svp - vnode of new entry.
* name - name of new entry.
* cr - credentials of caller.
* ct - caller context
*
* RETURN: 0 if success
* error code if failure
*
* Timestamps:
* tdvp - ctime|mtime updated
* svp - ctime updated
*/
/* ARGSUSED */
static int
{
int error;
/*
* POSIX dictates that we return EPERM here.
* Better choices include ENOTSUP or EISDIR.
*/
return (EPERM);
}
return (EXDEV);
}
return (error);
}
return (EPERM);
}
return (EILSEQ);
}
if (flags & FIGNORECASE)
/*
* We do not support links between attributes and non-attributes
* because of the potential security risk of creating links
* into "normal" file space in order to circumvent restrictions
* imposed in attribute space.
*/
return (EINVAL);
}
secpolicy_basic_link(cr) != 0) {
return (EPERM);
}
return (error);
}
top:
/*
* Attempt to lock directory; fail if entry already exists.
*/
if (error) {
return (error);
}
if (error) {
goto top;
}
return (error);
}
if (error == 0) {
if (flags & FIGNORECASE)
}
if (error == 0) {
}
return (error);
}
/*
* zfs_null_putapage() is used when the file system has been force
* unmounted. It just drops the pages.
*/
/* ARGSUSED */
static int
{
return (0);
}
/*
* Push a page out to disk, klustering if possible.
*
* IN: vp - file to push page to.
* pp - page to push.
* flags - additional flags.
* cr - credentials of caller.
*
* OUT: offp - start of range pushed.
* lenp - len of range pushed.
*
* RETURN: 0 if success
* error code if failure
*
* NOTE: callers must have locked the page to be pushed. On
* exit, the page (and all other pages in the kluster) must be
* unlocked.
*/
/* ARGSUSED */
static int
{
int err;
/*
* If our blocksize is bigger than the page size, try to kluster
* multiple pages so that we write a full block (thus avoiding
* a read-modify-write).
*/
}
/*
* Can't push pages past end-of-file.
*/
/* ignore all pages */
err = 0;
goto out;
/* ignore pages past end of file */
if (trunc)
}
goto out;
}
top:
if (err != 0) {
goto top;
}
goto out;
}
} else {
}
if (err == 0) {
int count = 0;
&mtime, 16);
&ctime, 16);
B_TRUE);
}
out:
if (offp)
if (lenp)
return (err);
}
/*
* Copy the portion of the file indicated from pages into the file.
* The pages are stored in a page list attached to the files vnode.
*
* IN: vp - vnode of file to push page data to.
* off - position in file to put data.
* len - amount of data to write.
* flags - flags to control the operation.
* cr - credentials of caller.
* ct - caller context.
*
* RETURN: 0 if success
* error code if failure
*
* Timestamps:
* vp - ctime|mtime updated
*/
/*ARGSUSED*/
static int
{
int error = 0;
/*
* Align this request to the file block size in case we kluster.
* XXX - this can result in pretty aggresive locking, which can
* to break up long requests (len == 0) into block-by-block
* operations to get narrower locking.
*/
else
io_off = 0;
else
io_len = 0;
if (io_len == 0) {
/*
* Search the entire vp list for pages >= io_off.
*/
goto out;
}
/* past end of file */
return (0);
}
} else {
}
int err;
/*
* Found a dirty page to push
*/
if (err)
} else {
}
}
out:
return (error);
}
/*ARGSUSED*/
void
{
int error;
/*
* The fs has been unmounted, or we did a
*/
if (vn_has_cached_data(vp)) {
}
return;
}
/*
* Attempt to push any data in the page cache. If this fails
* we will get kicked out later in zfs_zinactive().
*/
if (vn_has_cached_data(vp)) {
cr);
}
if (error) {
} else {
zp->z_atime_dirty = 0;
}
}
}
/*
* Bounds-check the seek operation.
*
* IN: vp - vnode seeking within
* ooff - old file offset
* noffp - pointer to new file offset
* ct - caller context
*
* RETURN: 0 if success
* EINVAL if new offset invalid
*/
/* ARGSUSED */
static int
{
return (0);
}
/*
* Pre-filter the generic locking function to trap attempts to place
* a mandatory lock on a memory mapped file.
*/
static int
{
/*
* We are following the UFS semantics with respect to mapcnt
* here: If we see that the file is mapped already, then we will
* return an error, but we don't worry about races between this
* function and zfs_map().
*/
return (EAGAIN);
}
}
/*
* If we can't find a page in the cache, we will create a new page
* and fill it with file data. For efficiency, we may try to fill
* multiple pages at once (klustering) to fill up the supplied page
* list. Note that the pages to be filled are held with an exclusive
* lock to prevent access by other threads while they are being filled.
*/
static int
{
int err;
/*
* We only have a single page, don't bother klustering
*/
} else {
/*
* Try to find enough pages to fill the page list
*/
}
/*
* The page already exists, nothing to do here.
*/
return (0);
}
/*
* Fill the pages in the kluster.
*/
if (err) {
/* On error, toss the entire kluster */
/* convert checksum errors into IO errors */
return (err);
}
}
/*
* Fill in the page list array from the kluster starting
* from the desired offset `off'.
* NOTE: the page list will always be null terminated.
*/
return (0);
}
/*
* Return pointers to the pages for the file region [off, off + len]
* in the pl array. If plsz is greater than len, this function may
* also return page pointers from after the specified region
* (i.e. the region [off, off + plsz]). These additional pages are
* only returned if they are already in the cache, or were created as
* part of a klustered read.
*
* IN: vp - vnode of file to get data from.
* off - position in file to get data from.
* len - amount of data to retrieve.
* plsz - length of provided page list.
* seg - segment to obtain pages for.
* addr - virtual address of fault.
* rw - mode of created pages.
* cr - credentials of caller.
* ct - caller context.
*
* OUT: protp - protection mode of created pages.
* pl - list of pages created.
*
* RETURN: 0 if success
* error code if failure
*
* Timestamps:
* vp - atime updated
*/
/* ARGSUSED */
static int
{
int err = 0;
/* we do our own caching, faultahead is unnecessary */
return (0);
else
if (protp)
/*
* Loop through the requested range [off, off + len) looking
* for pages. If we don't find a page, we will need to create
* a new page and fill it with data from the file.
*/
while (len > 0) {
goto out;
while (*pl) {
if (len > 0) {
}
pl++;
}
}
/*
* Fill out the page array with any pages already in the cache.
*/
while (plsz > 0 &&
}
out:
if (err) {
/*
* Release any pages we have previously locked.
*/
page_unlock(*--pl);
} else {
}
return (err);
}
/*
* Request a memory map for a section of a file. This code interacts
* with common code and the VM system as follows:
*
* common code calls mmap(), which ends up in smmap_common()
*
* this calls VOP_MAP(), which takes you into (say) zfs
*
* zfs_map() calls as_map(), passing segvn_create() as the callback
*
* segvn_create() creates the new segment and calls VOP_ADDMAP()
*
* zfs_addmap() updates z_mapcnt
*/
/*ARGSUSED*/
static int
{
int error;
return (EPERM);
}
return (EACCES);
}
return (ENOSYS);
}
return (ENXIO);
}
return (ENODEV);
}
/*
* If file is locked, disallow mapping.
*/
return (EAGAIN);
}
if (error != 0) {
return (error);
}
return (error);
}
/* ARGSUSED */
static int
{
return (0);
}
/*
* The reason we push dirty pages as part of zfs_delmap() is so that we get a
* more accurate mtime for the associated file. Since we don't have a way of
* detecting when the data was actually modified, we have to resort to
* heuristics. If an explicit msync() is done, then we mark the mtime when the
* last page is pushed. The problem occurs when the msync() call is omitted,
* which by far the most common case:
*
* open()
* mmap()
* <modify memory>
* munmap()
* close()
* <time lapse>
* putpage() via fsflush
*
* If we wait until fsflush to come along, we can have a modification time that
* is some arbitrary point in the future. In order to prevent this in the
* common case, we flush pages whenever a (MAP_SHARED, PROT_WRITE) mapping is
* torn down.
*/
/* ARGSUSED */
static int
{
return (0);
}
/*
* Free or allocate space in a file. Currently, this function only
* supports the `F_FREESP' command. However, this command is somewhat
* misnamed, as its functionality includes the ability to allocate as
* well as free space.
*
* IN: vp - vnode of file to free data in.
* cmd - action to take (only F_FREESP supported).
* flag - current file open mode flags.
* offset - current file offset.
* cr - credentials of caller [UNUSED].
* ct - caller context.
*
* RETURN: 0 if success
* error code if failure
*
* Timestamps:
* vp - ctime|mtime updated
*/
/* ARGSUSED */
static int
{
int error;
return (EINVAL);
}
return (error);
}
return (EINVAL);
}
return (error);
}
/*ARGSUSED*/
static int
{
return (error);
}
return (ENOSPC);
}
/* Must have a non-zero generation number to distinguish from .zfs */
if (gen == 0)
gen = 1;
if (size == LONG_FID_LEN) {
/* XXX - this should be the generation number for the objset */
}
return (0);
}
static int
{
int error;
switch (cmd) {
case _PC_LINK_MAX:
return (0);
case _PC_FILESIZEBITS:
*valp = 64;
return (0);
case _PC_XATTR_EXISTS:
*valp = 0;
if (error == 0) {
if (!zfs_dirempty(xzp))
*valp = 1;
/*
* If there aren't extended attributes, it's the
* same as having zero of them.
*/
error = 0;
}
return (error);
case _PC_SATTR_ENABLED:
case _PC_SATTR_EXISTS:
return (0);
case _PC_ACCESS_FILTERING:
return (0);
case _PC_ACL_ENABLED:
*valp = _ACL_ACE_ENABLED;
return (0);
case _PC_MIN_HOLE_SIZE:
return (0);
case _PC_TIMESTAMP_RESOLUTION:
/* nanosecond timestamp resolution */
*valp = 1L;
return (0);
default:
}
}
/*ARGSUSED*/
static int
{
int error;
return (error);
}
/*ARGSUSED*/
static int
{
int error;
return (error);
}
/*
* Tunable, both must be a power of 2.
*
* zcr_blksz_min: the smallest read we may consider to loan out an arcbuf
* zcr_blksz_max: if set to less than the file block size, allow loaning out of
* an arcbuf for a partial block read
*/
/*ARGSUSED*/
static int
{
int blksz;
int fullblk, i;
return (EINVAL);
switch (ioflag) {
case UIO_WRITE:
/*
* Loan out an arc_buf for write if write size is bigger than
* max_blksz, and the file's block size is also max_blksz.
*/
return (EINVAL);
}
/*
* Caller requests buffers for write before knowing where the
* write offset might be (e.g. NFS TCP write).
*/
if (offset == -1) {
preamble = 0;
} else {
if (preamble) {
}
}
(void) dmu_xuio_init(xuio,
int, postamble, int,
/*
* currently represent full arc_buf's.
*/
if (preamble) {
/* data begins in the middle of the arc_buf */
blksz);
}
for (i = 0; i < fullblk; i++) {
blksz);
}
if (postamble) {
/* data ends in the middle of the arc_buf */
blksz);
}
break;
case UIO_READ:
/*
* Loan out an arc_buf for read if the read size is larger than
* the current file block size. Block alignment is not
* considered. Partial arc_buf will be loaned out for read.
*/
if (blksz < zcr_blksz_min)
if (blksz > zcr_blksz_max)
/* avoid potential complexity of dealing with it */
return (EINVAL);
}
return (EINVAL);
}
break;
default:
return (EINVAL);
}
return (0);
}
/*ARGSUSED*/
static int
{
int i;
i = dmu_xuio_cnt(xuio);
while (i-- > 0) {
/*
* if abuf == NULL, it must be a write buffer
* that has been returned in zfs_write().
*/
if (abuf)
}
return (0);
}
/*
* Predeclare these here so that the compiler assumes that
* this is an "old style" function declaration that does
* not include arguments => we won't get type mismatch errors
* in the initializations that follow.
*/
static int zfs_inval();
static int zfs_isdir();
static int
{
return (EINVAL);
}
static int
{
return (EISDIR);
}
/*
* Directory vnode operations template
*/
const fs_operation_def_t zfs_dvnodeops_template[] = {
};
/*
* Regular file vnode operations template
*/
const fs_operation_def_t zfs_fvnodeops_template[] = {
};
/*
* Symbolic link vnode operations template
*/
const fs_operation_def_t zfs_symvnodeops_template[] = {
};
/*
* special share hidden files vnode operations template
*/
const fs_operation_def_t zfs_sharevnodeops_template[] = {
};
/*
* Extended attribute directory vnode operations template
* This template is identical to the directory vnodes
* operation template except for restricted operations:
* VOP_MKDIR()
* VOP_SYMLINK()
* Note that there are other restrictions embedded in:
* zfs_create() - restrict type to VREG
*/
const fs_operation_def_t zfs_xdvnodeops_template[] = {
};
/*
* Error vnode operations template
*/
const fs_operation_def_t zfs_evnodeops_template[] = {
};