/*
       *  linux/fs/readdir.c
       *
       *  Copyright (C) 1995  Linus Torvalds
       */
      
      #include <linux/sched.h>
      #include <linux/mm.h>
      #include <linux/errno.h>
      #include <linux/stat.h>
      #include <linux/file.h>
      #include <linux/smp_lock.h>
      
      #include <asm/uaccess.h>
      
  16  int vfs_readdir(struct file *file, filldir_t filler, void *buf)
      {
      	struct inode *inode = file->f_dentry->d_inode;
      	int res = -ENOTDIR;
  20  	if (!file->f_op || !file->f_op->readdir)
  21  		goto out;
      	down(&inode->i_sem);
      	down(&inode->i_zombie);
      	res = -ENOENT;
  25  	if (!IS_DEADDIR(inode)) {
  26  		lock_kernel();
      		res = file->f_op->readdir(file, buf, filler);
  28  		unlock_kernel();
      	}
      	up(&inode->i_zombie);
      	up(&inode->i_sem);
      out:
  33  	return res;
      }
      
      /*
       * Directory is locked and all positive dentries in it are safe, since
       * for ramfs-type trees they can't go away without unlink() or rmdir(),
       * both impossible due to the lock on directory.
       */
      
  42  int dcache_readdir(struct file * filp, void * dirent, filldir_t filldir)
      {
      	int i;
      	struct dentry *dentry = filp->f_dentry;
      
      	i = filp->f_pos;
  48  	switch (i) {
  49  		case 0:
  50  			if (filldir(dirent, ".", 1, i, dentry->d_inode->i_ino, DT_DIR) < 0)
  51  				break;
      			i++;
      			filp->f_pos++;
      			/* fallthrough */
  55  		case 1:
  56  			if (filldir(dirent, "..", 2, i, dentry->d_parent->d_inode->i_ino, DT_DIR) < 0)
  57  				break;
      			i++;
      			filp->f_pos++;
      			/* fallthrough */
  61  		default: {
      			struct list_head *list;
      			int j = i-2;
      
      			spin_lock(&dcache_lock);
      			list = dentry->d_subdirs.next;
      
  68  			for (;;) {
  69  				if (list == &dentry->d_subdirs) {
  70  					spin_unlock(&dcache_lock);
  71  					return 0;
      				}
  73  				if (!j)
  74  					break;
      				j--;
      				list = list->next;
      			}
      
  79  			while(1) {
      				struct dentry *de = list_entry(list, struct dentry, d_child);
      
  82  				if (!list_empty(&de->d_hash) && de->d_inode) {
  83  					spin_unlock(&dcache_lock);
  84  					if (filldir(dirent, de->d_name.name, de->d_name.len, filp->f_pos, de->d_inode->i_ino, DT_UNKNOWN) < 0)
  85  						break;
      					spin_lock(&dcache_lock);
      				}
      				filp->f_pos++;
      				list = list->next;
  90  				if (list != &dentry->d_subdirs)
  91  					continue;
  92  				spin_unlock(&dcache_lock);
  93  				break;
      			}
      		}
      	}
  97  	return 0;
      }
      
      /*
       * Traditional linux readdir() handling..
       *
       * "count=1" is a special case, meaning that the buffer is one
       * dirent-structure in size and that the code can't handle more
       * anyway. Thus the special "fillonedir()" function for that
       * case (the low-level handlers don't need to care about this).
       */
      #define NAME_OFFSET(de) ((int) ((de)->d_name - (char *) (de)))
      #define ROUND_UP(x) (((x)+sizeof(long)-1) & ~(sizeof(long)-1))
      
      #ifndef __ia64__
      
      struct old_linux_dirent {
      	unsigned long	d_ino;
      	unsigned long	d_offset;
      	unsigned short	d_namlen;
      	char		d_name[1];
      };
      
      struct readdir_callback {
      	struct old_linux_dirent * dirent;
      	int count;
      };
      
 125  static int fillonedir(void * __buf, const char * name, int namlen, off_t offset,
      		      ino_t ino, unsigned int d_type)
      {
      	struct readdir_callback * buf = (struct readdir_callback *) __buf;
      	struct old_linux_dirent * dirent;
      
 131  	if (buf->count)
 132  		return -EINVAL;
      	buf->count++;
      	dirent = buf->dirent;
      	put_user(ino, &dirent->d_ino);
      	put_user(offset, &dirent->d_offset);
      	put_user(namlen, &dirent->d_namlen);
      	copy_to_user(dirent->d_name, name, namlen);
      	put_user(0, dirent->d_name + namlen);
 140  	return 0;
      }
      
 143  asmlinkage int old_readdir(unsigned int fd, void * dirent, unsigned int count)
      {
      	int error;
      	struct file * file;
      	struct readdir_callback buf;
      
      	error = -EBADF;
      	file = fget(fd);
 151  	if (!file)
 152  		goto out;
      
      	buf.count = 0;
      	buf.dirent = dirent;
      
      	error = vfs_readdir(file, fillonedir, &buf);
 158  	if (error >= 0)
      		error = buf.count;
      
      	fput(file);
      out:
 163  	return error;
      }
      
      #endif /* !__ia64__ */
      
      /*
       * New, all-improved, singing, dancing, iBCS2-compliant getdents()
       * interface. 
       */
      struct linux_dirent {
      	unsigned long	d_ino;
      	unsigned long	d_off;
      	unsigned short	d_reclen;
      	char		d_name[1];
      };
      
      struct getdents_callback {
      	struct linux_dirent * current_dir;
      	struct linux_dirent * previous;
      	int count;
      	int error;
      };
      
 186  static int filldir(void * __buf, const char * name, int namlen, off_t offset,
      		   ino_t ino, unsigned int d_type)
      {
      	struct linux_dirent * dirent;
      	struct getdents_callback * buf = (struct getdents_callback *) __buf;
      	int reclen = ROUND_UP(NAME_OFFSET(dirent) + namlen + 1);
      
      	buf->error = -EINVAL;	/* only used if we fail.. */
 194  	if (reclen > buf->count)
 195  		return -EINVAL;
      	dirent = buf->previous;
 197  	if (dirent)
      		put_user(offset, &dirent->d_off);
      	dirent = buf->current_dir;
      	buf->previous = dirent;
      	put_user(ino, &dirent->d_ino);
      	put_user(reclen, &dirent->d_reclen);
      	copy_to_user(dirent->d_name, name, namlen);
      	put_user(0, dirent->d_name + namlen);
      	((char *) dirent) += reclen;
      	buf->current_dir = dirent;
      	buf->count -= reclen;
 208  	return 0;
      }
      
 211  asmlinkage long sys_getdents(unsigned int fd, void * dirent, unsigned int count)
      {
      	struct file * file;
      	struct linux_dirent * lastdirent;
      	struct getdents_callback buf;
      	int error;
      
      	error = -EBADF;
      	file = fget(fd);
 220  	if (!file)
 221  		goto out;
      
      	buf.current_dir = (struct linux_dirent *) dirent;
      	buf.previous = NULL;
      	buf.count = count;
      	buf.error = 0;
      
      	error = vfs_readdir(file, filldir, &buf);
 229  	if (error < 0)
 230  		goto out_putf;
      	error = buf.error;
      	lastdirent = buf.previous;
 233  	if (lastdirent) {
      		put_user(file->f_pos, &lastdirent->d_off);
      		error = count - buf.count;
      	}
      
      out_putf:
      	fput(file);
      out:
 241  	return error;
      }
      
      /*
       * And even better one including d_type field and 64bit d_ino and d_off.
       */
      struct linux_dirent64 {
      	u64		d_ino;
      	s64		d_off;
      	unsigned short	d_reclen;
      	unsigned char	d_type;
      	char		d_name[0];
      };
      
      #define ROUND_UP64(x) (((x)+sizeof(u64)-1) & ~(sizeof(u64)-1))
      
      struct getdents_callback64 {
      	struct linux_dirent64 * current_dir;
      	struct linux_dirent64 * previous;
      	int count;
      	int error;
      };
      
 264  static int filldir64(void * __buf, const char * name, int namlen, off_t offset,
      		     ino_t ino, unsigned int d_type)
      {
      	struct linux_dirent64 * dirent, d;
      	struct getdents_callback64 * buf = (struct getdents_callback64 *) __buf;
      	int reclen = ROUND_UP64(NAME_OFFSET(dirent) + namlen + 1);
      
      	buf->error = -EINVAL;	/* only used if we fail.. */
 272  	if (reclen > buf->count)
 273  		return -EINVAL;
      	dirent = buf->previous;
 275  	if (dirent) {
      		d.d_off = offset;
      		copy_to_user(&dirent->d_off, &d.d_off, sizeof(d.d_off));
      	}
      	dirent = buf->current_dir;
      	buf->previous = dirent;
      	memset(&d, 0, NAME_OFFSET(&d));
      	d.d_ino = ino;
      	d.d_reclen = reclen;
      	d.d_type = d_type;
      	copy_to_user(dirent, &d, NAME_OFFSET(&d));
      	copy_to_user(dirent->d_name, name, namlen);
      	put_user(0, dirent->d_name + namlen);
      	((char *) dirent) += reclen;
      	buf->current_dir = dirent;
      	buf->count -= reclen;
 291  	return 0;
      }
      
 294  asmlinkage long sys_getdents64(unsigned int fd, void * dirent, unsigned int count)
      {
      	struct file * file;
      	struct linux_dirent64 * lastdirent;
      	struct getdents_callback64 buf;
      	int error;
      
      	error = -EBADF;
      	file = fget(fd);
 303  	if (!file)
 304  		goto out;
      
      	buf.current_dir = (struct linux_dirent64 *) dirent;
      	buf.previous = NULL;
      	buf.count = count;
      	buf.error = 0;
      
      	error = vfs_readdir(file, filldir64, &buf);
 312  	if (error < 0)
 313  		goto out_putf;
      	error = buf.error;
      	lastdirent = buf.previous;
 316  	if (lastdirent) {
      		struct linux_dirent64 d;
      		d.d_off = file->f_pos;
      		copy_to_user(&lastdirent->d_off, &d.d_off, sizeof(d.d_off));
      		error = count - buf.count;
      	}
      
      out_putf:
      	fput(file);
      out:
 326  	return error;
      }