Commit | Line | Data |
---|---|---|
d6910058 | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
1da177e4 LT |
2 | /* -*- linux-c -*- --------------------------------------------------------- * |
3 | * | |
4 | * linux/fs/devpts/inode.c | |
5 | * | |
6 | * Copyright 1998-2004 H. Peter Anvin -- All Rights Reserved | |
7 | * | |
1da177e4 LT |
8 | * ------------------------------------------------------------------------- */ |
9 | ||
04541a2f FF |
10 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
11 | ||
1da177e4 LT |
12 | #include <linux/module.h> |
13 | #include <linux/init.h> | |
14 | #include <linux/fs.h> | |
cc0876f8 DH |
15 | #include <linux/fs_context.h> |
16 | #include <linux/fs_parser.h> | |
1da177e4 LT |
17 | #include <linux/sched.h> |
18 | #include <linux/namei.h> | |
5a0e3ad6 | 19 | #include <linux/slab.h> |
1da177e4 LT |
20 | #include <linux/mount.h> |
21 | #include <linux/tty.h> | |
718a9163 | 22 | #include <linux/mutex.h> |
1fd7317d | 23 | #include <linux/magic.h> |
718a9163 | 24 | #include <linux/idr.h> |
1da177e4 | 25 | #include <linux/devpts_fs.h> |
3972b7f6 | 26 | #include <linux/fsnotify.h> |
b87a267e | 27 | #include <linux/seq_file.h> |
1da177e4 | 28 | |
b87a267e | 29 | #define DEVPTS_DEFAULT_MODE 0600 |
1f8f1e29 SB |
30 | /* |
31 | * ptmx is a new node in /dev/pts and will be unused in legacy (single- | |
32 | * instance) mode. To prevent surprises in user space, set permissions of | |
33 | * ptmx to 0. Use 'chmod' or remount with '-o ptmxmode' to set meaningful | |
34 | * permissions. | |
35 | */ | |
36 | #define DEVPTS_DEFAULT_PTMX_MODE 0000 | |
527b3e47 | 37 | #define PTMX_MINOR 2 |
b87a267e | 38 | |
a4834c10 KK |
39 | /* |
40 | * sysctl support for setting limits on the number of Unix98 ptys allocated. | |
41 | * Otherwise one can eat up all kernel memory by opening /dev/ptmx repeatedly. | |
42 | */ | |
43 | static int pty_limit = NR_UNIX98_PTY_DEFAULT; | |
e9aba515 | 44 | static int pty_reserve = NR_UNIX98_PTY_RESERVE; |
a4834c10 | 45 | static int pty_limit_min; |
e9aba515 | 46 | static int pty_limit_max = INT_MAX; |
0f0a0e54 | 47 | static atomic_t pty_count = ATOMIC_INIT(0); |
a4834c10 | 48 | |
1751f872 | 49 | static const struct ctl_table pty_table[] = { |
a4834c10 KK |
50 | { |
51 | .procname = "max", | |
52 | .maxlen = sizeof(int), | |
53 | .mode = 0644, | |
54 | .data = &pty_limit, | |
55 | .proc_handler = proc_dointvec_minmax, | |
56 | .extra1 = &pty_limit_min, | |
57 | .extra2 = &pty_limit_max, | |
e9aba515 KK |
58 | }, { |
59 | .procname = "reserve", | |
60 | .maxlen = sizeof(int), | |
61 | .mode = 0644, | |
62 | .data = &pty_reserve, | |
63 | .proc_handler = proc_dointvec_minmax, | |
64 | .extra1 = &pty_limit_min, | |
65 | .extra2 = &pty_limit_max, | |
a4834c10 KK |
66 | }, { |
67 | .procname = "nr", | |
68 | .maxlen = sizeof(int), | |
69 | .mode = 0444, | |
70 | .data = &pty_count, | |
71 | .proc_handler = proc_dointvec, | |
72 | }, | |
a4834c10 KK |
73 | }; |
74 | ||
31af0abb | 75 | struct pts_mount_opts { |
1da177e4 LT |
76 | int setuid; |
77 | int setgid; | |
f04c6ce2 EB |
78 | kuid_t uid; |
79 | kgid_t gid; | |
1da177e4 | 80 | umode_t mode; |
1f8f1e29 | 81 | umode_t ptmxmode; |
eedf265a | 82 | int reserve; |
e9aba515 | 83 | int max; |
31af0abb | 84 | }; |
1da177e4 | 85 | |
7a673c6b | 86 | enum { |
e9aba515 | 87 | Opt_uid, Opt_gid, Opt_mode, Opt_ptmxmode, Opt_newinstance, Opt_max, |
7a673c6b DP |
88 | Opt_err |
89 | }; | |
90 | ||
cc0876f8 | 91 | static const struct fs_parameter_spec devpts_param_specs[] = { |
cfa5f336 | 92 | fsparam_gid ("gid", Opt_gid), |
cc0876f8 DH |
93 | fsparam_s32 ("max", Opt_max), |
94 | fsparam_u32oct ("mode", Opt_mode), | |
95 | fsparam_flag ("newinstance", Opt_newinstance), | |
96 | fsparam_u32oct ("ptmxmode", Opt_ptmxmode), | |
cfa5f336 | 97 | fsparam_uid ("uid", Opt_uid), |
cc0876f8 | 98 | {} |
7a673c6b DP |
99 | }; |
100 | ||
e76b7c01 SB |
101 | struct pts_fs_info { |
102 | struct ida allocated_ptys; | |
31af0abb | 103 | struct pts_mount_opts mount_opts; |
67245ff3 | 104 | struct super_block *sb; |
1f8f1e29 | 105 | struct dentry *ptmx_dentry; |
e76b7c01 SB |
106 | }; |
107 | ||
108 | static inline struct pts_fs_info *DEVPTS_SB(struct super_block *sb) | |
109 | { | |
110 | return sb->s_fs_info; | |
111 | } | |
112 | ||
311fc65c EB |
113 | static int devpts_ptmx_path(struct path *path) |
114 | { | |
115 | struct super_block *sb; | |
116 | int err; | |
117 | ||
311fc65c EB |
118 | /* Is a devpts filesystem at "pts" in the same directory? */ |
119 | err = path_pts(path); | |
120 | if (err) | |
121 | return err; | |
122 | ||
123 | /* Is the path the root of a devpts filesystem? */ | |
124 | sb = path->mnt->mnt_sb; | |
125 | if ((sb->s_magic != DEVPTS_SUPER_MAGIC) || | |
126 | (path->mnt->mnt_root != sb->s_root)) | |
127 | return -ENODEV; | |
128 | ||
129 | return 0; | |
130 | } | |
131 | ||
4e15f760 CB |
132 | /* |
133 | * Try to find a suitable devpts filesystem. We support the following | |
134 | * scenarios: | |
135 | * - The ptmx device node is located in the same directory as the devpts | |
136 | * mount where the pts device nodes are located. | |
137 | * This is e.g. the case when calling open on the /dev/pts/ptmx device | |
138 | * node when the devpts filesystem is mounted at /dev/pts. | |
139 | * - The ptmx device node is located outside the devpts filesystem mount | |
140 | * where the pts device nodes are located. For example, the ptmx device | |
141 | * is a symlink, separate device node, or bind-mount. | |
142 | * A supported scenario is bind-mounting /dev/pts/ptmx to /dev/ptmx and | |
143 | * then calling open on /dev/ptmx. In this case a suitable pts | |
144 | * subdirectory can be found in the common parent directory /dev of the | |
145 | * devpts mount and the ptmx bind-mount, after resolving the /dev/ptmx | |
146 | * bind-mount. | |
147 | * If no suitable pts subdirectory can be found this function will fail. | |
148 | * This is e.g. the case when bind-mounting /dev/pts/ptmx to /ptmx. | |
149 | */ | |
311fc65c EB |
150 | struct vfsmount *devpts_mntget(struct file *filp, struct pts_fs_info *fsi) |
151 | { | |
152 | struct path path; | |
7d71109d | 153 | int err = 0; |
311fc65c EB |
154 | |
155 | path = filp->f_path; | |
156 | path_get(&path); | |
157 | ||
a319b01d CB |
158 | /* Walk upward while the start point is a bind mount of |
159 | * a single file. | |
160 | */ | |
161 | while (path.mnt->mnt_root == path.dentry) | |
162 | if (follow_up(&path) == 0) | |
163 | break; | |
164 | ||
165 | /* devpts_ptmx_path() finds a devpts fs or returns an error. */ | |
166 | if ((path.mnt->mnt_sb->s_magic != DEVPTS_SUPER_MAGIC) || | |
167 | (DEVPTS_SB(path.mnt->mnt_sb) != fsi)) | |
7d71109d | 168 | err = devpts_ptmx_path(&path); |
311fc65c | 169 | dput(path.dentry); |
a319b01d CB |
170 | if (!err) { |
171 | if (DEVPTS_SB(path.mnt->mnt_sb) == fsi) | |
172 | return path.mnt; | |
7d71109d | 173 | |
a319b01d | 174 | err = -ENODEV; |
311fc65c | 175 | } |
7d71109d | 176 | |
a319b01d CB |
177 | mntput(path.mnt); |
178 | return ERR_PTR(err); | |
311fc65c EB |
179 | } |
180 | ||
143c97cc | 181 | struct pts_fs_info *devpts_acquire(struct file *filp) |
59e55e6c | 182 | { |
eedf265a EB |
183 | struct pts_fs_info *result; |
184 | struct path path; | |
185 | struct super_block *sb; | |
eedf265a EB |
186 | |
187 | path = filp->f_path; | |
188 | path_get(&path); | |
189 | ||
7d71109d CB |
190 | /* Has the devpts filesystem already been found? */ |
191 | if (path.mnt->mnt_sb->s_magic != DEVPTS_SUPER_MAGIC) { | |
192 | int err; | |
193 | ||
194 | err = devpts_ptmx_path(&path); | |
195 | if (err) { | |
196 | result = ERR_PTR(err); | |
197 | goto out; | |
198 | } | |
eedf265a EB |
199 | } |
200 | ||
201 | /* | |
202 | * pty code needs to hold extra references in case of last /dev/tty close | |
203 | */ | |
311fc65c | 204 | sb = path.mnt->mnt_sb; |
eedf265a EB |
205 | atomic_inc(&sb->s_active); |
206 | result = DEVPTS_SB(sb); | |
207 | ||
208 | out: | |
209 | path_put(&path); | |
210 | return result; | |
211 | } | |
212 | ||
213 | void devpts_release(struct pts_fs_info *fsi) | |
214 | { | |
215 | deactivate_super(fsi->sb); | |
59e55e6c SB |
216 | } |
217 | ||
1f71ebed | 218 | /* |
cc0876f8 | 219 | * devpts_parse_param - Parse mount parameters |
1f71ebed | 220 | */ |
cc0876f8 | 221 | static int devpts_parse_param(struct fs_context *fc, struct fs_parameter *param) |
1da177e4 | 222 | { |
cc0876f8 DH |
223 | struct pts_fs_info *fsi = fc->s_fs_info; |
224 | struct pts_mount_opts *opts = &fsi->mount_opts; | |
225 | struct fs_parse_result result; | |
226 | int opt; | |
227 | ||
228 | opt = fs_parse(fc, devpts_param_specs, param, &result); | |
229 | if (opt < 0) | |
230 | return opt; | |
231 | ||
232 | switch (opt) { | |
233 | case Opt_uid: | |
234 | opts->uid = result.uid; | |
235 | opts->setuid = 1; | |
236 | break; | |
237 | case Opt_gid: | |
238 | opts->gid = result.gid; | |
239 | opts->setgid = 1; | |
240 | break; | |
241 | case Opt_mode: | |
242 | opts->mode = result.uint_32 & S_IALLUGO; | |
243 | break; | |
244 | case Opt_ptmxmode: | |
245 | opts->ptmxmode = result.uint_32 & S_IALLUGO; | |
246 | break; | |
247 | case Opt_newinstance: | |
248 | break; | |
249 | case Opt_max: | |
250 | if (result.uint_32 > NR_UNIX98_PTY_MAX) | |
251 | return invalf(fc, "max out of range"); | |
252 | opts->max = result.uint_32; | |
253 | break; | |
1da177e4 | 254 | } |
1da177e4 LT |
255 | |
256 | return 0; | |
257 | } | |
258 | ||
cc0876f8 | 259 | static int mknod_ptmx(struct super_block *sb, struct fs_context *fc) |
1f8f1e29 SB |
260 | { |
261 | int mode; | |
262 | int rc = -ENOMEM; | |
263 | struct dentry *dentry; | |
264 | struct inode *inode; | |
265 | struct dentry *root = sb->s_root; | |
266 | struct pts_fs_info *fsi = DEVPTS_SB(sb); | |
267 | struct pts_mount_opts *opts = &fsi->mount_opts; | |
e98d4137 EB |
268 | kuid_t ptmx_uid = current_fsuid(); |
269 | kgid_t ptmx_gid = current_fsgid(); | |
1f8f1e29 | 270 | |
5955102c | 271 | inode_lock(d_inode(root)); |
1f8f1e29 SB |
272 | |
273 | /* If we have already created ptmx node, return */ | |
274 | if (fsi->ptmx_dentry) { | |
275 | rc = 0; | |
276 | goto out; | |
277 | } | |
278 | ||
279 | dentry = d_alloc_name(root, "ptmx"); | |
280 | if (!dentry) { | |
04541a2f | 281 | pr_err("Unable to alloc dentry for ptmx node\n"); |
1f8f1e29 SB |
282 | goto out; |
283 | } | |
284 | ||
285 | /* | |
286 | * Create a new 'ptmx' node in this mount of devpts. | |
287 | */ | |
288 | inode = new_inode(sb); | |
289 | if (!inode) { | |
04541a2f | 290 | pr_err("Unable to alloc inode for ptmx node\n"); |
1f8f1e29 SB |
291 | dput(dentry); |
292 | goto out; | |
293 | } | |
294 | ||
295 | inode->i_ino = 2; | |
69d9116d | 296 | simple_inode_init_ts(inode); |
1f8f1e29 SB |
297 | |
298 | mode = S_IFCHR|opts->ptmxmode; | |
299 | init_special_inode(inode, mode, MKDEV(TTYAUX_MAJOR, 2)); | |
e98d4137 EB |
300 | inode->i_uid = ptmx_uid; |
301 | inode->i_gid = ptmx_gid; | |
1f8f1e29 SB |
302 | |
303 | d_add(dentry, inode); | |
304 | ||
305 | fsi->ptmx_dentry = dentry; | |
306 | rc = 0; | |
1f8f1e29 | 307 | out: |
5955102c | 308 | inode_unlock(d_inode(root)); |
1f8f1e29 SB |
309 | return rc; |
310 | } | |
311 | ||
312 | static void update_ptmx_mode(struct pts_fs_info *fsi) | |
313 | { | |
314 | struct inode *inode; | |
315 | if (fsi->ptmx_dentry) { | |
2b0143b5 | 316 | inode = d_inode(fsi->ptmx_dentry); |
1f8f1e29 SB |
317 | inode->i_mode = S_IFCHR|fsi->mount_opts.ptmxmode; |
318 | } | |
319 | } | |
1f8f1e29 | 320 | |
cc0876f8 | 321 | static int devpts_reconfigure(struct fs_context *fc) |
53af8ee4 | 322 | { |
cc0876f8 DH |
323 | struct pts_fs_info *fsi = DEVPTS_SB(fc->root->d_sb); |
324 | struct pts_fs_info *new = fc->s_fs_info; | |
53af8ee4 | 325 | |
cc0876f8 DH |
326 | /* Apply the revised options. We don't want to change ->reserve. |
327 | * Ideally, we'd update each option conditionally on it having been | |
328 | * explicitly changed, but the default is to reset everything so that | |
329 | * would break UAPI... | |
330 | */ | |
331 | fsi->mount_opts.setuid = new->mount_opts.setuid; | |
332 | fsi->mount_opts.setgid = new->mount_opts.setgid; | |
333 | fsi->mount_opts.uid = new->mount_opts.uid; | |
334 | fsi->mount_opts.gid = new->mount_opts.gid; | |
335 | fsi->mount_opts.mode = new->mount_opts.mode; | |
336 | fsi->mount_opts.ptmxmode = new->mount_opts.ptmxmode; | |
337 | fsi->mount_opts.max = new->mount_opts.max; | |
1f8f1e29 SB |
338 | |
339 | /* | |
340 | * parse_mount_options() restores options to default values | |
341 | * before parsing and may have changed ptmxmode. So, update the | |
342 | * mode in the inode too. Bogus options don't fail the remount, | |
343 | * so do this even on error return. | |
344 | */ | |
345 | update_ptmx_mode(fsi); | |
346 | ||
cc0876f8 | 347 | return 0; |
53af8ee4 SB |
348 | } |
349 | ||
34c80b1d | 350 | static int devpts_show_options(struct seq_file *seq, struct dentry *root) |
b87a267e | 351 | { |
34c80b1d | 352 | struct pts_fs_info *fsi = DEVPTS_SB(root->d_sb); |
31af0abb SB |
353 | struct pts_mount_opts *opts = &fsi->mount_opts; |
354 | ||
355 | if (opts->setuid) | |
04541a2f FF |
356 | seq_printf(seq, ",uid=%u", |
357 | from_kuid_munged(&init_user_ns, opts->uid)); | |
31af0abb | 358 | if (opts->setgid) |
04541a2f FF |
359 | seq_printf(seq, ",gid=%u", |
360 | from_kgid_munged(&init_user_ns, opts->gid)); | |
31af0abb | 361 | seq_printf(seq, ",mode=%03o", opts->mode); |
1f8f1e29 | 362 | seq_printf(seq, ",ptmxmode=%03o", opts->ptmxmode); |
e9aba515 KK |
363 | if (opts->max < NR_UNIX98_PTY_MAX) |
364 | seq_printf(seq, ",max=%d", opts->max); | |
b87a267e MS |
365 | |
366 | return 0; | |
367 | } | |
368 | ||
ee9b6d61 | 369 | static const struct super_operations devpts_sops = { |
1da177e4 | 370 | .statfs = simple_statfs, |
b87a267e | 371 | .show_options = devpts_show_options, |
1da177e4 LT |
372 | }; |
373 | ||
cc0876f8 | 374 | static int devpts_fill_super(struct super_block *s, struct fs_context *fc) |
1da177e4 | 375 | { |
cc0876f8 | 376 | struct pts_fs_info *fsi = DEVPTS_SB(s); |
1f8f1e29 | 377 | struct inode *inode; |
1da177e4 | 378 | |
cc50a07a | 379 | s->s_iflags &= ~SB_I_NODEV; |
1da177e4 LT |
380 | s->s_blocksize = 1024; |
381 | s->s_blocksize_bits = 10; | |
382 | s->s_magic = DEVPTS_SUPER_MAGIC; | |
383 | s->s_op = &devpts_sops; | |
73052b0d | 384 | s->s_d_op = &simple_dentry_operations; |
1da177e4 | 385 | s->s_time_gran = 1; |
cc0876f8 | 386 | fsi->sb = s; |
1da177e4 LT |
387 | |
388 | inode = new_inode(s); | |
389 | if (!inode) | |
cc0876f8 | 390 | return -ENOMEM; |
1da177e4 | 391 | inode->i_ino = 1; |
69d9116d | 392 | simple_inode_init_ts(inode); |
1da177e4 LT |
393 | inode->i_mode = S_IFDIR | S_IRUGO | S_IXUGO | S_IWUSR; |
394 | inode->i_op = &simple_dir_inode_operations; | |
395 | inode->i_fop = &simple_dir_operations; | |
bfe86848 | 396 | set_nlink(inode, 2); |
1da177e4 | 397 | |
48fde701 | 398 | s->s_root = d_make_root(inode); |
180d9044 EB |
399 | if (!s->s_root) { |
400 | pr_err("get root dentry failed\n"); | |
cc0876f8 | 401 | return -ENOMEM; |
180d9044 | 402 | } |
1f8f1e29 | 403 | |
cc0876f8 | 404 | return mknod_ptmx(s, fc); |
1da177e4 LT |
405 | } |
406 | ||
2a1b2dc0 | 407 | /* |
cc0876f8 | 408 | * devpts_get_tree() |
1bd79035 | 409 | * |
eedf265a EB |
410 | * Mount a new (private) instance of devpts. PTYs created in this |
411 | * instance are independent of the PTYs in other devpts instances. | |
d4076ac5 | 412 | */ |
cc0876f8 | 413 | static int devpts_get_tree(struct fs_context *fc) |
1da177e4 | 414 | { |
cc0876f8 DH |
415 | return get_tree_nodev(fc, devpts_fill_super); |
416 | } | |
417 | ||
418 | static void devpts_free_fc(struct fs_context *fc) | |
419 | { | |
420 | kfree(fc->s_fs_info); | |
421 | } | |
422 | ||
423 | static const struct fs_context_operations devpts_context_ops = { | |
424 | .free = devpts_free_fc, | |
425 | .parse_param = devpts_parse_param, | |
426 | .get_tree = devpts_get_tree, | |
427 | .reconfigure = devpts_reconfigure, | |
428 | }; | |
429 | ||
430 | /* | |
431 | * Set up the filesystem mount context. | |
432 | */ | |
433 | static int devpts_init_fs_context(struct fs_context *fc) | |
434 | { | |
435 | struct pts_fs_info *fsi; | |
436 | ||
437 | fsi = kzalloc(sizeof(struct pts_fs_info), GFP_KERNEL); | |
438 | if (!fsi) | |
439 | return -ENOMEM; | |
440 | ||
441 | ida_init(&fsi->allocated_ptys); | |
442 | fsi->mount_opts.uid = GLOBAL_ROOT_UID; | |
443 | fsi->mount_opts.gid = GLOBAL_ROOT_GID; | |
444 | fsi->mount_opts.mode = DEVPTS_DEFAULT_MODE; | |
445 | fsi->mount_opts.ptmxmode = DEVPTS_DEFAULT_PTMX_MODE; | |
446 | fsi->mount_opts.max = NR_UNIX98_PTY_MAX; | |
447 | ||
448 | if (fc->purpose == FS_CONTEXT_FOR_MOUNT && | |
449 | current->nsproxy->mnt_ns == init_task.nsproxy->mnt_ns) | |
450 | fsi->mount_opts.reserve = true; | |
451 | ||
452 | fc->s_fs_info = fsi; | |
453 | fc->ops = &devpts_context_ops; | |
454 | return 0; | |
1da177e4 | 455 | } |
482984f0 | 456 | |
e76b7c01 SB |
457 | static void devpts_kill_sb(struct super_block *sb) |
458 | { | |
459 | struct pts_fs_info *fsi = DEVPTS_SB(sb); | |
460 | ||
40b320e1 EB |
461 | if (fsi) |
462 | ida_destroy(&fsi->allocated_ptys); | |
e76b7c01 | 463 | kfree(fsi); |
1f8f1e29 | 464 | kill_litter_super(sb); |
e76b7c01 SB |
465 | } |
466 | ||
1da177e4 | 467 | static struct file_system_type devpts_fs_type = { |
1da177e4 | 468 | .name = "devpts", |
cc0876f8 DH |
469 | .init_fs_context = devpts_init_fs_context, |
470 | .parameters = devpts_param_specs, | |
e76b7c01 | 471 | .kill_sb = devpts_kill_sb, |
cc50a07a | 472 | .fs_flags = FS_USERNS_MOUNT, |
1da177e4 LT |
473 | }; |
474 | ||
475 | /* | |
476 | * The normal naming convention is simply /dev/pts/<number>; this conforms | |
477 | * to the System V naming convention | |
478 | */ | |
479 | ||
67245ff3 | 480 | int devpts_new_index(struct pts_fs_info *fsi) |
718a9163 | 481 | { |
0f0a0e54 | 482 | int index = -ENOSPC; |
e9aba515 | 483 | |
0f0a0e54 MW |
484 | if (atomic_inc_return(&pty_count) >= (pty_limit - |
485 | (fsi->mount_opts.reserve ? 0 : pty_reserve))) | |
486 | goto out; | |
718a9163 | 487 | |
0f0a0e54 MW |
488 | index = ida_alloc_max(&fsi->allocated_ptys, fsi->mount_opts.max - 1, |
489 | GFP_KERNEL); | |
490 | ||
491 | out: | |
492 | if (index < 0) | |
493 | atomic_dec(&pty_count); | |
718a9163 SB |
494 | return index; |
495 | } | |
496 | ||
67245ff3 | 497 | void devpts_kill_index(struct pts_fs_info *fsi, int idx) |
718a9163 | 498 | { |
0f0a0e54 MW |
499 | ida_free(&fsi->allocated_ptys, idx); |
500 | atomic_dec(&pty_count); | |
718a9163 SB |
501 | } |
502 | ||
1dcb8e6d JS |
503 | /** |
504 | * devpts_pty_new -- create a new inode in /dev/pts/ | |
ec05b126 | 505 | * @fsi: Filesystem info for this instance. |
1dcb8e6d JS |
506 | * @index: used as a name of the node |
507 | * @priv: what's given back by devpts_get_priv | |
508 | * | |
ec05b126 MWO |
509 | * The dentry for the created inode is returned. |
510 | * Remove it from /dev/pts/ with devpts_pty_kill(). | |
1dcb8e6d | 511 | */ |
8ead9dd5 | 512 | struct dentry *devpts_pty_new(struct pts_fs_info *fsi, int index, void *priv) |
1da177e4 | 513 | { |
1da177e4 | 514 | struct dentry *dentry; |
eedf265a | 515 | struct super_block *sb = fsi->sb; |
162b97cf | 516 | struct inode *inode; |
9ce71148 | 517 | struct dentry *root; |
9ce71148 | 518 | struct pts_mount_opts *opts; |
89a52e10 | 519 | char s[12]; |
1da177e4 | 520 | |
9ce71148 | 521 | root = sb->s_root; |
9ce71148 JT |
522 | opts = &fsi->mount_opts; |
523 | ||
162b97cf | 524 | inode = new_inode(sb); |
1da177e4 | 525 | if (!inode) |
162b97cf | 526 | return ERR_PTR(-ENOMEM); |
1da177e4 | 527 | |
f11afb61 | 528 | inode->i_ino = index + 3; |
d0eafc7d DH |
529 | inode->i_uid = opts->setuid ? opts->uid : current_fsuid(); |
530 | inode->i_gid = opts->setgid ? opts->gid : current_fsgid(); | |
69d9116d | 531 | simple_inode_init_ts(inode); |
8ead9dd5 | 532 | init_special_inode(inode, S_IFCHR|opts->mode, MKDEV(UNIX98_PTY_SLAVE_MAJOR, index)); |
1da177e4 | 533 | |
f11afb61 | 534 | sprintf(s, "%d", index); |
89a52e10 | 535 | |
59e55e6c | 536 | dentry = d_alloc_name(root, s); |
b12d1259 | 537 | if (dentry) { |
8ead9dd5 | 538 | dentry->d_fsdata = priv; |
89a52e10 | 539 | d_add(dentry, inode); |
2b0143b5 | 540 | fsnotify_create(d_inode(root), dentry); |
aa597bc1 AV |
541 | } else { |
542 | iput(inode); | |
8ead9dd5 | 543 | dentry = ERR_PTR(-ENOMEM); |
3972b7f6 | 544 | } |
1da177e4 | 545 | |
8ead9dd5 | 546 | return dentry; |
1da177e4 LT |
547 | } |
548 | ||
1dcb8e6d JS |
549 | /** |
550 | * devpts_get_priv -- get private data for a slave | |
ec05b126 | 551 | * @dentry: dentry of the slave |
1dcb8e6d JS |
552 | * |
553 | * Returns whatever was passed as priv in devpts_pty_new for a given inode. | |
554 | */ | |
8ead9dd5 | 555 | void *devpts_get_priv(struct dentry *dentry) |
1da177e4 | 556 | { |
3e423945 LT |
557 | if (dentry->d_sb->s_magic != DEVPTS_SUPER_MAGIC) |
558 | return NULL; | |
8ead9dd5 | 559 | return dentry->d_fsdata; |
1da177e4 LT |
560 | } |
561 | ||
1dcb8e6d JS |
562 | /** |
563 | * devpts_pty_kill -- remove inode form /dev/pts/ | |
ec05b126 | 564 | * @dentry: dentry of the slave to be removed |
1dcb8e6d JS |
565 | * |
566 | * This is an inverse operation of devpts_pty_new. | |
567 | */ | |
8ead9dd5 | 568 | void devpts_pty_kill(struct dentry *dentry) |
1da177e4 | 569 | { |
8ead9dd5 | 570 | WARN_ON_ONCE(dentry->d_sb->s_magic != DEVPTS_SUPER_MAGIC); |
1da177e4 | 571 | |
8ead9dd5 LT |
572 | dentry->d_fsdata = NULL; |
573 | drop_nlink(dentry->d_inode); | |
46c46f8d | 574 | d_drop(dentry); |
29044dae | 575 | fsnotify_unlink(d_inode(dentry->d_parent), dentry); |
aa597bc1 | 576 | dput(dentry); /* d_alloc_name() in devpts_pty_new() */ |
1da177e4 LT |
577 | } |
578 | ||
579 | static int __init init_devpts_fs(void) | |
580 | { | |
581 | int err = register_filesystem(&devpts_fs_type); | |
582 | if (!err) { | |
3e27877a | 583 | register_sysctl("kernel/pty", pty_table); |
1da177e4 LT |
584 | } |
585 | return err; | |
586 | } | |
1da177e4 | 587 | module_init(init_devpts_fs) |