cregit-Linux how code gets into the kernel

Release 4.11 fs/read_write.c

Directory: fs
/*
 *  linux/fs/read_write.c
 *
 *  Copyright (C) 1991, 1992  Linus Torvalds
 */

#include <linux/slab.h>
#include <linux/stat.h>
#include <linux/sched/xacct.h>
#include <linux/fcntl.h>
#include <linux/file.h>
#include <linux/uio.h>
#include <linux/fsnotify.h>
#include <linux/security.h>
#include <linux/export.h>
#include <linux/syscalls.h>
#include <linux/pagemap.h>
#include <linux/splice.h>
#include <linux/compat.h>
#include <linux/mount.h>
#include <linux/fs.h>
#include "internal.h"

#include <linux/uaccess.h>
#include <asm/unistd.h>


const struct file_operations generic_ro_fops = {
	.llseek		= generic_file_llseek,
	.read_iter	= generic_file_read_iter,
	.mmap		= generic_file_readonly_mmap,
	.splice_read	= generic_file_splice_read,
};


EXPORT_SYMBOL(generic_ro_fops);


static inline int unsigned_offsets(struct file *file) { return file->f_mode & FMODE_UNSIGNED_OFFSET; }

Contributors

PersonTokensPropCommitsCommitProp
Kamezawa Hiroyuki1789.47%150.00%
Al Viro210.53%150.00%
Total19100.00%2100.00%

/** * vfs_setpos - update the file offset for lseek * @file: file structure in question * @offset: file offset to seek to * @maxsize: maximum file size * * This is a low-level filesystem helper for updating the file offset to * the value specified by @offset if the given offset is valid and it is * not equal to the current file offset. * * Return the specified offset on success and -EINVAL on invalid offset. */
loff_t vfs_setpos(struct file *file, loff_t offset, loff_t maxsize) { if (offset < 0 && !unsigned_offsets(file)) return -EINVAL; if (offset > maxsize) return -EINVAL; if (offset != file->f_pos) { file->f_pos = offset; file->f_version = 0; } return offset; }

Contributors

PersonTokensPropCommitsCommitProp
Linus Torvalds3552.24%111.11%
Kamezawa Hiroyuki1116.42%111.11%
Christoph Hellwig710.45%111.11%
Andi Kleen710.45%111.11%
Al Viro22.99%111.11%
Robert Love22.99%111.11%
Josef Bacik11.49%111.11%
Andrew Morton11.49%111.11%
Jie Liu11.49%111.11%
Total67100.00%9100.00%

EXPORT_SYMBOL(vfs_setpos); /** * generic_file_llseek_size - generic llseek implementation for regular files * @file: file structure to seek on * @offset: file offset to seek to * @whence: type of seek * @size: max size of this file in file system * @eof: offset used for SEEK_END position * * This is a variant of generic_file_llseek that allows passing in a custom * maximum file size and a custom EOF position, for e.g. hashed directories * * Synchronization: * SEEK_SET and SEEK_END are unsynchronized (but atomic on 64bit platforms) * SEEK_CUR is synchronized against other SEEK_CURs, but not read/writes. * read/writes behave like SEEK_SET against seeks. */
loff_t generic_file_llseek_size(struct file *file, loff_t offset, int whence, loff_t maxsize, loff_t eof) { switch (whence) { case SEEK_END: offset += eof; break; case SEEK_CUR: /* * Here we special-case the lseek(fd, 0, SEEK_CUR) * position-querying operation. Avoid rewriting the "same" * f_pos value back to the file because a concurrent read(), * write() or lseek() might have altered it */ if (offset == 0) return file->f_pos; /* * f_lock protects against read/modify/write race with other * SEEK_CURs. Note that parallel writes and reads behave * like SEEK_SET. */ spin_lock(&file->f_lock); offset = vfs_setpos(file, file->f_pos + offset, maxsize); spin_unlock(&file->f_lock); return offset; case SEEK_DATA: /* * In the generic case the entire file is data, so as long as * offset isn't at the end of the file then the offset is data. */ if (offset >= eof) return -ENXIO; break; case SEEK_HOLE: /* * There is a virtual hole at the end of the file, so as long as * offset isn't i_size or larger, return i_size. */ if (offset >= eof) return -ENXIO; offset = eof; break; } return vfs_setpos(file, offset, maxsize); }

Contributors

PersonTokensPropCommitsCommitProp
Andi Kleen9573.08%342.86%
Robert Love2418.46%114.29%
Eric Sandeen75.38%114.29%
Jie Liu21.54%114.29%
Andrew Morton21.54%114.29%
Total130100.00%7100.00%

EXPORT_SYMBOL(generic_file_llseek_size); /** * generic_file_llseek - generic llseek implementation for regular files * @file: file structure to seek on * @offset: file offset to seek to * @whence: type of seek * * This is a generic implemenation of ->llseek useable for all normal local * filesystems. It just updates the file offset to the value specified by * @offset and @whence. */
loff_t generic_file_llseek(struct file *file, loff_t offset, int whence) { struct inode *inode = file->f_mapping->host; return generic_file_llseek_size(file, offset, whence, inode->i_sb->s_maxbytes, i_size_read(inode)); }

Contributors

PersonTokensPropCommitsCommitProp
Andi Kleen3981.25%240.00%
Eric Sandeen510.42%120.00%
Andrew Morton24.17%120.00%
Linus Torvalds24.17%120.00%
Total48100.00%5100.00%

EXPORT_SYMBOL(generic_file_llseek); /** * fixed_size_llseek - llseek implementation for fixed-sized devices * @file: file structure to seek on * @offset: file offset to seek to * @whence: type of seek * @size: size of the file * */
loff_t fixed_size_llseek(struct file *file, loff_t offset, int whence, loff_t size) { switch (whence) { case SEEK_SET: case SEEK_CUR: case SEEK_END: return generic_file_llseek_size(file, offset, whence, size, size); default: return -EINVAL; } }

Contributors

PersonTokensPropCommitsCommitProp
Al Viro53100.00%1100.00%
Total53100.00%1100.00%

EXPORT_SYMBOL(fixed_size_llseek); /** * no_seek_end_llseek - llseek implementation for fixed-sized devices * @file: file structure to seek on * @offset: file offset to seek to * @whence: type of seek * */
loff_t no_seek_end_llseek(struct file *file, loff_t offset, int whence) { switch (whence) { case SEEK_SET: case SEEK_CUR: return generic_file_llseek_size(file, offset, whence, OFFSET_MAX, 0); default: return -EINVAL; } }

Contributors

PersonTokensPropCommitsCommitProp
Al Viro4697.87%150.00%
Wouter van Kesteren12.13%150.00%
Total47100.00%2100.00%

EXPORT_SYMBOL(no_seek_end_llseek); /** * no_seek_end_llseek_size - llseek implementation for fixed-sized devices * @file: file structure to seek on * @offset: file offset to seek to * @whence: type of seek * @size: maximal offset allowed * */
loff_t no_seek_end_llseek_size(struct file *file, loff_t offset, int whence, loff_t size) { switch (whence) { case SEEK_SET: case SEEK_CUR: return generic_file_llseek_size(file, offset, whence, size, 0); default: return -EINVAL; } }

Contributors

PersonTokensPropCommitsCommitProp
Al Viro50100.00%1100.00%
Total50100.00%1100.00%

EXPORT_SYMBOL(no_seek_end_llseek_size); /** * noop_llseek - No Operation Performed llseek implementation * @file: file structure to seek on * @offset: file offset to seek to * @whence: type of seek * * This is an implementation of ->llseek useable for the rare special case when * userspace expects the seek to succeed but the (device) file is actually not * able to perform the seek. In this case you use noop_llseek() instead of * falling back to the default implementation of ->llseek. */
loff_t noop_llseek(struct file *file, loff_t offset, int whence) { return file->f_pos; }

Contributors

PersonTokensPropCommitsCommitProp
Jan Blunck2095.24%150.00%
Andrew Morton14.76%150.00%
Total21100.00%2100.00%

EXPORT_SYMBOL(noop_llseek);
loff_t no_llseek(struct file *file, loff_t offset, int whence) { return -ESPIPE; }

Contributors

PersonTokensPropCommitsCommitProp
Linus Torvalds1995.00%150.00%
Andrew Morton15.00%150.00%
Total20100.00%2100.00%

EXPORT_SYMBOL(no_llseek);
loff_t default_llseek(struct file *file, loff_t offset, int whence) { struct inode *inode = file_inode(file); loff_t retval; inode_lock(inode); switch (whence) { case SEEK_END: offset += i_size_read(inode); break; case SEEK_CUR: if (offset == 0) { retval = file->f_pos; goto out; } offset += file->f_pos; break; case SEEK_DATA: /* * In the generic case the entire file is data, so as * long as offset isn't at the end of the file then the * offset is data. */ if (offset >= inode->i_size) { retval = -ENXIO; goto out; } break; case SEEK_HOLE: /* * There is a virtual hole at the end of the file, so * as long as offset isn't i_size or larger, return * i_size. */ if (offset >= inode->i_size) { retval = -ENXIO; goto out; } offset = inode->i_size; break; } retval = -EINVAL; if (offset >= 0 || unsigned_offsets(file)) { if (offset != file->f_pos) { file->f_pos = offset; file->f_version = 0; } retval = offset; } out: inode_unlock(inode); return retval; }

Contributors

PersonTokensPropCommitsCommitProp
Linus Torvalds (pre-git)7741.85%422.22%
Josef Bacik4725.54%15.56%
Alain Knaff1910.33%15.56%
Dan Carpenter168.70%15.56%
Al Viro63.26%316.67%
Andrew Morton63.26%316.67%
Arnd Bergmann42.17%15.56%
Kamezawa Hiroyuki42.17%15.56%
Chris Snook21.09%15.56%
Linus Torvalds21.09%15.56%
David Sterba10.54%15.56%
Total184100.00%18100.00%

EXPORT_SYMBOL(default_llseek);
loff_t vfs_llseek(struct file *file, loff_t offset, int whence) { loff_t (*fn)(struct file *, loff_t, int); fn = no_llseek; if (file->f_mode & FMODE_LSEEK) { if (file->f_op->llseek) fn = file->f_op->llseek; } return fn(file, offset, whence); }

Contributors

PersonTokensPropCommitsCommitProp
Linus Torvalds (pre-git)5577.46%555.56%
Linus Torvalds1318.31%222.22%
Andrew Morton22.82%111.11%
Neil Brown11.41%111.11%
Total71100.00%9100.00%

EXPORT_SYMBOL(vfs_llseek); SYSCALL_DEFINE3(lseek, unsigned int, fd, off_t, offset, unsigned int, whence) { off_t retval; struct fd f = fdget_pos(fd); if (!f.file) return -EBADF; retval = -EINVAL; if (whence <= SEEK_MAX) { loff_t res = vfs_llseek(f.file, offset, whence); retval = res; if (res != (loff_t)retval) retval = -EOVERFLOW; /* LFS: should only happen on 32 bit platforms */ } fdput_pos(f); return retval; } #ifdef CONFIG_COMPAT COMPAT_SYSCALL_DEFINE3(lseek, unsigned int, fd, compat_off_t, offset, unsigned int, whence) { return sys_lseek(fd, offset, whence); } #endif #ifdef __ARCH_WANT_SYS_LLSEEK SYSCALL_DEFINE5(llseek, unsigned int, fd, unsigned long, offset_high, unsigned long, offset_low, loff_t __user *, result, unsigned int, whence) { int retval; struct fd f = fdget_pos(fd); loff_t offset; if (!f.file) return -EBADF; retval = -EINVAL; if (whence > SEEK_MAX) goto out_putf; offset = vfs_llseek(f.file, ((loff_t) offset_high << 32) | offset_low, whence); retval = (int)offset; if (offset >= 0) { retval = -EFAULT; if (!copy_to_user(result, &offset, sizeof(offset))) retval = 0; } out_putf: fdput_pos(f); return retval; } #endif
ssize_t vfs_iter_read(struct file *file, struct iov_iter *iter, loff_t *ppos) { struct kiocb kiocb; ssize_t ret; if (!file->f_op->read_iter) return -EINVAL; init_sync_kiocb(&kiocb, file); kiocb.ki_pos = *ppos; iter->type |= READ; ret = call_read_iter(file, &kiocb, iter); BUG_ON(ret == -EIOCBQUEUED); if (ret > 0) *ppos = kiocb.ki_pos; return ret; }

Contributors

PersonTokensPropCommitsCommitProp
Christoph Hellwig9396.88%266.67%
Miklos Szeredi33.12%133.33%
Total96100.00%3100.00%

EXPORT_SYMBOL(vfs_iter_read);
ssize_t vfs_iter_write(struct file *file, struct iov_iter *iter, loff_t *ppos) { struct kiocb kiocb; ssize_t ret; if (!file->f_op->write_iter) return -EINVAL; init_sync_kiocb(&kiocb, file); kiocb.ki_pos = *ppos; iter->type |= WRITE; ret = call_write_iter(file, &kiocb, iter); BUG_ON(ret == -EIOCBQUEUED); if (ret > 0) *ppos = kiocb.ki_pos; return ret; }

Contributors

PersonTokensPropCommitsCommitProp
Christoph Hellwig9396.88%266.67%
Miklos Szeredi33.12%133.33%
Total96100.00%3100.00%

EXPORT_SYMBOL(vfs_iter_write);
int rw_verify_area(int read_write, struct file *file, const loff_t *ppos, size_t count) { struct inode *inode; loff_t pos; int retval = -EINVAL; inode = file_inode(file); if (unlikely((ssize_t) count < 0)) return retval; pos = *ppos; if (unlikely(pos < 0)) { if (!unsigned_offsets(file)) return retval; if (count >= -pos) /* both values are in 0..LLONG_MAX */ return -EOVERFLOW; } else if (unlikely((loff_t) (pos + count) < 0)) { if (!unsigned_offsets(file)) return retval; } if (unlikely(inode->i_flctx && mandatory_lock(inode))) { retval = locks_mandatory_area(inode, file, pos, pos + count - 1, read_write == READ ? F_RDLCK : F_WRLCK); if (retval < 0) return retval; } return security_file_permission(file, read_write == READ ? MAY_READ : MAY_WRITE); }

Contributors

PersonTokensPropCommitsCommitProp
Linus Torvalds9650.53%426.67%
Al Viro4121.58%426.67%
James Morris2613.68%16.67%
Christoph Hellwig126.32%16.67%
Kamezawa Hiroyuki63.16%16.67%
Eric Dumazet42.11%16.67%
Jens Axboe31.58%16.67%
Jeff Layton10.53%16.67%
Pavel Emelyanov10.53%16.67%
Total190100.00%15100.00%


static ssize_t new_sync_read(struct file *filp, char __user *buf, size_t len, loff_t *ppos) { struct iovec iov = { .iov_base = buf, .iov_len = len }; struct kiocb kiocb; struct iov_iter iter; ssize_t ret; init_sync_kiocb(&kiocb, filp); kiocb.ki_pos = *ppos; iov_iter_init(&iter, READ, &iov, 1, len); ret = call_read_iter(filp, &kiocb, &iter); BUG_ON(ret == -EIOCBQUEUED); *ppos = kiocb.ki_pos; return ret; }

Contributors

PersonTokensPropCommitsCommitProp
Al Viro10493.69%250.00%
Christoph Hellwig43.60%125.00%
Miklos Szeredi32.70%125.00%
Total111100.00%4100.00%


ssize_t __vfs_read(struct file *file, char __user *buf, size_t count, loff_t *pos) { if (file->f_op->read) return file->f_op->read(file, buf, count, pos); else if (file->f_op->read_iter) return new_sync_read(file, buf, count, pos); else return -EINVAL; }

Contributors

PersonTokensPropCommitsCommitProp
Dmitry Kasatkin6793.06%150.00%
Al Viro56.94%150.00%
Total72100.00%2100.00%

EXPORT_SYMBOL(__vfs_read);
ssize_t vfs_read(struct file *file, char __user *buf, size_t count, loff_t *pos) { ssize_t ret; if (!(file->f_mode & FMODE_READ)) return -EBADF; if (!(file->f_mode & FMODE_CAN_READ)) return -EINVAL; if (unlikely(!access_ok(VERIFY_WRITE, buf, count))) return -EFAULT; ret = rw_verify_area(READ, file, pos, count); if (!ret) { if (count > MAX_RW_COUNT) count = MAX_RW_COUNT; ret = __vfs_read(file, buf, count, pos); if (ret > 0) { fsnotify_access(file); add_rchar(current, ret); } inc_syscr(current); } return ret; }

Contributors

PersonTokensPropCommitsCommitProp
Christoph Hellwig3826.21%15.56%
Linus Torvalds (pre-git)3624.83%633.33%
Al Viro3020.69%316.67%
Linus Torvalds2517.24%422.22%
Alexey Dobriyan74.83%15.56%
Jay Lan74.83%15.56%
Robert Love10.69%15.56%
Dmitry Kasatkin10.69%15.56%
Total145100.00%18100.00%

EXPORT_SYMBOL(vfs_read);
static ssize_t new_sync_write(struct file *filp, const char __user *buf, size_t len, loff_t *ppos) { struct iovec iov = { .iov_base = (void __user *)buf, .iov_len = len }; struct kiocb kiocb; struct iov_iter iter; ssize_t ret; init_sync_kiocb(&kiocb, filp); kiocb.ki_pos = *ppos; iov_iter_init(&iter, WRITE, &iov, 1, len); ret = call_write_iter(filp, &kiocb, &iter); BUG_ON(ret == -EIOCBQUEUED); if (ret > 0) *ppos = kiocb.ki_pos; return ret; }

Contributors

PersonTokensPropCommitsCommitProp
Christoph Hellwig6149.59%222.22%
Al Viro2822.76%333.33%
Badari Pulavarty2318.70%111.11%
Neil Brown75.69%111.11%
Miklos Szeredi32.44%111.11%
Linus Torvalds10.81%111.11%
Total123100.00%9100.00%


ssize_t __vfs_write(struct file *file, const char __user *p, size_t count, loff_t *pos) { if (file->f_op->write) return file->f_op->write(file, p, count, pos); else if (file->f_op->write_iter) return new_sync_write(file, p, count, pos); else return -EINVAL; }

Contributors

PersonTokensPropCommitsCommitProp
Al Viro73100.00%1100.00%
Total73100.00%1100.00%

EXPORT_SYMBOL(__vfs_write);
ssize_t __kernel_write(struct file *file, const char *buf, size_t count, loff_t *pos) { mm_segment_t old_fs; const char __user *p; ssize_t ret; if (!(file->f_mode & FMODE_CAN_WRITE)) return -EINVAL; old_fs = get_fs(); set_fs(get_ds()); p = (__force const char __user *)buf; if (count > MAX_RW_COUNT) count = MAX_RW_COUNT; ret = __vfs_write(file, p, count, pos); set_fs(old_fs); if (ret > 0) { fsnotify_modify(file); add_wchar(current, ret); } inc_syscw(current); return ret; }

Contributors

PersonTokensPropCommitsCommitProp
Al Viro127100.00%5100.00%
Total127100.00%5100.00%

EXPORT_SYMBOL(__kernel_write);
ssize_t vfs_write(struct file *file, const char __user *buf, size_t count, loff_t *pos) { ssize_t ret; if (!(file->f_mode & FMODE_WRITE)) return -EBADF; if (!(file->f_mode & FMODE_CAN_WRITE)) return -EINVAL; if (unlikely(!access_ok(VERIFY_READ, buf, count))) return -EFAULT; ret = rw_verify_area(WRITE, file, pos, count); if (!ret) { if (count > MAX_RW_COUNT) count = MAX_RW_COUNT; file_start_write(file); ret = __vfs_write(file, buf, count, pos); if (ret > 0) { fsnotify_modify(file); add_wchar(current, ret); } inc_syscw(current); file_end_write(file); } return ret; }

Contributors

PersonTokensPropCommitsCommitProp
Neil Brown7145.51%17.14%
Al Viro3421.79%535.71%
Linus Torvalds2516.03%428.57%
Christoph Hellwig117.05%17.14%
Alexey Dobriyan74.49%17.14%
Jay Lan74.49%17.14%
Robert Love10.64%17.14%
Total156100.00%14100.00%

EXPORT_SYMBOL(vfs_write);
static inline loff_t file_pos_read(struct file *file) { return file->f_pos; }

Contributors

PersonTokensPropCommitsCommitProp
Linus Torvalds17100.00%1100.00%
Total17100.00%1100.00%


static inline void file_pos_write(struct file *file, loff_t pos) { file->f_pos = pos; }

Contributors

PersonTokensPropCommitsCommitProp
Linus Torvalds21100.00%1100.00%
Total21100.00%1100.00%

SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count) { struct fd f = fdget_pos(fd); ssize_t ret = -EBADF; if (f.file) { loff_t pos = file_pos_read(f.file); ret = vfs_read(f.file, buf, count, &pos); if (ret >= 0) file_pos_write(f.file, pos); fdput_pos(f); } return ret; } SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf, size_t, count) { struct fd f = fdget_pos(fd); ssize_t ret = -EBADF; if (f.file) { loff_t pos = file_pos_read(f.file); ret = vfs_write(f.file, buf, count, &pos); if (ret >= 0) file_pos_write(f.file, pos); fdput_pos(f); } return ret; } SYSCALL_DEFINE4(pread64, unsigned int, fd, char __user *, buf, size_t, count, loff_t, pos) { struct fd f; ssize_t ret = -EBADF; if (pos < 0) return -EINVAL; f = fdget(fd); if (f.file) { ret = -ESPIPE; if (f.file->f_mode & FMODE_PREAD) ret = vfs_read(f.file, buf, count, &pos); fdput(f); } return ret; } SYSCALL_DEFINE4(pwrite64, unsigned int, fd, const char __user *, buf, size_t, count, loff_t, pos) { struct fd f; ssize_t ret = -EBADF; if (pos < 0) return -EINVAL; f = fdget(fd); if (f.file) { ret = -ESPIPE; if (f.file->f_mode & FMODE_PWRITE) ret = vfs_write(f.file, buf, count, &pos); fdput(f); } return ret; } /* * Reduce an iovec's length in-place. Return the resulting number of segments */
unsigned long iov_shorten(struct iovec *iov, unsigned long nr_segs, size_t to) { unsigned long seg = 0; size_t len = 0; while (seg < nr_segs) { seg++; if (len + iov->iov_len >= to) { iov->iov_len = to - len; break; } len += iov->iov_len; iov++; } return seg; }

Contributors

PersonTokensPropCommitsCommitProp
Andrew Morton73100.00%1100.00%
Total73100.00%1100.00%

EXPORT_SYMBOL(iov_shorten);
static ssize_t do_iter_readv_writev(struct file *filp, struct iov_iter *iter, loff_t *ppos, int type, int flags) { struct kiocb kiocb; ssize_t ret; if (flags & ~(RWF_HIPRI | RWF_DSYNC | RWF_SYNC)) return -EOPNOTSUPP; init_sync_kiocb(&kiocb, filp); if (flags & RWF_HIPRI) kiocb.ki_flags |= IOCB_HIPRI; if (flags & RWF_DSYNC) kiocb.ki_flags |= IOCB_DSYNC; if (flags & RWF_SYNC) kiocb.ki_flags |= (IOCB_DSYNC | IOCB_SYNC); kiocb.ki_pos = *ppos; if (type == READ) ret = call_read_iter(filp, &kiocb, iter); else ret = call_write_iter(filp, &kiocb, iter); BUG_ON(ret == -EIOCBQUEUED); *ppos = kiocb.ki_pos; return ret; }

Contributors

PersonTokensPropCommitsCommitProp
Al Viro6844.16%225.00%
Christoph Hellwig6240.26%450.00%
Miklos Szeredi2415.58%225.00%
Total154100.00%