Minix1.5/fs/link.c
/* This file handles the LINK and UNLINK system calls. It also deals with
* deallocating the storage used by a file when the last UNLINK is done to a
* file and the blocks must be returned to the free block pool.
*
* The entry points into this file are
* do_link: perform the LINK system call
* do_unlink: perform the UNLINK and RMDIR system calls
* do_rename: perform the RENAME system call
* truncate: release all the blocks associated with an inode
*/
#include "fs.h"
#include <sys/stat.h>
#include <string.h>
#include <minix/callnr.h>
#include "buf.h"
#include "file.h"
#include "fproc.h"
#include "inode.h"
#include "param.h"
#define SAME 1000
PRIVATE char dot2[NAME_MAX] = "..\0\0\0\0\0\0\0\0\0\0\0";
/*===========================================================================*
* do_link *
*===========================================================================*/
PUBLIC int do_link()
{
/* Perform the link(name1, name2) system call. */
register struct inode *ip, *rip;
register int r;
char string[NAME_MAX];
struct inode *new_ip;
/* See if 'name' (file to be linked) exists. */
if (fetch_name(name1, name1_length, M1) != OK) return(err_code);
if ( (rip = eat_path(user_path)) == NIL_INODE) return(err_code);
/* Check to see if the file has maximum number of links already. */
r = OK;
if ( (rip->i_nlinks & BYTE) == LINK_MAX) r = EMLINK;
/* Only super_user may link to directories. */
if (r == OK)
if ( (rip->i_mode & I_TYPE) == I_DIRECTORY && !super_user) r = EPERM;
/* If error with 'name', return the inode. */
if (r != OK) {
put_inode(rip);
return(r);
}
/* Does the final directory of 'name2' exist? */
if (fetch_name(name2, name2_length, M1) != OK) {
put_inode(rip);
return(err_code);
}
if ( (ip = last_dir(user_path, string)) == NIL_INODE) r = err_code;
/* If 'name2' exists in full (even if no space) set 'r' to error. */
if (r == OK) {
if ( (new_ip = advance(ip, string)) == NIL_INODE) {
r = err_code;
if (r == ENOENT) r = OK;
} else {
put_inode(new_ip);
r = EEXIST;
}
}
/* Check for links across devices. */
if (r == OK)
if (rip->i_dev != ip->i_dev) r = EXDEV;
/* Try to link. */
if (r == OK)
r = search_dir(ip, string, &rip->i_num, ENTER);
/* If success, register the linking. */
if (r == OK) {
rip->i_nlinks++;
rip->i_dirt = DIRTY;
}
/* Done. Release both inodes. */
put_inode(rip);
put_inode(ip);
return(r);
}
/*===========================================================================*
* do_unlink *
*===========================================================================*/
PUBLIC int do_unlink()
{
/* Perform the unlink(name) or rmdir(name) system call. The code for these two
* is almost the same. They differ only in some condition testing. Unlink()
* may be used by the superuser to do dangerous things; rmdir() may not.
*/
register struct inode *rip;
struct inode *rldirp;
register struct fproc *rfp;
int r, r1;
ino_t numb;
mode_t old_mode;
uid_t old_uid;
char string[NAME_MAX];
/* Get the last directory in the path. */
if (fetch_name(name, name_length, M3) != OK) return(err_code);
if ( (rldirp = last_dir(user_path, string)) == NIL_INODE)
return(err_code);
/* The last directory exists. Does the file also exist? */
r = OK;
if ( (rip = advance(rldirp, string)) == NIL_INODE) r = err_code;
/* If error, return inode. */
if (r != OK) {
put_inode(rldirp);
return(r);
}
old_mode = rip->i_mode; /* save mode; it must be fudged for . and .. */
old_uid = rip->i_uid; /* save uid; it must be fudged for . and .. */
/* Now test if the call is allowed, separately for unlink() and rmdir(). */
if (fs_call == UNLINK) {
/* Only the su may unlink directories, but the su can unlink any dir.*/
if ( (rip->i_mode & I_TYPE) == I_DIRECTORY && !super_user) r = EPERM;
/* Actually try to unlink the file; fails if parent is mode 0 etc. */
if (r == OK) r = search_dir(rldirp, string, (ino_t *) 0, DELETE);
} else {
/* The call is rmdir(). Five conditions have to met for this call:
* - The file must be a directory
* - The directory must be empty (except for . and ..)
* - It must not be /
* - The path must not end in . or ..
* - The directory must not be anybody's working directory
*/
if ( (rip->i_mode & I_TYPE) != I_DIRECTORY) r = ENOTDIR;
if (search_dir(rip, "", &numb, LOOK_UP) == OK) r = ENOTEMPTY;
if (strcmp(user_path, "/") == 0) r = EPERM; /* can't remove root */
if (strcmp(string, ".") == 0 || strcmp(string, "..") == 0) r = EPERM;
for (rfp = &fproc[INIT_PROC_NR + 1]; rfp < &fproc[NR_PROCS]; rfp++) {
if (rfp->fp_workdir == rip || rfp->fp_rootdir == rip) {
r = EBUSY; /* can't remove anybody's working dir*/
break;
}
}
/* Actually try to unlink the file; fails if parent is mode 0 etc. */
if (r == OK) r = search_dir(rldirp, string, (ino_t *) 0, DELETE);
/* If all the conditions have been met, remove . and .. from the dir.
* If the directory is not searchable, it will not be possible to
* unlink . and .. even though this is legal, so change the mode.
*/
if (r == OK) {
rip->i_mode |= S_IRWXU; /* turn on all the owner bits */
rip->i_uid = fp->fp_effuid; /* may not fail due to uid */
if ( (r = search_dir(rip, ".", (ino_t *) 0, DELETE)) == OK)
rip->i_nlinks --; /* . pts to dir being removed*/
if ( (r1 = search_dir(rip, "..", (ino_t *) 0, DELETE)) == OK)
rldirp->i_nlinks--; /* .. points to parent dir */
rip->i_dirt = DIRTY;
rldirp->i_dirt = DIRTY;
if (r1 != OK) r = r1;
rip->i_mode = old_mode; /* restore the old mode */
rip->i_uid = old_uid;
}
}
if (r == OK) {
rip->i_nlinks--;
rip->i_dirt = DIRTY;
}
/* If unlink was possible, it has been done, otherwise it has not. */
rip->i_mode = old_mode; /* restore mode in case it has been changed */
put_inode(rip);
put_inode(rldirp);
return(r);
}
/*===========================================================================*
* do_rename *
*===========================================================================*/
PUBLIC int do_rename()
{
/* Perform the rename(name1, name2) system call. */
struct inode *old_dirp, *old_ip; /* ptrs to old dir, file inodes */
struct inode *new_dirp, *new_ip; /* ptrs to new dir, file inodes */
int r = OK; /* error flag; initially no error */
int odir, ndir; /* TRUE iff {old|new} file is dir */
char string[NAME_MAX+1], old_string[NAME_MAX+1];
char old_name[PATH_MAX+1];
ino_t numb;
/* See if 'name1' (existing file) exists. Get dir and file inodes. */
if (fetch_name(name1, name1_length, M1) != OK) return(err_code);
if ( (old_dirp = last_dir(user_path, string)) == NIL_INODE) return(err_code);
if ( (old_ip = advance(old_dirp, string)) == NIL_INODE) r = err_code;
strcpy(old_name, user_path); /* save the old name here */
strcpy(old_string, string); /* save last component of the name here */
/* See if 'name2' (new name) exists. Get dir and file inodes. */
if (fetch_name(name2, name2_length, M1) != OK) r = err_code;
if ( (new_dirp = last_dir(user_path, string)) == NIL_INODE) r = err_code;
new_ip = advance(new_dirp, string); /* not required to exist */
/* If it is ok, check for a variety of possible errors. */
if (r == OK) {
/* The old path must not be a prefix of the new one. */
if (strncmp(old_name, user_path, strlen(old_name)) == 0) r = EINVAL;
/* The old path must not be . or .. */
if (strcmp(old_name, ".")==0 || strcmp(old_name, "..")==0) r = EINVAL;
/* Both directories must be on the same device. */
if (old_dirp->i_dev != new_dirp->i_dev) r = EXDEV;
/* Both directories must be writable and searchable. */
if (forbidden(old_dirp, W_BIT | X_BIT, 0)) r = EACCES;
if (forbidden(new_dirp, W_BIT | X_BIT, 0)) r = EACCES;
/* Some tests apply only if the new path exists. */
odir = S_ISDIR(old_ip->i_mode); /* TRUE iff old file is dir */
if (new_ip != NIL_INODE) {
ndir = S_ISDIR(new_ip->i_mode); /* TRUE iff new file is dir */
if (odir == TRUE && ndir == FALSE) r = ENOTDIR;
if (odir == FALSE && ndir == TRUE) r = EISDIR;
if (old_ip->i_num == new_ip->i_num) r = SAME; /* old=new */
if (ndir == TRUE) {
if (search_dir(new_ip, "", &numb, LOOK_UP) == OK)
r = ENOTEMPTY;
}
}
}
/* The rename will probably work. The only thing that could still go wrong
* is being unable to make the new directory entry (directory has to grow by
* one block and cannot because the disk is completely full). Two cases can
* be distinguished now, depending on whether the 'new' entry exists or not.
* Case 1 ('new' entry does not exist): create a dir entry for it.
* Case 2 ('new' entry exists): update inum in existing dir entry.
*/
if (r == OK) {
/* For both cases we need the number of the old inode entry. */
numb = old_ip->i_num; /* inode number of old file */
/* For case 1, make new entry; for case 2, delete then enter. */
if (new_ip == NIL_INODE) {
/* There is no entry for 'new'. Make one.*/
r = search_dir(new_dirp, string, &numb, ENTER); /* can fail */
if (r == OK && odir) {
new_dirp->i_nlinks++; /* new entry created */
new_dirp->i_dirt = DIRTY;
}
} else {
/* There is already an entry for 'new'. Slot can be reused. */
(void) search_dir(new_dirp, string, (ino_t *) 0, DELETE);
(void) search_dir(new_dirp, string, &numb, ENTER);
new_ip->i_nlinks--; /* entry deleted from parent's dir */
new_ip->i_dirt = DIRTY;
if (odir) new_ip->i_nlinks--; /* new_ip's . is going away */
}
/* Delete the directory entry for 'old', but do not change link ct. */
if (r == OK) {
if (odir) {
old_dirp->i_nlinks--; /* old_ip's .. is going away */
old_dirp->i_dirt = DIRTY;
numb = new_dirp->i_num;
(void) search_dir(old_ip, "..", (ino_t *) 0, DELETE);
(void) search_dir(old_ip, dot2, &numb ,ENTER);
}
(void) search_dir(old_dirp, old_string, (ino_t *)0, DELETE);
/* Mark times for updating later. */
old_dirp->i_update = CTIME | MTIME;
new_dirp->i_update = CTIME | MTIME;
}
}
/* Release the inodes. */
put_inode(old_dirp);
put_inode(old_ip);
put_inode(new_dirp);
put_inode(new_ip);
return(r == SAME ? OK : r);
}
/*===========================================================================*
* truncate *
*===========================================================================*/
PUBLIC void truncate(rip)
register struct inode *rip; /* pointer to inode to be truncated */
{
/* Remove all the zones from the inode 'rip' and mark it dirty. */
register block_nr b;
register zone_nr z, *iz;
off_t position;
zone_type zone_size;
int scale, file_type, waspipe;
struct buf *bp;
dev_t dev;
file_type = rip->i_mode & S_IFMT; /* check to see if file is special */
if (file_type == S_IFCHR || file_type == S_IFBLK) return;
dev = rip->i_dev; /* device on which inode resides */
scale = scale_factor(rip);
zone_size = (zone_type) BLOCK_SIZE << scale;
if (waspipe = (rip->i_pipe == I_PIPE))
rip->i_size = PIPE_SIZE; /* pipes can shrink */
/* Step through the file a zone at a time, finding and freeing the zones. */
for (position = 0; position < rip->i_size; position += zone_size) {
if ( (b = read_map(rip, position)) != NO_BLOCK) {
z = (zone_nr) b >> scale;
free_zone(dev, z);
}
}
/* All the data zones have been freed. Now free the indirect zones. */
free_zone(dev, rip->i_zone[NR_DZONE_NUM]); /* single indirect zone */
if ( (z = rip->i_zone[NR_DZONE_NUM+1]) != NO_ZONE) {
b = (block_nr) z << scale;
bp = get_block(dev, b, NORMAL); /* get double indirect zone */
for (iz = &bp->b_ind[0]; iz < &bp->b_ind[NR_INDIRECTS]; iz++) {
free_zone(dev, *iz);
}
/* Now free the double indirect zone itself. */
put_block(bp, INDIRECT_BLOCK);
free_zone(dev, z);
}
/* Leave zone numbers for de(1) to recover file after an unlink(2). */
if (waspipe)
wipe_inode(rip); /* clear out inode for pipes */
rip->i_dirt = DIRTY;
}