/*
* linux/fs/readdir.c
*
* Copyright (C) 1995 Linus Torvalds
*/
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/stat.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/mm.h>
#include <asm/segment.h>
/*
* 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))
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;
};
static int fillonedir(void * __buf, const char * name, int namlen, off_t offset, ino_t ino)
{
struct readdir_callback * buf = (struct readdir_callback *) __buf;
struct old_linux_dirent * dirent;
if (buf->count)
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);
memcpy_tofs(dirent->d_name, name, namlen);
put_user(0, dirent->d_name + namlen);
return 0;
}
asmlinkage int old_readdir(unsigned int fd, void * dirent, unsigned int count)
{
int error;
struct file * file;
struct readdir_callback buf;
if (fd >= NR_OPEN || !(file = current->files->fd[fd]))
return -EBADF;
if (!file->f_op || !file->f_op->readdir)
return -ENOTDIR;
error = verify_area(VERIFY_WRITE, dirent, sizeof(struct old_linux_dirent));
if (error)
return error;
buf.count = 0;
buf.dirent = dirent;
error = file->f_op->readdir(file->f_inode, file, &buf, fillonedir);
if (error < 0)
return error;
return buf.count;
}
/*
* 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;
};
static int filldir(void * __buf, const char * name, int namlen, off_t offset, ino_t ino)
{
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.. */
if (reclen > buf->count)
return -EINVAL;
dirent = buf->previous;
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);
memcpy_tofs(dirent->d_name, name, namlen);
put_user(0, dirent->d_name + namlen);
((char *) dirent) += reclen;
buf->current_dir = dirent;
buf->count -= reclen;
return 0;
}
asmlinkage int sys_getdents(unsigned int fd, void * dirent, unsigned int count)
{
struct file * file;
struct linux_dirent * lastdirent;
struct getdents_callback buf;
int error;
if (fd >= NR_OPEN || !(file = current->files->fd[fd]))
return -EBADF;
if (!file->f_op || !file->f_op->readdir)
return -ENOTDIR;
error = verify_area(VERIFY_WRITE, dirent, count);
if (error)
return error;
buf.current_dir = (struct linux_dirent *) dirent;
buf.previous = NULL;
buf.count = count;
buf.error = 0;
error = file->f_op->readdir(file->f_inode, file, &buf, filldir);
if (error < 0)
return error;
lastdirent = buf.previous;
if (!lastdirent)
return buf.error;
put_user(file->f_pos, &lastdirent->d_off);
return count - buf.count;
}