Minix1.5/fs/link.c

Compare this file to the similar file:
Show the results in this format:

/* 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;
}