xref: /minix/minix/servers/vfs/cdev.c (revision c5da0dff)
1 /*
2  * This file contains routines to perform character device operations.
3  * Character drivers may suspend I/O requests on their devices (read, write,
4  * ioctl), as well as select requests.  These requests will therefore suspend
5  * their calling process, freeing up the associated VFS worker thread for other
6  * tasks.  The I/O requests may later be cancelled as a result of the suspended
7  * process receiving a signal (which it either catches or dies from), in which
8  * case there will be a worker thread associated with the cancellation.  Open
9  * and close requests may not suspend and will thus block the calling thread.
10  *
11  * The entry points in this file are:
12  *   cdev_map:    map a character device to its actual device number
13  *   cdev_open:   open a character device
14  *   cdev_close:  close a character device
15  *   cdev_io:     initiate a read, write, or ioctl to a character device
16  *   cdev_select: initiate a select call on a device
17  *   cdev_cancel: cancel an I/O request, blocking until it has been cancelled
18  *   cdev_reply:  process the result of a character driver request
19  */
20 
21 #include "fs.h"
22 #include "vnode.h"
23 #include "file.h"
24 #include <string.h>
25 #include <fcntl.h>
26 #include <sys/ttycom.h>
27 #include <assert.h>
28 
29 /*
30  * Map the given device number to a real device number, remapping /dev/tty to
31  * the given process's controlling terminal if it has one.  Perform a bounds
32  * check on the resulting device's major number, and return NO_DEV on failure.
33  * This function is idempotent but not used that way.
34  */
35 dev_t
cdev_map(dev_t dev,struct fproc * rfp)36 cdev_map(dev_t dev, struct fproc * rfp)
37 {
38 	devmajor_t major;
39 
40 	/*
41 	 * First cover one special case: /dev/tty, the magic device that
42 	 * translates to the controlling TTY.
43 	 */
44 	if ((major = major(dev)) == CTTY_MAJOR) {
45 		/* No controlling terminal?  Fail the request. */
46 		if (rfp->fp_tty == NO_DEV) return NO_DEV;
47 
48 		/* Substitute the controlling terminal device. */
49 		dev = rfp->fp_tty;
50 		major = major(dev);
51 	}
52 
53 	if (major < 0 || major >= NR_DEVICES) return NO_DEV;
54 
55 	return dev;
56 }
57 
58 /*
59  * Obtain the dmap structure for the given device, if a valid driver exists for
60  * the major device.  Perform redirection for CTTY_MAJOR.
61  */
62 static struct dmap *
cdev_get(dev_t dev,devminor_t * minor_dev)63 cdev_get(dev_t dev, devminor_t * minor_dev)
64 {
65 	struct dmap *dp;
66 	int slot;
67 
68 	/*
69 	 * Remap /dev/tty as needed.  Perform a bounds check on the major
70 	 * number.
71 	 */
72 	if ((dev = cdev_map(dev, fp)) == NO_DEV)
73 		return NULL;
74 
75 	/* Determine the driver endpoint. */
76 	dp = &dmap[major(dev)];
77 
78 	/* See if driver is roughly valid. */
79 	if (dp->dmap_driver == NONE) return NULL;
80 
81 	if (isokendpt(dp->dmap_driver, &slot) != OK) {
82 		printf("VFS: cdev_get: old driver for major %x (%d)\n",
83 		    major(dev), dp->dmap_driver);
84 		return NULL;
85 	}
86 
87 	/* Also return the (possibly redirected) minor number. */
88 	*minor_dev = minor(dev);
89 	return dp;
90 }
91 
92 /*
93  * A new minor device number has been returned.  Request PFS to create a
94  * temporary device file to hold it.
95  */
96 static int
cdev_clone(int fd,dev_t dev,devminor_t new_minor)97 cdev_clone(int fd, dev_t dev, devminor_t new_minor)
98 {
99 	struct vnode *vp;
100 	struct node_details res;
101 	int r;
102 
103 	assert(fd != -1);
104 
105 	/* Device number of the new device. */
106 	dev = makedev(major(dev), new_minor);
107 
108 	/* Create a new file system node on PFS for the cloned device. */
109 	r = req_newnode(PFS_PROC_NR, fp->fp_effuid, fp->fp_effgid,
110 	    RWX_MODES | I_CHAR_SPECIAL, dev, &res);
111 	if (r != OK) {
112 		(void)cdev_close(dev);
113 		return r;
114 	}
115 
116 	/* Drop the old node and use the new values. */
117 	if ((vp = get_free_vnode()) == NULL) {
118 		req_putnode(PFS_PROC_NR, res.inode_nr, 1); /* is this right? */
119 		(void)cdev_close(dev);
120 		return err_code;
121 	}
122 	lock_vnode(vp, VNODE_OPCL);
123 
124 	assert(fp->fp_filp[fd] != NULL);
125 	unlock_vnode(fp->fp_filp[fd]->filp_vno);
126 	put_vnode(fp->fp_filp[fd]->filp_vno);
127 
128 	vp->v_fs_e = res.fs_e;
129 	vp->v_vmnt = NULL;
130 	vp->v_dev = NO_DEV;
131 	vp->v_inode_nr = res.inode_nr;
132 	vp->v_mode = res.fmode;
133 	vp->v_sdev = dev;
134 	vp->v_fs_count = 1;
135 	vp->v_ref_count = 1;
136 	fp->fp_filp[fd]->filp_vno = vp;
137 
138 	return OK;
139 }
140 
141 /*
142  * Open or close a character device.  The given operation must be either
143  * CDEV_OPEN or CDEV_CLOSE.  For CDEV_OPEN, 'fd' must be the file descriptor
144  * for the file being opened; for CDEV_CLOSE, it is ignored.  For CDEV_OPEN,
145  * 'flags' identifies a bitwise combination of R_BIT, W_BIT, and/or O_NOCTTY;
146  * for CDEV_CLOSE, it too is ignored.
147  */
148 static int
cdev_opcl(int op,dev_t dev,int fd,int flags)149 cdev_opcl(int op, dev_t dev, int fd, int flags)
150 {
151 	devminor_t minor_dev, new_minor;
152 	struct dmap *dp;
153 	struct fproc *rfp;
154 	message dev_mess;
155 	int r, r2, acc;
156 
157 	/*
158 	 * We need the a descriptor for CDEV_OPEN, because if the driver
159 	 * returns a cloned device, we need to replace what the fd points to.
160 	 * For CDEV_CLOSE however, we may be closing a device for which the
161 	 * calling process has no file descriptor, and thus we expect no
162 	 * meaningful fd value in that case.
163 	 */
164 	assert(op == CDEV_OPEN || op == CDEV_CLOSE);
165 	assert(fd != -1 || op == CDEV_CLOSE);
166 
167 	/* Determine task dmap. */
168 	if ((dp = cdev_get(dev, &minor_dev)) == NULL)
169 		return ENXIO;
170 
171 	/*
172 	 * CTTY exception: do not actually send the open/close request for
173 	 * /dev/tty to the driver.  This avoids the case that the actual device
174 	 * will remain open forever if the process calls setsid() after opening
175 	 * /dev/tty.
176 	 */
177 	if (major(dev) == CTTY_MAJOR) return OK;
178 
179 	/*
180 	 * Add O_NOCTTY to the access flags if this process is not a session
181 	 * leader, or if it already has a controlling tty, or if it is someone
182 	 * else's controlling tty.  For performance reasons, only search the
183 	 * full process table if this driver has set controlling TTYs before.
184 	 */
185 	if (!(fp->fp_flags & FP_SESLDR) || fp->fp_tty != 0) {
186 		flags |= O_NOCTTY;
187 	} else if (!(flags & O_NOCTTY) && dp->dmap_seen_tty) {
188 		for (rfp = &fproc[0]; rfp < &fproc[NR_PROCS]; rfp++)
189 			if (rfp->fp_pid != PID_FREE && rfp->fp_tty == dev)
190 				flags |= O_NOCTTY;
191 	}
192 
193 	/* Prepare the request message. */
194 	memset(&dev_mess, 0, sizeof(dev_mess));
195 	dev_mess.m_type = op;
196 	dev_mess.m_vfs_lchardriver_openclose.minor = minor_dev;
197 	dev_mess.m_vfs_lchardriver_openclose.id = who_e;
198 	if (op == CDEV_OPEN) {
199 		acc = 0;
200 		if (flags & R_BIT) acc |= CDEV_R_BIT;
201 		if (flags & W_BIT) acc |= CDEV_W_BIT;
202 		if (flags & O_NOCTTY) acc |= CDEV_NOCTTY;
203 		dev_mess.m_vfs_lchardriver_openclose.user = who_e;
204 		dev_mess.m_vfs_lchardriver_openclose.access = acc;
205 	}
206 
207 	/* Send the request to the driver. */
208 	if ((r = asynsend3(dp->dmap_driver, &dev_mess, AMF_NOREPLY)) != OK)
209 		panic("VFS: asynsend in cdev_opcl failed: %d", r);
210 
211 	/* Block the thread waiting for a reply. */
212 	self->w_task = dp->dmap_driver;
213 	self->w_drv_sendrec = &dev_mess;
214 
215 	worker_wait();
216 
217 	self->w_task = NONE;
218 	assert(self->w_drv_sendrec == NULL);
219 
220 	/* Process the reply. */
221 	r = dev_mess.m_lchardriver_vfs_reply.status;
222 
223 	if (op == CDEV_OPEN && r >= 0) {
224 		/*
225 		 * Some devices need special processing upon open.  Such a
226 		 * device is "cloned", i.e., on a succesful open it is replaced
227 		 * by a new device with a new unique minor device number.  This
228 		 * new device number identifies a new object that has been
229 		 * allocated within a driver.
230 		 */
231 		if (r & CDEV_CLONED) {
232 			new_minor = r & ~(CDEV_CLONED | CDEV_CTTY);
233 			if ((r2 = cdev_clone(fd, dev, new_minor)) < 0)
234 				return r2;
235 		}
236 
237 		/* Did this call make the TTY the controlling TTY? */
238 		if (r & CDEV_CTTY) {
239 			fp->fp_tty = dev;
240 			dp->dmap_seen_tty = TRUE;
241 		}
242 
243 		r = OK;
244 	}
245 
246 	/* Return the result from the driver. */
247 	return r;
248 }
249 
250 /*
251  * Open a character device.
252  */
253 int
cdev_open(int fd,dev_t dev,int flags)254 cdev_open(int fd, dev_t dev, int flags)
255 {
256 
257 	return cdev_opcl(CDEV_OPEN, dev, fd, flags);
258 }
259 
260 /*
261  * Close a character device.
262  */
263 int
cdev_close(dev_t dev)264 cdev_close(dev_t dev)
265 {
266 
267 	return cdev_opcl(CDEV_CLOSE, dev, -1, 0);
268 }
269 
270 /*
271  * Initiate a read, write, or ioctl to a character device.  The given operation
272  * must be CDEV_READ, CDEV_WRITE, or CDEV_IOCTL.  The call is made on behalf of
273  * user process 'proc_e'.  For read/write requests, 'bytes' is the number of
274  * bytes to read into 'buf' at file position 'pos'.  For ioctl requests,
275  * 'bytes' is actually an IOCTL request code, which implies the size of the
276  * buffer 'buf' if needed for the request at all ('pos' is ignored here).  The
277  * 'flags' field contains file pointer flags, from which O_NONBLOCK is tested.
278  */
279 int
cdev_io(int op,dev_t dev,endpoint_t proc_e,vir_bytes buf,off_t pos,unsigned long bytes,int flags)280 cdev_io(int op, dev_t dev, endpoint_t proc_e, vir_bytes buf, off_t pos,
281 	unsigned long bytes, int flags)
282 {
283 	devminor_t minor_dev;
284 	struct dmap *dp;
285 	message dev_mess;
286 	cp_grant_id_t gid;
287 	int r;
288 
289 	assert(op == CDEV_READ || op == CDEV_WRITE || op == CDEV_IOCTL);
290 
291 	/* Determine task map. */
292 	if ((dp = cdev_get(dev, &minor_dev)) == NULL)
293 		return EIO;
294 
295 	/*
296 	 * Handle TIOCSCTTY ioctl: set controlling TTY.  FIXME: this should not
297 	 * hardcode major device numbers, and not assume that the IOCTL request
298 	 * succeeds!
299 	 */
300 	if (op == CDEV_IOCTL && bytes == TIOCSCTTY &&
301 	    (major(dev) == TTY_MAJOR || major(dev) == PTY_MAJOR)) {
302 		fp->fp_tty = dev;
303 	}
304 
305 	/* Create a grant for the buffer provided by the user process. */
306 	if (op != CDEV_IOCTL) {
307 		gid = cpf_grant_magic(dp->dmap_driver, proc_e, buf,
308 		    (size_t)bytes, (op == CDEV_READ) ? CPF_WRITE : CPF_READ);
309 		if (!GRANT_VALID(gid))
310 			panic("VFS: cpf_grant_magic failed");
311 	} else
312 		gid = make_ioctl_grant(dp->dmap_driver, proc_e, buf, bytes);
313 
314 	/* Set up the message that will be sent to the driver. */
315 	memset(&dev_mess, 0, sizeof(dev_mess));
316 	dev_mess.m_type = op;
317 	dev_mess.m_vfs_lchardriver_readwrite.minor = minor_dev;
318 	if (op == CDEV_IOCTL) {
319 		dev_mess.m_vfs_lchardriver_readwrite.request = bytes;
320 		dev_mess.m_vfs_lchardriver_readwrite.user = proc_e;
321 	} else {
322 		dev_mess.m_vfs_lchardriver_readwrite.pos = pos;
323 		dev_mess.m_vfs_lchardriver_readwrite.count = bytes;
324 	}
325 	dev_mess.m_vfs_lchardriver_readwrite.id = proc_e;
326 	dev_mess.m_vfs_lchardriver_readwrite.grant = gid;
327 	dev_mess.m_vfs_lchardriver_readwrite.flags = 0;
328 	if (flags & O_NONBLOCK)
329 		  dev_mess.m_vfs_lchardriver_readwrite.flags |= CDEV_NONBLOCK;
330 
331 	/* Send the request to the driver. */
332 	if ((r = asynsend3(dp->dmap_driver, &dev_mess, AMF_NOREPLY)) != OK)
333 		panic("VFS: asynsend in cdev_io failed: %d", r);
334 
335 	/* Suspend the calling process until a reply arrives. */
336 	fp->fp_cdev.dev = dev;
337 	fp->fp_cdev.endpt = dp->dmap_driver;
338 	fp->fp_cdev.grant = gid;	/* revoke this when unsuspended */
339 	suspend(FP_BLOCKED_ON_CDEV);
340 
341 	return SUSPEND;
342 }
343 
344 /*
345  * Initiate a select call on a device.  Return OK iff the request was sent.
346  * This function explicitly bypasses cdev_get() since it must not do CTTY
347  * mapping, because a) the caller already has done that, b) "fp" may be wrong.
348  */
349 int
cdev_select(dev_t dev,int ops)350 cdev_select(dev_t dev, int ops)
351 {
352 	devmajor_t major;
353 	message dev_mess;
354 	struct dmap *dp;
355 	int r;
356 
357 	/* Determine task dmap, without CTTY mapping. */
358 	assert(dev != NO_DEV);
359 	major = major(dev);
360 	assert(major >= 0 && major < NR_DEVICES);
361 	assert(major != CTTY_MAJOR);
362 	dp = &dmap[major];
363 
364 	/* Prepare the request message. */
365 	memset(&dev_mess, 0, sizeof(dev_mess));
366 	dev_mess.m_type = CDEV_SELECT;
367 	dev_mess.m_vfs_lchardriver_select.minor = minor(dev);
368 	dev_mess.m_vfs_lchardriver_select.ops = ops;
369 
370 	/* Send the request to the driver. */
371 	if ((r = asynsend3(dp->dmap_driver, &dev_mess, AMF_NOREPLY)) != OK)
372 		panic("VFS: asynsend in cdev_select failed: %d", r);
373 
374 	return OK;
375 }
376 
377 /*
378  * Cancel an I/O request, blocking until it has been cancelled.
379  */
380 int
cdev_cancel(dev_t dev,endpoint_t endpt __unused,cp_grant_id_t grant)381 cdev_cancel(dev_t dev, endpoint_t endpt __unused, cp_grant_id_t grant)
382 {
383 	devminor_t minor_dev;
384 	message dev_mess;
385 	struct dmap *dp;
386 	int r;
387 
388 	/* Determine task dmap. */
389 	if ((dp = cdev_get(dev, &minor_dev)) == NULL)
390 		return EIO;
391 
392 	/* Prepare the request message. */
393 	memset(&dev_mess, 0, sizeof(dev_mess));
394 	dev_mess.m_type = CDEV_CANCEL;
395 	dev_mess.m_vfs_lchardriver_cancel.minor = minor_dev;
396 	dev_mess.m_vfs_lchardriver_cancel.id = fp->fp_endpoint;
397 
398 	/* Send the request to the driver. */
399 	if ((r = asynsend3(dp->dmap_driver, &dev_mess, AMF_NOREPLY)) != OK)
400 		panic("VFS: asynsend in cdev_cancel failed: %d", r);
401 
402 	/* Suspend this thread until we have received the response. */
403 	self->w_task = dp->dmap_driver;
404 	self->w_drv_sendrec = &dev_mess;
405 
406 	worker_wait();
407 
408 	self->w_task = NONE;
409 	assert(self->w_drv_sendrec == NULL);
410 
411 	/* Clean up. */
412 	if (GRANT_VALID(grant))
413 		(void)cpf_revoke(grant);
414 
415 	/* Return the result.  Note that the request may have completed. */
416 	r = dev_mess.m_lchardriver_vfs_reply.status;
417 
418 	return (r == EAGAIN) ? EINTR : r; /* see below regarding error codes */
419 }
420 
421 /*
422  * A character driver has results for an open, close, read, write, or ioctl
423  * call (i.e., everything except select).  There may be a thread waiting for
424  * these results as part of an ongoing open, close, or (for read/write/ioctl)
425  * cancel call.  If so, wake up that thread; if not, send a reply to the
426  * requesting process. This function MUST NOT block its calling thread.
427  */
428 static void
cdev_generic_reply(message * m_ptr)429 cdev_generic_reply(message * m_ptr)
430 {
431 	struct fproc *rfp;
432 	struct worker_thread *wp;
433 	endpoint_t proc_e;
434 	int r, slot;
435 
436 	proc_e = m_ptr->m_lchardriver_vfs_reply.id;
437 
438 	if (m_ptr->m_lchardriver_vfs_reply.status == SUSPEND) {
439 		printf("VFS: ignoring SUSPEND status from %d\n",
440 		    m_ptr->m_source);
441 		return;
442 	}
443 
444 	if (isokendpt(proc_e, &slot) != OK) {
445 		printf("VFS: proc %d from %d not found\n",
446 		    proc_e, m_ptr->m_source);
447 		return;
448 	}
449 	rfp = &fproc[slot];
450 	wp = rfp->fp_worker;
451 	if (wp != NULL && wp->w_task == who_e && wp->w_drv_sendrec != NULL) {
452 		assert(!fp_is_blocked(rfp));
453 		*wp->w_drv_sendrec = *m_ptr;
454 		wp->w_drv_sendrec = NULL;
455 		worker_signal(wp);	/* continue open/close/cancel */
456 	} else if (rfp->fp_blocked_on != FP_BLOCKED_ON_CDEV ||
457 	    rfp->fp_cdev.endpt != m_ptr->m_source) {
458 		/*
459 		 * This would typically be caused by a protocol error, i.e., a
460 		 * driver not properly following the character driver protocol.
461 		 */
462 		printf("VFS: proc %d not blocked on %d\n",
463 		    proc_e, m_ptr->m_source);
464 	} else {
465 		/*
466 		 * Some services use the same infrastructure for nonblocking
467 		 * and cancelled requests, resulting in one of EINTR or EAGAIN
468 		 * when the other is really the appropriate code.  Thus,
469 		 * cdev_cancel converts EAGAIN into EINTR, and we convert EINTR
470 		 * into EAGAIN here.  TODO: this may be obsolete by now..?
471 		 */
472 		r = m_ptr->m_lchardriver_vfs_reply.status;
473 		revive(proc_e, (r == EINTR) ? EAGAIN : r);
474 	}
475 }
476 
477 /*
478  * A character driver has results for us.
479  */
480 void
cdev_reply(void)481 cdev_reply(void)
482 {
483 
484 	if (get_dmap_by_endpt(who_e) == NULL) {
485 		printf("VFS: ignoring char dev reply from unknown driver %d\n",
486 		    who_e);
487 		return;
488 	}
489 
490 	switch (call_nr) {
491 	case CDEV_REPLY:
492 		cdev_generic_reply(&m_in);
493 		break;
494 	case CDEV_SEL1_REPLY:
495 		select_cdev_reply1(m_in.m_source,
496 		    m_in.m_lchardriver_vfs_sel1.minor,
497 		    m_in.m_lchardriver_vfs_sel1.status);
498 		break;
499 	case CDEV_SEL2_REPLY:
500 		select_cdev_reply2(m_in.m_source,
501 		    m_in.m_lchardriver_vfs_sel2.minor,
502 		    m_in.m_lchardriver_vfs_sel2.status);
503 		break;
504 	default:
505 		printf("VFS: char driver %u sent unknown reply %x\n",
506 		    who_e, call_nr);
507 	}
508 }
509