summaryrefslogtreecommitdiff
path: root/fs/ioctl.c
blob: e6500cd122584e830f66d8af209ea817004f68dc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
/*
 *  linux/fs/ioctl.c
 *
 *  Copyright (C) 1991, 1992  Linus Torvalds
 */

#include <linux/syscalls.h>
#include <linux/mm.h>
#include <linux/smp_lock.h>
#include <linux/capability.h>
#include <linux/file.h>
#include <linux/fs.h>
#include <linux/security.h>
#include <linux/module.h>
#include <linux/uaccess.h>

#include <asm/ioctls.h>

/**
 * vfs_ioctl - call filesystem specific ioctl methods
 * @filp: [in]     open file to invoke ioctl method on
 * @cmd:  [in]     ioctl command to execute
 * @arg:  [in/out] command-specific argument for ioctl
 *
 * Invokes filesystem specific ->unlocked_ioctl, if one exists; otherwise
 * invokes * filesystem specific ->ioctl method.  If neither method exists,
 * returns -ENOTTY.
 *
 * Returns 0 on success, -errno on error.
 */
long vfs_ioctl(struct file *filp, unsigned int cmd,
	       unsigned long arg)
{
	int error = -ENOTTY;

	if (!filp->f_op)
		goto out;

	if (filp->f_op->unlocked_ioctl) {
		error = filp->f_op->unlocked_ioctl(filp, cmd, arg);
		if (error == -ENOIOCTLCMD)
			error = -EINVAL;
		goto out;
	} else if (filp->f_op->ioctl) {
		lock_kernel();
		error = filp->f_op->ioctl(filp->f_path.dentry->d_inode,
					  filp, cmd, arg);
		unlock_kernel();
	}

 out:
	return error;
}

static int file_ioctl(struct file *filp, unsigned int cmd,
		unsigned long arg)
{
	int error;
	int block;
	struct inode *inode = filp->f_path.dentry->d_inode;
	int __user *p = (int __user *)arg;

	switch (cmd) {
	case FIBMAP:
	{
		struct address_space *mapping = filp->f_mapping;
		int res;
		/* do we support this mess? */
		if (!mapping->a_ops->bmap)
			return -EINVAL;
		if (!capable(CAP_SYS_RAWIO))
			return -EPERM;
		error = get_user(block, p);
		if (error)
			return error;
		lock_kernel();
		res = mapping->a_ops->bmap(mapping, block);
		unlock_kernel();
		return put_user(res, p);
	}
	case FIGETBSZ:
		return put_user(inode->i_sb->s_blocksize, p);
	case FIONREAD:
		return put_user(i_size_read(inode) - filp->f_pos, p);
	}

	return vfs_ioctl(filp, cmd, arg);
}

/*
 * When you add any new common ioctls to the switches above and below
 * please update compat_sys_ioctl() too.
 *
 * do_vfs_ioctl() is not for drivers and not intended to be EXPORT_SYMBOL()'d.
 * It's just a simple helper for sys_ioctl and compat_sys_ioctl.
 */
int do_vfs_ioctl(struct file *filp, unsigned int fd, unsigned int cmd,
	     unsigned long arg)
{
	unsigned int flag;
	int on, error = 0;

	switch (cmd) {
	case FIOCLEX:
		set_close_on_exec(fd, 1);
		break;

	case FIONCLEX:
		set_close_on_exec(fd, 0);
		break;

	case FIONBIO:
		error = get_user(on, (int __user *)arg);
		if (error)
			break;
		flag = O_NONBLOCK;
#ifdef __sparc__
		/* SunOS compatibility item. */
		if (O_NONBLOCK != O_NDELAY)
			flag |= O_NDELAY;
#endif
		if (on)
			filp->f_flags |= flag;
		else
			filp->f_flags &= ~flag;
		break;

	case FIOASYNC:
		error = get_user(on, (int __user *)arg);
		if (error)
			break;
		flag = on ? FASYNC : 0;

		/* Did FASYNC state change ? */
		if ((flag ^ filp->f_flags) & FASYNC) {
			if (filp->f_op && filp->f_op->fasync) {
				lock_kernel();
				error = filp->f_op->fasync(fd, filp, on);
				unlock_kernel();
			} else
				error = -ENOTTY;
		}
		if (error != 0)
			break;

		if (on)
			filp->f_flags |= FASYNC;
		else
			filp->f_flags &= ~FASYNC;
		break;

	case FIOQSIZE:
		if (S_ISDIR(filp->f_path.dentry->d_inode->i_mode) ||
		    S_ISREG(filp->f_path.dentry->d_inode->i_mode) ||
		    S_ISLNK(filp->f_path.dentry->d_inode->i_mode)) {
			loff_t res =
				inode_get_bytes(filp->f_path.dentry->d_inode);
			error = copy_to_user((loff_t __user *)arg, &res,
					     sizeof(res)) ? -EFAULT : 0;
		} else
			error = -ENOTTY;
		break;
	default:
		if (S_ISREG(filp->f_path.dentry->d_inode->i_mode))
			error = file_ioctl(filp, cmd, arg);
		else
			error = vfs_ioctl(filp, cmd, arg);
		break;
	}
	return error;
}

asmlinkage long sys_ioctl(unsigned int fd, unsigned int cmd, unsigned long arg)
{
	struct file *filp;
	int error = -EBADF;
	int fput_needed;

	filp = fget_light(fd, &fput_needed);
	if (!filp)
		goto out;

	error = security_file_ioctl(filp, cmd, arg);
	if (error)
		goto out_fput;

	error = do_vfs_ioctl(filp, fd, cmd, arg);
 out_fput:
	fput_light(filp, fput_needed);
 out:
	return error;
}