/*
       *  linux/fs/open.c
       *
       *  Copyright (C) 1991, 1992  Linus Torvalds
       */
      
      #include <linux/string.h>
      #include <linux/mm.h>
      #include <linux/utime.h>
      #include <linux/file.h>
      #include <linux/smp_lock.h>
      #include <linux/quotaops.h>
      #include <linux/dnotify.h>
      #include <linux/module.h>
      #include <linux/slab.h>
      
      #include <asm/uaccess.h>
      
      #define special_file(m) (S_ISCHR(m)||S_ISBLK(m)||S_ISFIFO(m)||S_ISSOCK(m))
      
  21  int vfs_statfs(struct super_block *sb, struct statfs *buf)
      {
      	int retval = -ENODEV;
      
  25  	if (sb) {
      		retval = -ENOSYS;
  27  		if (sb->s_op && sb->s_op->statfs) {
      			memset(buf, 0, sizeof(struct statfs));
  29  			lock_kernel();
      			retval = sb->s_op->statfs(sb, buf);
  31  			unlock_kernel();
      		}
      	}
  34  	return retval;
      }
      
      
  38  asmlinkage long sys_statfs(const char * path, struct statfs * buf)
      {
      	struct nameidata nd;
      	int error;
      
      	error = user_path_walk(path, &nd);
  44  	if (!error) {
      		struct statfs tmp;
      		error = vfs_statfs(nd.dentry->d_inode->i_sb, &tmp);
  47  		if (!error && copy_to_user(buf, &tmp, sizeof(struct statfs)))
      			error = -EFAULT;
      		path_release(&nd);
      	}
  51  	return error;
      }
      
  54  asmlinkage long sys_fstatfs(unsigned int fd, struct statfs * buf)
      {
      	struct file * file;
      	struct statfs tmp;
      	int error;
      
      	error = -EBADF;
      	file = fget(fd);
  62  	if (!file)
  63  		goto out;
      	error = vfs_statfs(file->f_dentry->d_inode->i_sb, &tmp);
  65  	if (!error && copy_to_user(buf, &tmp, sizeof(struct statfs)))
      		error = -EFAULT;
      	fput(file);
      out:
  69  	return error;
      }
      
  72  int do_truncate(struct dentry *dentry, loff_t length)
      {
      	struct inode *inode = dentry->d_inode;
      	int error;
      	struct iattr newattrs;
      
      	/* Not pretty: "inode->i_size" shouldn't really be signed. But it is. */
  79  	if (length < 0)
  80  		return -EINVAL;
      
      	down(&inode->i_sem);
      	newattrs.ia_size = length;
      	newattrs.ia_valid = ATTR_SIZE | ATTR_CTIME;
      	error = notify_change(dentry, &newattrs);
      	up(&inode->i_sem);
  87  	return error;
      }
      
  90  static inline long do_sys_truncate(const char * path, loff_t length)
      {
      	struct nameidata nd;
      	struct inode * inode;
      	int error;
      
      	error = -EINVAL;
  97  	if (length < 0)	/* sorry, but loff_t says... */
  98  		goto out;
      
      	error = user_path_walk(path, &nd);
 101  	if (error)
 102  		goto out;
      	inode = nd.dentry->d_inode;
      
      	error = -EACCES;
 106  	if (!S_ISREG(inode->i_mode))
 107  		goto dput_and_out;
      
      	error = permission(inode,MAY_WRITE);
 110  	if (error)
 111  		goto dput_and_out;
      
      	error = -EROFS;
 114  	if (IS_RDONLY(inode))
 115  		goto dput_and_out;
      
      	error = -EPERM;
 118  	if (IS_IMMUTABLE(inode) || IS_APPEND(inode))
 119  		goto dput_and_out;
      
      	/*
      	 * Make sure that there are no leases.
      	 */
      	error = get_lease(inode, FMODE_WRITE);
 125  	if (error)
 126  		goto dput_and_out;
      
      	error = get_write_access(inode);
 129  	if (error)
 130  		goto dput_and_out;
      
      	error = locks_verify_truncate(inode, NULL, length);
 133  	if (!error) {
 134  		DQUOT_INIT(inode);
      		error = do_truncate(nd.dentry, length);
      	}
      	put_write_access(inode);
      
      dput_and_out:
      	path_release(&nd);
      out:
 142  	return error;
      }
      
 145  asmlinkage long sys_truncate(const char * path, unsigned long length)
      {
 147  	return do_sys_truncate(path, length);
      }
      
 150  static inline long do_sys_ftruncate(unsigned int fd, loff_t length)
      {
      	struct inode * inode;
      	struct dentry *dentry;
      	struct file * file;
      	int error;
      
      	error = -EINVAL;
 158  	if (length < 0)
 159  		goto out;
      	error = -EBADF;
      	file = fget(fd);
 162  	if (!file)
 163  		goto out;
      	dentry = file->f_dentry;
      	inode = dentry->d_inode;
      	error = -EACCES;
 167  	if (!S_ISREG(inode->i_mode) || !(file->f_mode & FMODE_WRITE))
 168  		goto out_putf;
      	error = -EPERM;
 170  	if (IS_IMMUTABLE(inode) || IS_APPEND(inode))
 171  		goto out_putf;
      
      	error = locks_verify_truncate(inode, file, length);
 174  	if (!error)
      		error = do_truncate(dentry, length);
      out_putf:
      	fput(file);
      out:
 179  	return error;
      }
      
 182  asmlinkage long sys_ftruncate(unsigned int fd, unsigned long length)
      {
 184  	return do_sys_ftruncate(fd, length);
      }
      
      /* LFS versions of truncate are only needed on 32 bit machines */
      #if BITS_PER_LONG == 32
 189  asmlinkage long sys_truncate64(const char * path, loff_t length)
      {
 191  	return do_sys_truncate(path, length);
      }
      
 194  asmlinkage long sys_ftruncate64(unsigned int fd, loff_t length)
      {
 196  	return do_sys_ftruncate(fd, length);
      }
      #endif
      
      #if !(defined(__alpha__) || defined(__ia64__))
      
      /*
       * sys_utime() can be implemented in user-level using sys_utimes().
       * Is this for backwards compatibility?  If so, why not move it
       * into the appropriate arch directory (for those architectures that
       * need it).
       */
      
      /* If times==NULL, set access and modification to current time,
       * must be owner or have write permission.
       * Else, update from *times, must be owner or super user.
       */
 213  asmlinkage long sys_utime(char * filename, struct utimbuf * times)
      {
      	int error;
      	struct nameidata nd;
      	struct inode * inode;
      	struct iattr newattrs;
      
      	error = user_path_walk(filename, &nd);
 221  	if (error)
 222  		goto out;
      	inode = nd.dentry->d_inode;
      
      	error = -EROFS;
 226  	if (IS_RDONLY(inode))
 227  		goto dput_and_out;
      
      	/* Don't worry, the checks are done in inode_change_ok() */
      	newattrs.ia_valid = ATTR_CTIME | ATTR_MTIME | ATTR_ATIME;
 231  	if (times) {
      		error = get_user(newattrs.ia_atime, ×->actime);
 233  		if (!error) 
      			error = get_user(newattrs.ia_mtime, ×->modtime);
 235  		if (error)
 236  			goto dput_and_out;
      
      		newattrs.ia_valid |= ATTR_ATIME_SET | ATTR_MTIME_SET;
 239  	} else {
      		if (current->fsuid != inode->i_uid &&
 241  		    (error = permission(inode,MAY_WRITE)) != 0)
 242  			goto dput_and_out;
      	}
      	error = notify_change(nd.dentry, &newattrs);
      dput_and_out:
      	path_release(&nd);
      out:
 248  	return error;
      }
      
      #endif
      
      /* If times==NULL, set access and modification to current time,
       * must be owner or have write permission.
       * Else, update from *times, must be owner or super user.
       */
 257  asmlinkage long sys_utimes(char * filename, struct timeval * utimes)
      {
      	int error;
      	struct nameidata nd;
      	struct inode * inode;
      	struct iattr newattrs;
      
      	error = user_path_walk(filename, &nd);
      
 266  	if (error)
 267  		goto out;
      	inode = nd.dentry->d_inode;
      
      	error = -EROFS;
 271  	if (IS_RDONLY(inode))
 272  		goto dput_and_out;
      
      	/* Don't worry, the checks are done in inode_change_ok() */
      	newattrs.ia_valid = ATTR_CTIME | ATTR_MTIME | ATTR_ATIME;
 276  	if (utimes) {
      		struct timeval times[2];
      		error = -EFAULT;
 279  		if (copy_from_user(×, utimes, sizeof(times)))
 280  			goto dput_and_out;
      		newattrs.ia_atime = times[0].tv_sec;
      		newattrs.ia_mtime = times[1].tv_sec;
      		newattrs.ia_valid |= ATTR_ATIME_SET | ATTR_MTIME_SET;
 284  	} else {
 285  		if ((error = permission(inode,MAY_WRITE)) != 0)
 286  			goto dput_and_out;
      	}
      	error = notify_change(nd.dentry, &newattrs);
      dput_and_out:
      	path_release(&nd);
      out:
 292  	return error;
      }
      
      /*
       * access() needs to use the real uid/gid, not the effective uid/gid.
       * We do this by temporarily clearing all FS-related capabilities and
       * switching the fsuid/fsgid around to the real ones.
       */
 300  asmlinkage long sys_access(const char * filename, int mode)
      {
      	struct nameidata nd;
      	int old_fsuid, old_fsgid;
      	kernel_cap_t old_cap;
      	int res;
      
 307  	if (mode & ~S_IRWXO)	/* where's F_OK, X_OK, W_OK, R_OK? */
 308  		return -EINVAL;
      
      	old_fsuid = current->fsuid;
      	old_fsgid = current->fsgid;
      	old_cap = current->cap_effective;
      
      	current->fsuid = current->uid;
      	current->fsgid = current->gid;
      
      	/* Clear the capabilities if we switch to a non-root user */
 318  	if (current->uid)
 319  		cap_clear(current->cap_effective);
 320  	else
      		current->cap_effective = current->cap_permitted;
      
      	res = user_path_walk(filename, &nd);
 324  	if (!res) {
      		res = permission(nd.dentry->d_inode, mode);
      		/* SuS v2 requires we report a read only fs too */
      		if(!res && (mode & S_IWOTH) && IS_RDONLY(nd.dentry->d_inode)
 328  		   && !special_file(nd.dentry->d_inode->i_mode))
      			res = -EROFS;
      		path_release(&nd);
      	}
      
      	current->fsuid = old_fsuid;
      	current->fsgid = old_fsgid;
      	current->cap_effective = old_cap;
      
 337  	return res;
      }
      
 340  asmlinkage long sys_chdir(const char * filename)
      {
      	int error;
      	struct nameidata nd;
      	char *name;
      
      	name = getname(filename);
      	error = PTR_ERR(name);
 348  	if (IS_ERR(name))
 349  		goto out;
      
      	error = 0;
 352  	if (path_init(name,LOOKUP_POSITIVE|LOOKUP_FOLLOW|LOOKUP_DIRECTORY,&nd))
      		error = path_walk(name, &nd);
      	putname(name);
 355  	if (error)
 356  		goto out;
      
      	error = permission(nd.dentry->d_inode,MAY_EXEC);
 359  	if (error)
 360  		goto dput_and_out;
      
      	set_fs_pwd(current->fs, nd.mnt, nd.dentry);
      
      dput_and_out:
      	path_release(&nd);
      out:
 367  	return error;
      }
      
 370  asmlinkage long sys_fchdir(unsigned int fd)
      {
      	struct file *file;
      	struct dentry *dentry;
      	struct inode *inode;
      	struct vfsmount *mnt;
      	int error;
      
      	error = -EBADF;
      	file = fget(fd);
 380  	if (!file)
 381  		goto out;
      
      	dentry = file->f_dentry;
      	mnt = file->f_vfsmnt;
      	inode = dentry->d_inode;
      
      	error = -ENOTDIR;
 388  	if (!S_ISDIR(inode->i_mode))
 389  		goto out_putf;
      
      	error = permission(inode, MAY_EXEC);
 392  	if (!error)
      		set_fs_pwd(current->fs, mnt, dentry);
      out_putf:
      	fput(file);
      out:
 397  	return error;
      }
      
 400  asmlinkage long sys_chroot(const char * filename)
      {
      	int error;
      	struct nameidata nd;
      	char *name;
      
      	name = getname(filename);
      	error = PTR_ERR(name);
 408  	if (IS_ERR(name))
 409  		goto out;
      
      	path_init(name, LOOKUP_POSITIVE | LOOKUP_FOLLOW |
      		      LOOKUP_DIRECTORY | LOOKUP_NOALT, &nd);
      	error = path_walk(name, &nd);	
      	putname(name);
 415  	if (error)
 416  		goto out;
      
      	error = permission(nd.dentry->d_inode,MAY_EXEC);
 419  	if (error)
 420  		goto dput_and_out;
      
      	error = -EPERM;
 423  	if (!capable(CAP_SYS_CHROOT))
 424  		goto dput_and_out;
      
      	set_fs_root(current->fs, nd.mnt, nd.dentry);
      	set_fs_altroot();
      	error = 0;
      dput_and_out:
      	path_release(&nd);
      out:
 432  	return error;
      }
      
 435  asmlinkage long sys_fchmod(unsigned int fd, mode_t mode)
      {
      	struct inode * inode;
      	struct dentry * dentry;
      	struct file * file;
      	int err = -EBADF;
      	struct iattr newattrs;
      
      	file = fget(fd);
 444  	if (!file)
 445  		goto out;
      
      	dentry = file->f_dentry;
      	inode = dentry->d_inode;
      
      	err = -EROFS;
 451  	if (IS_RDONLY(inode))
 452  		goto out_putf;
      	err = -EPERM;
 454  	if (IS_IMMUTABLE(inode) || IS_APPEND(inode))
 455  		goto out_putf;
 456  	if (mode == (mode_t) -1)
      		mode = inode->i_mode;
      	newattrs.ia_mode = (mode & S_IALLUGO) | (inode->i_mode & ~S_IALLUGO);
      	newattrs.ia_valid = ATTR_MODE | ATTR_CTIME;
      	err = notify_change(dentry, &newattrs);
      
      out_putf:
      	fput(file);
      out:
 465  	return err;
      }
      
 468  asmlinkage long sys_chmod(const char * filename, mode_t mode)
      {
      	struct nameidata nd;
      	struct inode * inode;
      	int error;
      	struct iattr newattrs;
      
      	error = user_path_walk(filename, &nd);
 476  	if (error)
 477  		goto out;
      	inode = nd.dentry->d_inode;
      
      	error = -EROFS;
 481  	if (IS_RDONLY(inode))
 482  		goto dput_and_out;
      
      	error = -EPERM;
 485  	if (IS_IMMUTABLE(inode) || IS_APPEND(inode))
 486  		goto dput_and_out;
      
 488  	if (mode == (mode_t) -1)
      		mode = inode->i_mode;
      	newattrs.ia_mode = (mode & S_IALLUGO) | (inode->i_mode & ~S_IALLUGO);
      	newattrs.ia_valid = ATTR_MODE | ATTR_CTIME;
      	error = notify_change(nd.dentry, &newattrs);
      
      dput_and_out:
      	path_release(&nd);
      out:
 497  	return error;
      }
      
 500  static int chown_common(struct dentry * dentry, uid_t user, gid_t group)
      {
      	struct inode * inode;
      	int error;
      	struct iattr newattrs;
      
      	error = -ENOENT;
 507  	if (!(inode = dentry->d_inode)) {
      		printk("chown_common: NULL inode\n");
 509  		goto out;
      	}
      	error = -EROFS;
 512  	if (IS_RDONLY(inode))
 513  		goto out;
      	error = -EPERM;
 515  	if (IS_IMMUTABLE(inode) || IS_APPEND(inode))
 516  		goto out;
 517  	if (user == (uid_t) -1)
      		user = inode->i_uid;
 519  	if (group == (gid_t) -1)
      		group = inode->i_gid;
      	newattrs.ia_mode = inode->i_mode;
      	newattrs.ia_uid = user;
      	newattrs.ia_gid = group;
      	newattrs.ia_valid =  ATTR_UID | ATTR_GID | ATTR_CTIME;
      	/*
      	 * If the user or group of a non-directory has been changed by a
      	 * non-root user, remove the setuid bit.
      	 * 19981026	David C Niemi <niemi@tux.org>
      	 *
      	 * Changed this to apply to all users, including root, to avoid
      	 * some races. This is the behavior we had in 2.0. The check for
      	 * non-root was definitely wrong for 2.2 anyway, as it should
      	 * have been using CAP_FSETID rather than fsuid -- 19990830 SD.
      	 */
      	if ((inode->i_mode & S_ISUID) == S_ISUID &&
 536  		!S_ISDIR(inode->i_mode))
      	{
      		newattrs.ia_mode &= ~S_ISUID;
      		newattrs.ia_valid |= ATTR_MODE;
      	}
      	/*
      	 * Likewise, if the user or group of a non-directory has been changed
      	 * by a non-root user, remove the setgid bit UNLESS there is no group
      	 * execute bit (this would be a file marked for mandatory locking).
      	 * 19981026	David C Niemi <niemi@tux.org>
      	 *
      	 * Removed the fsuid check (see the comment above) -- 19990830 SD.
      	 */
      	if (((inode->i_mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP)) 
 550  		&& !S_ISDIR(inode->i_mode))
      	{
      		newattrs.ia_mode &= ~S_ISGID;
      		newattrs.ia_valid |= ATTR_MODE;
      	}
      	error = DQUOT_TRANSFER(dentry, &newattrs);
      out:
 557  	return error;
      }
      
 560  asmlinkage long sys_chown(const char * filename, uid_t user, gid_t group)
      {
      	struct nameidata nd;
      	int error;
      
      	error = user_path_walk(filename, &nd);
 566  	if (!error) {
      		error = chown_common(nd.dentry, user, group);
      		path_release(&nd);
      	}
 570  	return error;
      }
      
 573  asmlinkage long sys_lchown(const char * filename, uid_t user, gid_t group)
      {
      	struct nameidata nd;
      	int error;
      
      	error = user_path_walk_link(filename, &nd);
 579  	if (!error) {
      		error = chown_common(nd.dentry, user, group);
      		path_release(&nd);
      	}
 583  	return error;
      }
      
      
 587  asmlinkage long sys_fchown(unsigned int fd, uid_t user, gid_t group)
      {
      	struct file * file;
      	int error = -EBADF;
      
      	file = fget(fd);
 593  	if (file) {
      		error = chown_common(file->f_dentry, user, group);
      		fput(file);
      	}
 597  	return error;
      }
      
      /*
       * Note that while the flag value (low two bits) for sys_open means:
       *	00 - read-only
       *	01 - write-only
       *	10 - read-write
       *	11 - special
       * it is changed into
       *	00 - no permissions needed
       *	01 - read-permission
       *	10 - write-permission
       *	11 - read-write
       * for the internal routines (ie open_namei()/follow_link() etc). 00 is
       * used by symlinks.
       */
 614  struct file *filp_open(const char * filename, int flags, int mode)
      {
      	int namei_flags, error;
      	struct nameidata nd;
      
      	namei_flags = flags;
 620  	if ((namei_flags+1) & O_ACCMODE)
      		namei_flags++;
 622  	if (namei_flags & O_TRUNC)
      		namei_flags |= 2;
      
      	error = open_namei(filename, namei_flags, mode, &nd);
 626  	if (!error)
 627  		return dentry_open(nd.dentry, nd.mnt, flags);
      
 629  	return ERR_PTR(error);
      }
      
 632  struct file *dentry_open(struct dentry *dentry, struct vfsmount *mnt, int flags)
      {
      	struct file * f;
      	struct inode *inode;
      	int error;
      
      	error = -ENFILE;
      	f = get_empty_filp();
 640  	if (!f)
 641  		goto cleanup_dentry;
      	f->f_flags = flags;
      	f->f_mode = (flags+1) & O_ACCMODE;
      	inode = dentry->d_inode;
 645  	if (f->f_mode & FMODE_WRITE) {
      		error = get_write_access(inode);
 647  		if (error)
 648  			goto cleanup_file;
      	}
      
      	f->f_dentry = dentry;
      	f->f_vfsmnt = mnt;
      	f->f_pos = 0;
      	f->f_reada = 0;
      	f->f_op = fops_get(inode->i_fop);
 656  	if (inode->i_sb)
      		file_move(f, &inode->i_sb->s_files);
 658  	if (f->f_op && f->f_op->open) {
      		error = f->f_op->open(inode,f);
 660  		if (error)
 661  			goto cleanup_all;
      	}
      	f->f_flags &= ~(O_CREAT | O_EXCL | O_NOCTTY | O_TRUNC);
      
 665  	return f;
      
      cleanup_all:
 668  	fops_put(f->f_op);
 669  	if (f->f_mode & FMODE_WRITE)
      		put_write_access(inode);
      	f->f_dentry = NULL;
      	f->f_vfsmnt = NULL;
      cleanup_file:
      	put_filp(f);
      cleanup_dentry:
      	dput(dentry);
      	mntput(mnt);
 678  	return ERR_PTR(error);
      }
      
      /*
       * Find an empty file descriptor entry, and mark it busy.
       */
 684  int get_unused_fd(void)
      {
      	struct files_struct * files = current->files;
      	int fd, error;
      
        	error = -EMFILE;
      	write_lock(&files->file_lock);
      
      repeat:
       	fd = find_next_zero_bit(files->open_fds, 
      				files->max_fdset, 
      				files->next_fd);
      
      	/*
      	 * N.B. For clone tasks sharing a files structure, this test
      	 * will limit the total number of files that can be opened.
      	 */
 701  	if (fd >= current->rlim[RLIMIT_NOFILE].rlim_cur)
 702  		goto out;
      
      	/* Do we need to expand the fdset array? */
 705  	if (fd >= files->max_fdset) {
      		error = expand_fdset(files, fd);
 707  		if (!error) {
      			error = -EMFILE;
 709  			goto repeat;
      		}
 711  		goto out;
      	}
      	
      	/* 
      	 * Check whether we need to expand the fd array.
      	 */
 717  	if (fd >= files->max_fds) {
      		error = expand_fd_array(files, fd);
 719  		if (!error) {
      			error = -EMFILE;
 721  			goto repeat;
      		}
 723  		goto out;
      	}
      
      	FD_SET(fd, files->open_fds);
      	FD_CLR(fd, files->close_on_exec);
      	files->next_fd = fd + 1;
      #if 1
      	/* Sanity check */
 731  	if (files->fd[fd] != NULL) {
      		printk("get_unused_fd: slot %d not NULL!\n", fd);
      		files->fd[fd] = NULL;
      	}
      #endif
      	error = fd;
      
      out:
 739  	write_unlock(&files->file_lock);
 740  	return error;
      }
      
 743  asmlinkage long sys_open(const char * filename, int flags, int mode)
      {
      	char * tmp;
      	int fd, error;
      
      #if BITS_PER_LONG != 32
      	flags |= O_LARGEFILE;
      #endif
      	tmp = getname(filename);
      	fd = PTR_ERR(tmp);
 753  	if (!IS_ERR(tmp)) {
      		fd = get_unused_fd();
 755  		if (fd >= 0) {
      			struct file *f = filp_open(tmp, flags, mode);
      			error = PTR_ERR(f);
 758  			if (IS_ERR(f))
 759  				goto out_error;
      			fd_install(fd, f);
      		}
      out:
      		putname(tmp);
      	}
 765  	return fd;
      
      out_error:
      	put_unused_fd(fd);
      	fd = error;
 770  	goto out;
      }
      
      #ifndef __alpha__
      
      /*
       * For backward compatibility?  Maybe this should be moved
       * into arch/i386 instead?
       */
 779  asmlinkage long sys_creat(const char * pathname, int mode)
      {
 781  	return sys_open(pathname, O_CREAT | O_WRONLY | O_TRUNC, mode);
      }
      
      #endif
      
      /*
       * "id" is the POSIX thread ID. We use the
       * files pointer for this..
       */
 790  int filp_close(struct file *filp, fl_owner_t id)
      {
      	int retval;
      
 794  	if (!file_count(filp)) {
      		printk("VFS: Close: file count is 0\n");
 796  		return 0;
      	}
      	retval = 0;
 799  	if (filp->f_op && filp->f_op->flush) {
 800  		lock_kernel();
      		retval = filp->f_op->flush(filp);
 802  		unlock_kernel();
      	}
      	fcntl_dirnotify(0, filp, 0);
      	locks_remove_posix(filp, id);
      	fput(filp);
 807  	return retval;
      }
      
      /*
       * Careful here! We test whether the file pointer is NULL before
       * releasing the fd. This ensures that one clone task can't release
       * an fd while another clone is opening it.
       */
 815  asmlinkage long sys_close(unsigned int fd)
      {
      	struct file * filp;
      	struct files_struct *files = current->files;
      
      	write_lock(&files->file_lock);
 821  	if (fd >= files->max_fds)
 822  		goto out_unlock;
      	filp = files->fd[fd];
 824  	if (!filp)
 825  		goto out_unlock;
      	files->fd[fd] = NULL;
      	FD_CLR(fd, files->close_on_exec);
      	__put_unused_fd(files, fd);
 829  	write_unlock(&files->file_lock);
 830  	return filp_close(filp, files);
      
      out_unlock:
 833  	write_unlock(&files->file_lock);
 834  	return -EBADF;
      }
      
      /*
       * This routine simulates a hangup on the tty, to arrange that users
       * are given clean terminals at login time.
       */
 841  asmlinkage long sys_vhangup(void)
      {
 843  	if (capable(CAP_SYS_TTY_CONFIG)) {
      		tty_vhangup(current->tty);
 845  		return 0;
      	}
 847  	return -EPERM;
      }