tmp_vnops.c revision 54207fd2e1e7ed01d0416da8cf296dbef920fbfc
/*
* 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
*/
/*
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* Copyright (c) 2015, Joyent, Inc. All rights reserved.
* Copyright 2015 Nexenta Systems, Inc. All rights reserved.
*/
#include <sys/sysmacros.h>
#include <sys/vfs_opreg.h>
#include <sys/pathname.h>
int, struct cred *);
/* ARGSUSED1 */
static int
{
/*
* swapon to a tmpfs file is not supported so access
* is denied on open if VISSWAP is set.
*/
return (EINVAL);
return (0);
}
/* ARGSUSED1 */
static int
int flag,
int count,
{
return (0);
}
/*
* wrtmp does the real work of write requests for tmpfs.
*/
static int
struct caller_context *ct)
{
int error = 0;
int pagecreate; /* == 1 if we allocated a page */
int newpage;
long tn_size_changed = 0;
long old_tn_size;
long new_tn_size;
"tmp_wrtmp_start:vp %p", vp);
/*
* tmp_getattr ends up being called by chklock
*/
if (error != 0) {
return (error);
}
}
if (uio->uio_loffset < 0)
return (EINVAL);
limit = MAXOFFSET_T;
mutex_enter(&p->p_lock);
p, RCA_UNSAFE_SIGINFO);
mutex_exit(&p->p_lock);
return (EFBIG);
}
return (EFBIG);
}
"tmp_wrtmp_end:vp %p error %d", vp, 0);
return (0);
}
do {
long offset;
long delta;
/*
* A maximum of PAGESIZE bytes of data is transferred
* each pass through this loop
*/
goto out;
}
}
/*
* delta is the amount of anonymous memory
* to reserve for the file.
* We always reserve in pagesize increments so
* unless we're extending the file into a new page,
* we don't need to call tmp_resv.
*/
if (delta > 0) {
pagecreate = 1;
/*
* Log file system full in the zone that owns
* the tmpfs mount, as well as in the global
* zone if necessary.
*/
CE_WARN, "%s: File system full, "
"swap space limit exceeded",
tm->tm_mntpath);
CE_WARN, "%s: File system full, "
"swap space limit exceeded",
}
break;
}
}
/* grow the file to the new length */
tn_size_changed = 1;
/*
* Postpone updating tp->tn_size until uiomove() is
* done.
*/
}
/*
* Writing whole page so reading from disk
* is a waste
*/
pagecreate = 1;
} else {
pagecreate = 0;
}
/*
* If writing past EOF or filling in a hole
* we need to allocate an anon slot.
*/
pagecreate = 1;
tp->tn_nblocks++;
}
/*
* We have to drop the contents lock to allow the VM
* system to reacquire it in tmp_getpage()
*/
/*
* Touch the page and fault it in if it is not in core
* before segmap_getmapflt or vpm_data_copy can lock it.
* This is to avoid the deadlock if the buffer is mapped
* to the same file through mmap which we want to write.
*/
newpage = 0;
if (vpm_enable) {
/*
* Copy data. If new pages are created, part of
* the page that is not written will be initizliazed
* with zeros.
*/
} else {
/* Get offset within the segmap mapping */
S_WRITE);
}
if (!vpm_enable && pagecreate) {
/*
* segmap_pagecreate() returns 1 if it calls
* page_create_va() to allocate any pages.
*/
/*
* Clear from the beginning of the page to the starting
* offset of the data.
*/
if (pageoffset != 0)
(size_t)pageoffset);
}
if (!vpm_enable) {
}
if (!vpm_enable && pagecreate &&
long zoffset; /* zero from offset into page */
/*
* We created pages w/o initializing them completely,
* thus we need to zero the part that wasn't set up.
* This happens on most EOF write cases and if
* we had some sort of error during the uiomove.
*/
long nmoved;
/*
* Zero from the end of data in the page to the
* end of the page.
*/
}
/*
* Unlock the pages which have been allocated by
* page_create_va() in segmap_pagecreate()
*/
if (!vpm_enable && newpage) {
}
if (error) {
/*
* If we failed on a write, we must
* be sure to invalidate any pages that may have
* been allocated.
*/
if (vpm_enable) {
SM_INVAL);
} else {
}
} else {
if (vpm_enable) {
0);
} else {
}
}
/*
* Re-acquire contents lock.
*/
/*
* Update tn_size.
*/
if (tn_size_changed)
/*
* If the uiomove failed, fix up tn_size.
*/
if (error) {
if (tn_size_changed) {
/*
* The uiomove failed, and we
* allocated blocks,so get rid
* of them.
*/
}
} else {
/*
* XXX - Can this be out of the loop?
*/
/*
* Clear Set-UID & Set-GID bits on
* successful write if not privileged
* and at least one of the execute bits
* is set. If we always clear Set-GID,
* mandatory file and record locking is
* unuseable.
*/
}
gethrestime(&now);
}
out:
/*
* If we've already done a partial-write, terminate
* the write but return no error.
*/
error = 0;
return (error);
}
/*
* rdtmp does the real work of read requests for tmpfs.
*/
static int
struct caller_context *ct)
{
int error;
#if defined(lint)
#endif
vp);
/*
* tmp_getattr ends up being called by chklock
*/
if (error != 0) {
return (error);
}
}
return (0);
}
if (uio->uio_loffset < 0)
return (EINVAL);
"tmp_rdtmp_end:vp %p error %d", vp, 0);
return (0);
}
do {
long diff;
long offset;
if (diff <= 0) {
error = 0;
goto out;
}
/*
* We have to drop the contents lock to allow the VM system
* to reacquire it in tmp_getpage() should the uiomove cause a
* pagefault.
*/
if (vpm_enable) {
/*
* Copy data.
*/
0, S_READ);
} else {
}
if (error) {
if (vpm_enable) {
} else {
}
} else {
if (vpm_enable) {
0);
} else {
}
}
/*
* Re-acquire contents lock.
*/
out:
/*
* If we've already done a partial read, terminate
* the read but return no error.
*/
error = 0;
return (error);
}
/* ARGSUSED2 */
static int
struct caller_context *ct)
{
int error;
/*
* We don't currently support reading non-regular files
*/
return (EISDIR);
return (EINVAL);
/*
* tmp_rwlock should have already been called from layers above
*/
return (error);
}
static int
struct caller_context *ct)
{
int error;
/*
* We don't currently support writing to non-regular files
*/
return (EINVAL); /* XXX EISDIR? */
/*
* tmp_rwlock should have already been called from layers above
*/
/*
* In append mode start at end of file.
*/
}
return (error);
}
/* ARGSUSED */
static int
int com,
int flag,
int *rvalp,
{
return (ENOTTY);
}
/* ARGSUSED2 */
static int
int flags,
{
int attrs = 1;
/*
* A special case to handle the root tnode on a diskless nfs
* client who may have had its uid and gid inherited
* from an nfs vnode with nobody ownership. Likely the
* may be mapable so ask again.
* vfsp can't get unmounted because we hold vp.
*/
} else {
}
}
if (attrs == 0) {
}
/*
* XXX Holes are not taken into account. We could take the time to
* run through the anon array looking for allocated slots...
*/
return (0);
}
/*ARGSUSED4*/
static int
int flags,
{
int error = 0;
long mask;
/*
* Cannot set these attributes
*/
return (EINVAL);
/*
* Change file access modes. Must be owner or have sufficient
* privileges.
*/
tp);
if (error)
goto out;
}
/* Don't support large files. */
goto out;
}
goto out1;
}
out:
out1:
return (error);
}
/* ARGSUSED2 */
static int
int mode,
int flags,
{
int error;
return (error);
}
/* ARGSUSED3 */
static int
char *nm,
int flags,
int *direntflags,
{
int error;
/* allow cd into @ dir */
if (flags & LOOKUP_XATTR) {
/*
* don't allow attributes if not mounted XATTR support
*/
return (EINVAL);
/* No attributes on attributes */
return (EINVAL);
if (!(flags & CREATE_XATTR_DIR)) {
return (ENOENT);
}
/*
* No attribute directory exists for this
* node - create the attr dir as a side effect
* of this lookup.
*/
/*
* Make sure we have adequate permission...
*/
return (error);
}
/*
* Fix-up fields unique to attribute directories.
*/
} else {
}
} else {
}
return (0);
}
/*
* Null component name is a synonym for directory being searched.
*/
if (*nm == '\0') {
return (0);
}
if (error == 0) {
/*
* If vnode is a device return special vnode instead
*/
cred);
}
}
"tmpfs lookup:vp %p name %s vpp %p error %d",
return (error);
}
/*ARGSUSED7*/
static int
char *nm,
int mode,
int flag,
{
int error;
error = 0;
/* device files not allowed in ext. attr dirs */
return (EINVAL);
/* Must be privileged to set sticky bit */
return (EINVAL);
}
/*
* Null component name is a synonym for directory being searched.
*/
if (*nm == '\0') {
} else {
}
if (error == 0) { /* name found */
/*
* directory, allow it
*/
else {
}
if (error) {
return (error);
}
}
cred);
return (ENOSYS);
}
}
if (trunc)
return (0);
}
return (error);
if (error) {
if (self)
/*
* This means that the file was created sometime
* after we checked and did not find it and when
* we went to create it.
* Since creat() is supposed to truncate a file
* that already exits go back to the begining
* of the function. This time we will find it
* and go down the tmp_trunc() path
*/
goto again;
}
return (error);
}
return (ENOSYS);
}
return (0);
}
/* ARGSUSED3 */
static int
char *nm,
int flags)
{
int error;
if (error)
return (error);
return (error);
}
/* ARGSUSED4 */
static int
char *tnm,
int flags)
{
int error;
return (EPERM);
/*
* Make sure link for extended attributes is valid
* We only support hard linking of xattr's in xattrdir to an xattrdir
*/
return (EINVAL);
if (error == 0) {
return (EEXIST);
}
return (error);
if (error == 0) {
}
return (error);
}
/* ARGSUSED5 */
static int
char *onm, /* source name */
char *nnm, /* destination name */
int flags)
{
struct tmpnode *fromparent;
int error;
int samedir = 0; /* set if odvp == ndvp */
return (EINVAL);
/*
* Look up tmpnode of file we're supposed to rename.
*/
if (error) {
return (error);
}
/*
* Make sure we can delete the old (source) entry. This
* requires write permission on the containing directory. If
* that directory is "sticky" it requires further checks.
*/
goto done;
/*
* Check for renaming to or from '.' or '..' or that
* fromtp == fromparent
*/
if ((onm[0] == '.' &&
(nnm[0] == '.' &&
(fromparent == fromtp)) {
goto done;
}
/*
* Make sure we can search and rename into the new
* (destination) directory.
*/
if (!samedir) {
if (error)
goto done;
}
}
/* Notify the target dir. if not the same as the source dir. */
}
/*
* Link source to new target
*/
if (error) {
/*
* ESAME isn't really an error; it indicates that the
* operation should not be done because the source and target
* are the same file, but that no error should be reported.
*/
error = 0;
goto done;
}
/*
* Unlink from source.
*/
/*
* The following handles the case where our source tmpnode was
* removed before we got to it.
*
* XXX We should also cleanup properly in the case where tdirdelete
* fails for some other reason. Currently this case shouldn't happen.
* (see 1184991).
*/
error = 0;
if (error == 0) {
/*
* vnevent_rename_dest is called in tdirenter().
* Notify the target dir if not same as source dir.
*/
}
done:
return (error);
}
/* ARGSUSED5 */
static int
char *nm,
int flags,
{
int error;
/* no new dirs allowed in xattr dirs */
return (EINVAL);
/*
* Might be dangling directory. Catch it here,
* because a ENOENT return from tdirlookup() is
* an "o.k. return".
*/
return (ENOENT);
if (error == 0) {
return (EEXIST);
}
return (error);
if (error) {
if (self)
return (error);
}
return (0);
}
/* ARGSUSED4 */
static int
char *nm,
int flags)
{
int error = 0;
/*
* Return error when removing . and ..
*/
return (EINVAL);
return (EEXIST); /* Should be ENOTEMPTY */
if (error)
return (error);
goto done1;
}
goto done1;
}
goto done1;
}
if (vn_vfswlock(vp)) {
goto done1;
}
goto done;
}
/*
* Check for an empty directory
* i.e. only includes entries for "." and ".."
*/
/*
* Update atime because checking tn_dirents is logically
* equivalent to reading the directory
*/
goto done;
}
done:
return (error);
}
/* ARGSUSED2 */
static int
int *eofp,
int flags)
{
int error = 0;
long outcount = 0;
long bufsize;
int reclen;
if (eofp)
*eofp = 1;
return (0);
}
/*
* assuming system call has already called tmp_rwlock
*/
return (EINVAL);
return (ENOTDIR);
/*
* There's a window here where someone could have removed
* all the entries in the directory after we put a hold on the
* vnode but before we grabbed the rwlock. Just return.
*/
/*NOTREACHED*/
}
return (0);
}
/*
* Get space for multiple directory entries
*/
offset = 0;
while (tdp) {
if (!outcount)
/*
* Buffer too small for any entries.
*/
break;
}
/* use strncpy(9f) to zero out uninitialized bytes */
}
}
if (!error)
if (!error) {
/* If we reached the end of the list our offset */
/* should now be just past the end. */
if (!tdp) {
offset += 1;
if (eofp)
*eofp = 1;
} else if (eofp)
*eofp = 0;
}
return (error);
}
/* ARGSUSED5 */
static int
char *lnm,
char *tnm,
int flags)
{
int error;
/* no symlinks allowed to files in xattr dirs */
return (EINVAL);
if (error == 0) {
/*
* The entry already exists
*/
return (EEXIST); /* was 0 */
}
return (error);
}
if (error) {
if (self)
return (error);
}
return (ENOSPC);
}
return (error);
}
/* ARGSUSED2 */
static int
{
int error = 0;
return (EINVAL);
return (error);
}
/* ARGSUSED */
static int
int syncflag,
{
return (0);
}
/* ARGSUSED */
static void
{
top:
/*
* If we don't have the last hold or the link count is non-zero,
* there's little to do -- just drop our hold.
*/
return;
}
/*
* We have the last hold *and* the link count is zero, so this
* tmpnode is dead from the filesystem's viewpoint. However,
* if the tmpnode has any pages associated with it (i.e. if it's
* a normal file with non-zero size), the tmpnode can still be
* discovered by pageout or fsflush via the page vnode pointers.
* In this case we must drop all our locks, truncate the tmpnode,
* and try the whole dance again.
*/
goto top;
}
}
/*
*/
if (tp->tn_xattrdp) {
}
/* Here's our chance to send invalid event while we're between locks */
else
}
/* ARGSUSED2 */
static int
{
return (ENOSPC);
}
return (0);
}
/*
* Return all the pages from [off..off+len] in given file
*/
/* ARGSUSED */
static int
{
int err = 0;
goto out;
}
/*
* Look for holes (no anon slot) in faulting range. If there are
* holes we have to switch to a write lock and fill them in. Swap
* space for holes was already reserved when the file was grown.
*/
/* Size may have changed when lock was dropped */
goto out;
}
}
/* XXX - may allocate mem w. write lock held */
tp->tn_nblocks++;
}
}
}
gethrestime(&now);
out:
return (err);
}
/*
* Called from pvn_getpages to get a particular page.
*/
/*ARGSUSED*/
static int
{
int flags;
int err = 0;
if (pl) {
} else {
}
} else {
/*
* Someone raced in and created the page after we did the
* lookup but before we did the create, so go back and
* try to look it up again.
*/
goto again;
/*
* Fill page from backing store, if any. If none, then
* either this is a newly filled hole or page must have
* been unmodified and freed so just zero it out.
*/
if (err) {
panic("tmp_getapage: no anon slot vp %p "
}
if (pvp) {
}
if (err == 0) {
if (pl)
else
}
}
return (err);
}
/*
* Flags are composed of {B_INVAL, B_DIRTY B_FREE, B_DONTNEED}.
* If len == 0, do from off to EOF.
*/
static int tmp_nopage = 0; /* Don't do tmp_putpage's if set */
/* ARGSUSED */
int
int flags,
{
int err = 0;
int dolock;
if (tmp_nopage)
return (0);
return (ENOSYS);
/*
* This being tmpfs, we don't ever do i/o unless we really
* have to (when we're low on memory and pageout calls us
* with B_ASYNC | B_FREE or the user explicitly asks for it with
* B_DONTNEED).
* XXX to approximately track the mod time like ufs we should
* update the times here. The problem is, once someone does a
* store we never clear the mod bit and do i/o, thus fsflush
* will keep calling us every 30 seconds to do the i/o and we'll
* continually update the mod time. At least we update the mod
* time on the first store because this results in a call to getpage.
*/
(flags & B_DONTNEED) == 0)
return (0);
/*
* If this thread owns the lock, i.e., this thread grabbed it
* as writer somewhere above, then we don't need to grab the
* lock as reader in this routine.
*/
/*
* If this is pageout don't block on the lock as you could deadlock
* when freemem == 0 (another thread has the read lock and is blocked
* creating a page, and a third thread is waiting to get the writers
* lock - waiting writers priority blocks us from getting the read
* lock). Of course, if the only freeable pages are on this tmpnode
* we're hosed anyways. A better solution might be a new lock type.
* Note: ufs has the same problem.
*/
if (curproc == proc_pageout) {
return (ENOMEM);
} else if (dolock)
if (!vn_has_cached_data(vp))
goto out;
if (len == 0) {
if (curproc == proc_pageout) {
panic("tmp: pageout can't block");
/*NOTREACHED*/
}
/* Search the entire vp list for pages >= off. */
} else {
/*
* Loop over all offsets in the range [off...off + len]
* looking for pages to deal with.
*/
/*
* If we are not invalidating, synchronously
* freeing or writing pages use the routine
* page_lookup_nowait() to prevent reclaiming
* them from the free list.
*/
} else {
}
else {
if (err != 0)
break;
}
}
}
/* If invalidating, verify all pages on vnode list are gone. */
panic("tmp_putpage: B_INVAL, pages not gone");
/*NOTREACHED*/
}
out:
/*
* Only reason putapage is going to give us SE_NOSWAP as error
* is when we ask a page to be written to physical backing store
* and there is none. Ignore this because we might be dealing
* with a swap page which does not have any backing store
* on disk. In any other case we won't get this error over here.
*/
err = 0;
return (err);
}
long tmp_putpagecnt, tmp_pagespushed;
/*
* Write out a single page.
* For tmpfs this means choose a physical swap slot and write the page
* out using VOP_PAGEIO. For performance, we attempt to kluster; i.e.,
* we try to find a bunch of other dirty pages adjacent in the file
* and a bunch of contiguous swap slots, and then write all the pages
* out in a single i/o.
*/
/*ARGSUSED*/
static int
int flags,
{
int err;
extern int klustsize;
long tmp_klustsize;
/* Kluster in tmp_klustsize chunks */
/* Get a kluster of pages */
pplist =
/*
* Get a cluster of physical offsets for the pages; the amount we
* get may be some subrange of what we ask for (io_off, io_len).
*/
if (err) {
/*
* If this routine is called as a result of segvn_sync
* operation and we have no physical swap then we can get an
* error here. In such case we would return SE_NOSWAP as error.
* At this point, we expect only SE_NOSWAP.
*/
goto out;
}
}
}
/* Do i/o on the remaining kluster */
}
out:
if (!err) {
if (offp)
if (lenp)
}
return (err);
}
/* ARGSUSED */
static int
{
struct segvn_crargs vn_a;
int error;
#ifdef _ILP32
return (ENOMEM);
#endif
return (ENOSYS);
return (ENXIO);
return (ENODEV);
/*
* Don't allow mapping to locked file
*/
return (EAGAIN);
}
if (error != 0) {
return (error);
}
return (error);
}
/*
* tmp_addmap and tmp_delmap can't be called since the vp
* maintained in the segvn mapping is NULL.
*/
/* ARGSUSED */
static int
{
return (0);
}
/* ARGSUSED */
static int
{
return (0);
}
static int
{
register int i;
int error;
return (EINVAL);
return (0);
}
/*
* Check for any mandatory locks on the range
*/
long save_start;
/*
* "Truncate up" case: need to make sure there
* is no lock beyond current end-of-file. To
* do so, we need to set l_start to the size
* of the file temporarily.
*/
}
return (i ? i : EAGAIN);
}
}
return (error);
}
/* ARGSUSED */
static int
int cmd,
int flag,
{
int error;
return (EINVAL);
return (EFBIG);
}
return (error);
}
/* ARGSUSED */
static int
{
}
/* ARGSUSED2 */
static int
{
if (write_lock) {
} else {
}
return (write_lock);
}
/* ARGSUSED1 */
static void
{
}
static int
int cmd,
{
int error;
switch (cmd) {
case _PC_XATTR_EXISTS:
*valp = 0; /* assume no attributes */
error = 0; /* okay to ask */
if (tp->tn_xattrdp) {
/* do not count "." and ".." */
*valp = 1;
}
} else {
}
break;
case _PC_SATTR_ENABLED:
case _PC_SATTR_EXISTS:
error = 0;
break;
case _PC_TIMESTAMP_RESOLUTION:
/* nanosecond timestamp resolution */
*valp = 1L;
error = 0;
break;
default:
}
return (error);
}
struct vnodeops *tmp_vnodeops;
const fs_operation_def_t tmp_vnodeops_template[] = {
};