* Copyright (c) 1982, 1986, 1989 Regents of the University of California.
* Redistribution and use in source and binary forms are permitted
* provided that the above copyright notice and this paragraph are
* duplicated in all such forms and that any documentation,
* advertising materials, and other materials related to such
* distribution and use acknowledge that the software was developed
* by the University of California, Berkeley. The name of the
* University may not be used to endorse or promote products derived
* from this software without specific prior written permission.
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
* @(#)ufs_vnops.c 7.18 (Berkeley) %G%
#include "../ufs/inode.h"
#include "../ufs/quota.h"
* Global vfs data structures for ufs
struct vnodeops ufs_vnodeops
= {
enum vtype iftovt_tab
[8] = {
VNON
, VCHR
, VDIR
, VBLK
, VREG
, VLNK
, VSOCK
, VBAD
,
0, IFREG
, IFDIR
, IFBLK
, IFCHR
, IFLNK
, IFSOCK
, IFMT
,
if (error
= maknode(MAKEIMODE(vap
->va_type
, vap
->va_mode
), ndp
, &ip
))
ufs_mknod(ndp
, vap
, cred
)
if (error
= maknode(MAKEIMODE(vap
->va_type
, vap
->va_mode
), ndp
, &ip
))
* Want to be able to use this to make badblock
* inodes, so don't truncate the dev number.
ITOV(ip
)->v_rdev
= ip
->i_rdev
= vap
->va_rdev
;
ip
->i_flag
|= IACC
|IUPD
|ICHG
;
* Remove inode so that it will be reloaded by iget and
* checked to see if it is an alias of an existing entry
* Update the times on the inode.
ufs_close(vp
, fflag
, cred
)
register struct inode
*ip
= VTOI(vp
);
if (vp
->v_count
> 1 && !(ip
->i_flag
& ILOCKED
))
ITIMES(ip
, &time
, &time
);
ufs_access(vp
, mode
, cred
)
return (iaccess(VTOI(vp
), mode
, cred
));
ufs_getattr(vp
, vap
, cred
)
register struct vattr
*vap
;
register struct inode
*ip
= VTOI(vp
);
ITIMES(ip
, &time
, &time
);
vap
->va_fsid
= ip
->i_dev
;
vap
->va_fileid
= ip
->i_number
;
vap
->va_mode
= ip
->i_mode
& ~IFMT
;
vap
->va_nlink
= ip
->i_nlink
;
vap
->va_rdev
= (dev_t
)ip
->i_rdev
;
vap
->va_size
= ip
->i_ic
.ic_size
.val
[0];
vap
->va_size1
= ip
->i_ic
.ic_size
.val
[1];
vap
->va_atime
.tv_sec
= ip
->i_atime
;
vap
->va_atime
.tv_usec
= 0;
vap
->va_mtime
.tv_sec
= ip
->i_mtime
;
vap
->va_mtime
.tv_usec
= 0;
vap
->va_ctime
.tv_sec
= ip
->i_ctime
;
vap
->va_ctime
.tv_usec
= 0;
vap
->va_flags
= ip
->i_flags
;
/* this doesn't belong here */
vap
->va_blocksize
= BLKDEV_IOSIZE
;
else if (vp
->v_type
== VCHR
)
vap
->va_blocksize
= MAXBSIZE
;
vap
->va_blocksize
= ip
->i_fs
->fs_bsize
;
vap
->va_bytes
= dbtob(ip
->i_blocks
);
vap
->va_type
= vp
->v_type
;
* Set attribute vnode op. called from several syscalls
ufs_setattr(vp
, vap
, cred
)
register struct vnode
*vp
;
register struct vattr
*vap
;
register struct ucred
*cred
;
register struct inode
*ip
= VTOI(vp
);
* Check for unsetable attributes.
if ((vap
->va_type
!= VNON
) || (vap
->va_nlink
!= VNOVAL
) ||
(vap
->va_fsid
!= VNOVAL
) || (vap
->va_fileid
!= VNOVAL
) ||
(vap
->va_blocksize
!= VNOVAL
) || (vap
->va_rdev
!= VNOVAL
) ||
((int)vap
->va_bytes
!= VNOVAL
) || (vap
->va_gen
!= VNOVAL
)) {
* Go through the fields and update iff not VNOVAL.
if (vap
->va_uid
!= (u_short
)VNOVAL
|| vap
->va_gid
!= (u_short
)VNOVAL
)
if (error
= chown1(vp
, vap
->va_uid
, vap
->va_gid
, cred
))
if (vap
->va_size
!= VNOVAL
) {
if (error
= itrunc(ip
, vap
->va_size
))
if (vap
->va_atime
.tv_sec
!= VNOVAL
|| vap
->va_mtime
.tv_sec
!= VNOVAL
) {
if (cred
->cr_uid
!= ip
->i_uid
&&
(error
= suser(cred
, &u
.u_acflag
)))
if (vap
->va_atime
.tv_sec
!= VNOVAL
)
if (vap
->va_mtime
.tv_sec
!= VNOVAL
)
if (error
= iupdat(ip
, &vap
->va_atime
, &vap
->va_mtime
, 1))
if (vap
->va_mode
!= (u_short
)VNOVAL
)
error
= chmod1(vp
, (int)vap
->va_mode
, cred
);
if (vap
->va_flags
!= VNOVAL
) {
if (cred
->cr_uid
!= ip
->i_uid
&&
(error
= suser(cred
, &u
.u_acflag
)))
ip
->i_flags
= vap
->va_flags
;
ip
->i_flags
&= 0xffff0000;
ip
->i_flags
|= (vap
->va_flags
& 0xffff);
* Change the mode on a file.
* Inode must be locked before calling.
register struct vnode
*vp
;
register struct inode
*ip
= VTOI(vp
);
if (cred
->cr_uid
!= ip
->i_uid
&&
(error
= suser(cred
, &u
.u_acflag
)))
if (!groupmember(ip
->i_gid
, cred
))
ip
->i_mode
|= mode
& 07777;
if ((vp
->v_flag
& VTEXT
) && (ip
->i_mode
& ISVTX
) == 0)
* Perform chown operation on inode ip;
* inode must be locked prior to call.
chown1(vp
, uid
, gid
, cred
)
register struct vnode
*vp
;
register struct inode
*ip
= VTOI(vp
);
if (uid
== (u_short
)VNOVAL
)
if (gid
== (u_short
)VNOVAL
)
* If we don't own the file, are trying to change the owner
* of the file, or are not a member of the target group,
* the caller must be superuser or the call fails.
if ((cred
->cr_uid
!= ip
->i_uid
|| uid
!= ip
->i_uid
||
!groupmember((gid_t
)gid
, cred
)) &&
(error
= suser(cred
, &u
.u_acflag
)))
if (ip
->i_uid
== uid
) /* this just speeds things a little */
(void) chkdq(ip
, -change
, 1);
(void) chkiq(ip
->i_dev
, ip
, ip
->i_uid
, 1);
if (ip
->i_uid
!= uid
&& cred
->cr_uid
!= 0)
if (ip
->i_gid
!= gid
&& cred
->cr_uid
!= 0)
ip
->i_dquot
= inoquota(ip
);
(void) chkdq(ip
, change
, 1);
(void) chkiq(ip
->i_dev
, (struct inode
*)NULL
, (uid_t
)uid
, 1);
return (u
.u_error
); /* should == 0 ALWAYS !! */
ufs_ioctl(vp
, com
, data
, fflag
, cred
)
printf("ufs_ioctl called with type %d\n", vp
->v_type
);
ufs_select(vp
, which
, cred
)
printf("ufs_select called with type %d\n", vp
->v_type
);
* NB Currently unsupported.
ufs_mmap(vp
, fflags
, cred
)
ufs_fsync(vp
, fflags
, cred
)
register struct inode
*ip
= VTOI(vp
);
* Nothing to do, so just return.
ufs_seek(vp
, oldoff
, newoff
, cred
)
* Hard to avoid races here, especially
* in unlinking directories.
register struct inode
*ip
, *dp
;
register struct vnode
*vp
;
register struct nameidata
*ndp
;
register struct inode
*ip
= VTOI(vp
);
if (ip
->i_nlink
== LINK_MAX
- 1) {
error
= iupdat(ip
, &time
, &time
, 1);
error
= direnter(ip
, ndp
);
* but ``atomically''. Can't do full commit without saving state in the
* inode on disk which isn't feasible at this time. Best we can do is
* always guarantee the target exists.
* 1) Bump link count on source while we're linking it to the
* target. This also ensure the inode won't be deleted out
* from underneath us while we work (it may be truncated by
* a concurrent `trunc' or `open' for creation).
* 2) Link source to destination. If destination already exists,
* 3) Unlink source reference to inode if still around. If a
* directory was moved and the parent of the destination
* is different from the source, patch the ".." entry in the
register struct nameidata
*fndp
, *tndp
;
register struct inode
*ip
, *xp
, *dp
;
struct dirtemplate dirbuf
;
int doingdirectory
= 0, oldparent
= 0, newparent
= 0;
if ((ip
->i_mode
&IFMT
) == IFDIR
) {
register struct direct
*d
= &fndp
->ni_dent
;
* Avoid ".", "..", and aliases of "." for obvious reasons.
if ((d
->d_namlen
== 1 && d
->d_name
[0] == '.') || dp
== ip
||
fndp
->ni_isdotdot
|| (ip
->i_flag
& IRENAME
)) {
oldparent
= dp
->i_number
;
* 1) Bump link count while we're moving stuff
* around. If we crash somewhere before
* completing our work, the link count
* may be wrong, but correctable.
error
= iupdat(ip
, &time
, &time
, 1);
* When the target exists, both the directory
* and target vnodes are returned locked.
* If ".." must be changed (ie the directory gets a new
* parent) then the source directory must not be in the
* directory heirarchy above the target, as this would
* orphan everything below the source directory. Also
* the user must have write permission in the source so
* as to be able to change "..". We must repeat the call
* to namei, as the parent directory is unlocked by the
if (oldparent
!= dp
->i_number
)
newparent
= dp
->i_number
;
if (doingdirectory
&& newparent
) {
if (error
= iaccess(ip
, IWRITE
, tndp
->ni_cred
))
tndp
->ni_nameiop
= RENAME
| LOCKPARENT
| LOCKLEAF
| NOCACHE
;
if (error
= checkpath(ip
, dp
, tndp
->ni_cred
))
} while (dp
!= VTOI(tndp
->ni_dvp
));
* 2) If target doesn't exist, link the target
* to the source and unlink the source.
* Otherwise, rewrite the target directory
* entry to reference the source inode and
* expunge the original entry's existence.
if (dp
->i_dev
!= ip
->i_dev
)
* Account for ".." in new directory.
* When source and destination have the same
* parent we don't fool with the link count.
if (doingdirectory
&& newparent
) {
error
= iupdat(dp
, &time
, &time
, 1);
if (error
= direnter(ip
, tndp
))
if (xp
->i_dev
!= dp
->i_dev
|| xp
->i_dev
!= ip
->i_dev
)
* Short circuit rename(foo, foo).
if (xp
->i_number
== ip
->i_number
)
panic("rename: same file");
* If the parent directory is "sticky", then the user must
* own the parent directory, or the destination of the rename,
* otherwise the destination may not be changed (except by
* root). This implements append-only directories.
if ((dp
->i_mode
& ISVTX
) && tndp
->ni_cred
->cr_uid
!= 0 &&
tndp
->ni_cred
->cr_uid
!= dp
->i_uid
&&
xp
->i_uid
!= tndp
->ni_cred
->cr_uid
) {
* Target must be empty if a directory
* and have no links to it.
* Also, insure source and target are
* compatible (both directories, or both
if ((xp
->i_mode
&IFMT
) == IFDIR
) {
if (!dirempty(xp
, dp
->i_number
, tndp
->ni_cred
) ||
} else if (doingdirectory
) {
if (error
= dirrewrite(dp
, ip
, tndp
))
* Adjust the link count of the target to
* reflect the dirrewrite above. If this is
* a directory it is empty and there are
* no links to it, so we can squash the inode and
* any space associated with it. We disallowed
* renaming over top of a directory with links to
* it above, as the remaining link would point to
* a directory without "." or ".." entries.
panic("rename: linked directory");
error
= itrunc(xp
, (u_long
)0);
fndp
->ni_nameiop
= DELETE
| LOCKPARENT
| LOCKLEAF
;
if (fndp
->ni_vp
!= NULL
) {
if (fndp
->ni_dvp
!= NULL
)
* Ensure that the directory entry still exists and has not
* changed while the new name has been entered. If the source is
* a file then the entry may have been unlinked or renamed. In
* either case there is no further work to be done. If the source
* is a directory then it cannot have been rmdir'ed; its link
* count of three would cause a rmdir to fail with ENOTEMPTY.
* The IRENAME flag ensures that it cannot be moved by another
panic("rename: lost dir entry");
* If the source is a directory with a
* new parent, the link count of the old
* parent directory must be decremented
* and ".." set to point to the new parent.
if (doingdirectory
&& newparent
) {
error
= rdwri(UIO_READ
, xp
, (caddr_t
)&dirbuf
,
sizeof (struct dirtemplate
), (off_t
)0,
UIO_SYSSPACE
, tndp
->ni_cred
, (int *)0);
if (dirbuf
.dotdot_namlen
!= 2 ||
dirbuf
.dotdot_name
[0] != '.' ||
dirbuf
.dotdot_name
[1] != '.') {
printf("rename: mangled dir\n");
dirbuf
.dotdot_ino
= newparent
;
(void) rdwri(UIO_WRITE
, xp
,
sizeof (struct dirtemplate
),
tndp
->ni_cred
, (int *)0);
* A virgin directory (no blushing please).
struct dirtemplate mastertemplate
= {
0, DIRBLKSIZ
- 12, 2, ".."
register struct inode
*ip
, *dp
;
struct dirtemplate dirtemplate
;
dmode
= vap
->va_mode
&0777;
* Must simulate part of maknode here
* in order to acquire the inode, but
* not have it entered in the parent
* directory. The entry is made later
* after writing "." and ".." entries out.
error
= ialloc(dp
, dirpref(dp
->i_fs
), dmode
, &tip
);
if (ip
->i_dquot
!= NODQUOT
)
ip
->i_flag
|= IACC
|IUPD
|ICHG
;
ITOV(ip
)->v_type
= VDIR
; /* Rest init'd in iget() */
ip
->i_uid
= ndp
->ni_cred
->cr_uid
;
ip
->i_dquot
= inoquota(ip
);
error
= iupdat(ip
, &time
, &time
, 1);
* Bump link count in parent directory
* to reflect work done below. Should
* be done before reference is created
* so reparation is possible if we crash.
error
= iupdat(dp
, &time
, &time
, 1);
* Initialize directory with "."
* and ".." from static template.
dirtemplate
= mastertemplate
;
dirtemplate
.dot_ino
= ip
->i_number
;
dirtemplate
.dotdot_ino
= dp
->i_number
;
error
= rdwri(UIO_WRITE
, ip
, (caddr_t
)&dirtemplate
,
sizeof (dirtemplate
), (off_t
)0, UIO_SYSSPACE
,
if (DIRBLKSIZ
> dp
->i_fs
->fs_fsize
)
panic("mkdir: blksize"); /* XXX - should grow w/balloc() */
* Directory all set up, now
* install the entry for it in
error
= direnter(ip
, ndp
);
ndp
->ni_nameiop
= LOOKUP
| NOCACHE
;
* No need to do an explicit itrunc here,
* vrele will do this for us because we set
register struct nameidata
*ndp
;
register struct inode
*ip
, *dp
;
* Verify the directory is empty (and valid).
* (Rmdir ".." won't be valid since
* ".." will contain a reference to
* the current directory and thus be
if (ip
->i_nlink
!= 2 || !dirempty(ip
, dp
->i_number
, ndp
->ni_cred
)) {
* Delete reference to directory before purging
* inode. If we crash in between, the directory
* will be reattached to lost+found,
if (error
= dirremove(ndp
))
* Truncate inode. The only stuff left
* in the directory is "." and "..". The
* "." reference is inconsequential since
* we're quashing it. The ".." reference
* has already been adjusted above. We've
* removed the "." reference and the reference
* in the parent directory, but there may be
* other hard links so decrement by 2 and
* worry about them later.
error
= itrunc(ip
, (u_long
)0);
* symlink -- make a symbolic link
ufs_symlink(ndp
, vap
, target
)
error
= maknode(IFLNK
| vap
->va_mode
, ndp
, &ip
);
error
= rdwri(UIO_WRITE
, ip
, target
, strlen(target
), (off_t
)0,
UIO_SYSSPACE
, ndp
->ni_cred
, (int *)0);
* Vnode op for read and write
ufs_readdir(vp
, uio
, offp
, cred
)
register struct uio
*uio
;
register struct inode
*ip
= VTOI(vp
);
count
&= ~(DIRBLKSIZ
- 1);
if (vp
->v_type
!= VDIR
|| uio
->uio_iovcnt
!= 1 ||
(count
< DIRBLKSIZ
) || (uio
->uio_offset
& (DIRBLKSIZ
-1))) {
uio
->uio_iov
->iov_len
= count
;
error
= readip(ip
, uio
, cred
);
*offp
+= count
- uio
->uio_resid
;
* Return target name of a symbolic link
ufs_readlink(vp
, uiop
, cred
)
return (readip(VTOI(vp
), uiop
, cred
));
* Ufs abort op, called after namei() when a CREATE/DELETE isn't actually
* done. Iff ni_vp/ni_dvp not null and locked, unlock.
register struct nameidata
*ndp
;
register struct inode
*ip
;
if (ip
->i_flag
& ILOCKED
)
if (ip
->i_flag
& ILOCKED
)
register struct inode
*ip
= VTOI(vp
);
register struct inode
*ip
= VTOI(vp
);
if (!(ip
->i_flag
& ILOCKED
))
panic("ufs_unlock NOT LOCKED");
ufs_bmap(vp
, bn
, vpp
, bnp
)
struct inode
*ip
= VTOI(vp
);
return (bmap(ip
, bn
, bnp
, (daddr_t
*)0, (int *)0));
* Just call the device strategy routine
(*bdevsw
[major(bp
->b_dev
)].d_strategy
)(bp
);
register struct nameidata
*ndp
;
register struct inode
*ip
;
register struct inode
*pdir
= VTOI(ndp
->ni_dvp
);
if ((mode
& IFMT
) == IFDIR
)
ipref
= dirpref(pdir
->i_fs
);
error
= ialloc(pdir
, ipref
, mode
, &tip
);
if (ip
->i_dquot
!= NODQUOT
)
ip
->i_flag
|= IACC
|IUPD
|ICHG
;
ITOV(ip
)->v_type
= IFTOVT(mode
); /* Rest init'd in iget() */
ip
->i_uid
= ndp
->ni_cred
->cr_uid
;
if ((ip
->i_mode
& ISGID
) && !groupmember(ip
->i_gid
, ndp
->ni_cred
) &&
suser(ndp
->ni_cred
, NULL
))
ip
->i_dquot
= inoquota(ip
);
* Make sure inode goes to disk before directory entry.
if ((error
= iupdat(ip
, &time
, &time
, 1)) ||
(error
= direnter(ip
, ndp
))) {
* Write error occurred trying to update the inode
* or the directory so must deallocate the inode.