linux/fs/readdir.c
<<
>>
Prefs
   1/*
   2 *  linux/fs/readdir.c
   3 *
   4 *  Copyright (C) 1995  Linus Torvalds
   5 */
   6
   7#include <linux/kernel.h>
   8#include <linux/module.h>
   9#include <linux/time.h>
  10#include <linux/mm.h>
  11#include <linux/errno.h>
  12#include <linux/stat.h>
  13#include <linux/file.h>
  14#include <linux/fs.h>
  15#include <linux/dirent.h>
  16#include <linux/security.h>
  17#include <linux/syscalls.h>
  18#include <linux/unistd.h>
  19
  20#include <asm/uaccess.h>
  21
  22int vfs_readdir(struct file *file, filldir_t filler, void *buf)
  23{
  24        struct inode *inode = file->f_path.dentry->d_inode;
  25        int res = -ENOTDIR;
  26        if (!file->f_op || !file->f_op->readdir)
  27                goto out;
  28
  29        res = security_file_permission(file, MAY_READ);
  30        if (res)
  31                goto out;
  32
  33        res = mutex_lock_killable(&inode->i_mutex);
  34        if (res)
  35                goto out;
  36
  37        res = -ENOENT;
  38        if (!IS_DEADDIR(inode)) {
  39                res = file->f_op->readdir(file, buf, filler);
  40                file_accessed(file);
  41        }
  42        mutex_unlock(&inode->i_mutex);
  43out:
  44        return res;
  45}
  46
  47EXPORT_SYMBOL(vfs_readdir);
  48
  49/*
  50 * Traditional linux readdir() handling..
  51 *
  52 * "count=1" is a special case, meaning that the buffer is one
  53 * dirent-structure in size and that the code can't handle more
  54 * anyway. Thus the special "fillonedir()" function for that
  55 * case (the low-level handlers don't need to care about this).
  56 */
  57#define NAME_OFFSET(de) ((int) ((de)->d_name - (char __user *) (de)))
  58
  59#ifdef __ARCH_WANT_OLD_READDIR
  60
  61struct old_linux_dirent {
  62        unsigned long   d_ino;
  63        unsigned long   d_offset;
  64        unsigned short  d_namlen;
  65        char            d_name[1];
  66};
  67
  68struct readdir_callback {
  69        struct old_linux_dirent __user * dirent;
  70        int result;
  71};
  72
  73static int fillonedir(void * __buf, const char * name, int namlen, loff_t offset,
  74                      u64 ino, unsigned int d_type)
  75{
  76        struct readdir_callback * buf = (struct readdir_callback *) __buf;
  77        struct old_linux_dirent __user * dirent;
  78        unsigned long d_ino;
  79
  80        if (buf->result)
  81                return -EINVAL;
  82        d_ino = ino;
  83        if (sizeof(d_ino) < sizeof(ino) && d_ino != ino)
  84                return -EOVERFLOW;
  85        buf->result++;
  86        dirent = buf->dirent;
  87        if (!access_ok(VERIFY_WRITE, dirent,
  88                        (unsigned long)(dirent->d_name + namlen + 1) -
  89                                (unsigned long)dirent))
  90                goto efault;
  91        if (    __put_user(d_ino, &dirent->d_ino) ||
  92                __put_user(offset, &dirent->d_offset) ||
  93                __put_user(namlen, &dirent->d_namlen) ||
  94                __copy_to_user(dirent->d_name, name, namlen) ||
  95                __put_user(0, dirent->d_name + namlen))
  96                goto efault;
  97        return 0;
  98efault:
  99        buf->result = -EFAULT;
 100        return -EFAULT;
 101}
 102
 103asmlinkage long old_readdir(unsigned int fd, struct old_linux_dirent __user * dirent, unsigned int count)
 104{
 105        int error;
 106        struct file * file;
 107        struct readdir_callback buf;
 108
 109        error = -EBADF;
 110        file = fget(fd);
 111        if (!file)
 112                goto out;
 113
 114        buf.result = 0;
 115        buf.dirent = dirent;
 116
 117        error = vfs_readdir(file, fillonedir, &buf);
 118        if (error >= 0)
 119                error = buf.result;
 120
 121        fput(file);
 122out:
 123        return error;
 124}
 125
 126#endif /* __ARCH_WANT_OLD_READDIR */
 127
 128/*
 129 * New, all-improved, singing, dancing, iBCS2-compliant getdents()
 130 * interface. 
 131 */
 132struct linux_dirent {
 133        unsigned long   d_ino;
 134        unsigned long   d_off;
 135        unsigned short  d_reclen;
 136        char            d_name[1];
 137};
 138
 139struct getdents_callback {
 140        struct linux_dirent __user * current_dir;
 141        struct linux_dirent __user * previous;
 142        int count;
 143        int error;
 144};
 145
 146static int filldir(void * __buf, const char * name, int namlen, loff_t offset,
 147                   u64 ino, unsigned int d_type)
 148{
 149        struct linux_dirent __user * dirent;
 150        struct getdents_callback * buf = (struct getdents_callback *) __buf;
 151        unsigned long d_ino;
 152        int reclen = ALIGN(NAME_OFFSET(dirent) + namlen + 2, sizeof(long));
 153
 154        buf->error = -EINVAL;   /* only used if we fail.. */
 155        if (reclen > buf->count)
 156                return -EINVAL;
 157        d_ino = ino;
 158        if (sizeof(d_ino) < sizeof(ino) && d_ino != ino)
 159                return -EOVERFLOW;
 160        dirent = buf->previous;
 161        if (dirent) {
 162                if (__put_user(offset, &dirent->d_off))
 163                        goto efault;
 164        }
 165        dirent = buf->current_dir;
 166        if (__put_user(d_ino, &dirent->d_ino))
 167                goto efault;
 168        if (__put_user(reclen, &dirent->d_reclen))
 169                goto efault;
 170        if (copy_to_user(dirent->d_name, name, namlen))
 171                goto efault;
 172        if (__put_user(0, dirent->d_name + namlen))
 173                goto efault;
 174        if (__put_user(d_type, (char __user *) dirent + reclen - 1))
 175                goto efault;
 176        buf->previous = dirent;
 177        dirent = (void __user *)dirent + reclen;
 178        buf->current_dir = dirent;
 179        buf->count -= reclen;
 180        return 0;
 181efault:
 182        buf->error = -EFAULT;
 183        return -EFAULT;
 184}
 185
 186asmlinkage long sys_getdents(unsigned int fd, struct linux_dirent __user * dirent, unsigned int count)
 187{
 188        struct file * file;
 189        struct linux_dirent __user * lastdirent;
 190        struct getdents_callback buf;
 191        int error;
 192
 193        error = -EFAULT;
 194        if (!access_ok(VERIFY_WRITE, dirent, count))
 195                goto out;
 196
 197        error = -EBADF;
 198        file = fget(fd);
 199        if (!file)
 200                goto out;
 201
 202        buf.current_dir = dirent;
 203        buf.previous = NULL;
 204        buf.count = count;
 205        buf.error = 0;
 206
 207        error = vfs_readdir(file, filldir, &buf);
 208        if (error < 0)
 209                goto out_putf;
 210        error = buf.error;
 211        lastdirent = buf.previous;
 212        if (lastdirent) {
 213                if (put_user(file->f_pos, &lastdirent->d_off))
 214                        error = -EFAULT;
 215                else
 216                        error = count - buf.count;
 217        }
 218
 219out_putf:
 220        fput(file);
 221out:
 222        return error;
 223}
 224
 225struct getdents_callback64 {
 226        struct linux_dirent64 __user * current_dir;
 227        struct linux_dirent64 __user * previous;
 228        int count;
 229        int error;
 230};
 231
 232static int filldir64(void * __buf, const char * name, int namlen, loff_t offset,
 233                     u64 ino, unsigned int d_type)
 234{
 235        struct linux_dirent64 __user *dirent;
 236        struct getdents_callback64 * buf = (struct getdents_callback64 *) __buf;
 237        int reclen = ALIGN(NAME_OFFSET(dirent) + namlen + 1, sizeof(u64));
 238
 239        buf->error = -EINVAL;   /* only used if we fail.. */
 240        if (reclen > buf->count)
 241                return -EINVAL;
 242        dirent = buf->previous;
 243        if (dirent) {
 244                if (__put_user(offset, &dirent->d_off))
 245                        goto efault;
 246        }
 247        dirent = buf->current_dir;
 248        if (__put_user(ino, &dirent->d_ino))
 249                goto efault;
 250        if (__put_user(0, &dirent->d_off))
 251                goto efault;
 252        if (__put_user(reclen, &dirent->d_reclen))
 253                goto efault;
 254        if (__put_user(d_type, &dirent->d_type))
 255                goto efault;
 256        if (copy_to_user(dirent->d_name, name, namlen))
 257                goto efault;
 258        if (__put_user(0, dirent->d_name + namlen))
 259                goto efault;
 260        buf->previous = dirent;
 261        dirent = (void __user *)dirent + reclen;
 262        buf->current_dir = dirent;
 263        buf->count -= reclen;
 264        return 0;
 265efault:
 266        buf->error = -EFAULT;
 267        return -EFAULT;
 268}
 269
 270asmlinkage long sys_getdents64(unsigned int fd, struct linux_dirent64 __user * dirent, unsigned int count)
 271{
 272        struct file * file;
 273        struct linux_dirent64 __user * lastdirent;
 274        struct getdents_callback64 buf;
 275        int error;
 276
 277        error = -EFAULT;
 278        if (!access_ok(VERIFY_WRITE, dirent, count))
 279                goto out;
 280
 281        error = -EBADF;
 282        file = fget(fd);
 283        if (!file)
 284                goto out;
 285
 286        buf.current_dir = dirent;
 287        buf.previous = NULL;
 288        buf.count = count;
 289        buf.error = 0;
 290
 291        error = vfs_readdir(file, filldir64, &buf);
 292        if (error < 0)
 293                goto out_putf;
 294        error = buf.error;
 295        lastdirent = buf.previous;
 296        if (lastdirent) {
 297                typeof(lastdirent->d_off) d_off = file->f_pos;
 298                error = -EFAULT;
 299                if (__put_user(d_off, &lastdirent->d_off))
 300                        goto out_putf;
 301                error = count - buf.count;
 302        }
 303
 304out_putf:
 305        fput(file);
 306out:
 307        return error;
 308}
 309
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.