O_NDELAY open in FreeBSD tape driver

Matt Jacob mjacob at FreeBSD.org
Sun Jan 8 11:20:54 PST 2006


The Bacula folks convinced me to try a bit harder to emulate Linux and Solaris
tape open behaviour which allows an open to succeed with no tape iff the
mode is O_NONBLOCK.

The patch below seems to do the trick- and still preserves the FreeBSD
semantics in that an O_NONBLOCK open with an samount that fails leaves
the tape 'open', but 'open pending mount' so that attempts to actually
*do* anything with a tape retries the samount (returning an error if
failed, otherwise transitioning to full open state). An open without
O_NONBLOCK works just as it did before.

At the same time, I also threw in a check for rdonly opens so that we don't
allow a filemark to be written to a tape that was opened O_RDONLY.

The advantage, other than similarity to Linux or Solaris, is that a pending
open is useful for backup packages that open the tape and then wait for the
robotics to catch up and insert that tape. Personally, I would have just
kept retrying the open until it succeeded, but it *is* true that a number
of packages do it the O_NONBLOCK way.

I checked the FreeBSD source tree and amanda, and I didn't see them using
O_NONBLOCK, so this change shouldn't affect them.

Comments?

-matt

Index: scsi_sa.c
===================================================================
RCS file: /home/ncvs/src/sys/cam/scsi/scsi_sa.c,v
retrieving revision 1.105
diff -u -r1.105 scsi_sa.c
--- scsi_sa.c	1 Jul 2005 15:21:30 -0000	1.105
+++ scsi_sa.c	8 Jan 2006 19:08:02 -0000
@@ -44,6 +44,7 @@
 #ifdef _KERNEL
 #include <sys/conf.h>
 #endif
+#include <sys/fcntl.h>
 #include <sys/devicestat.h>
 
 #ifndef _KERNEL
@@ -255,8 +256,10 @@
 	 * Misc other flags/state
 	 */
 	u_int32_t
-				: 31,
-		ctrl_mode	: 1;	/* control device open */
+					: 29,
+		open_rdonly		: 1,	/* open read-only */
+		open_pending_mount	: 1,	/* open pending mount */
+		ctrl_mode		: 1;	/* control device open */
 };
 
 struct sa_quirk_entry {
@@ -468,23 +471,37 @@
 		cam_periph_unlock(periph);
 		return (ENXIO);
 	}
+
 	if (SA_IS_CTRL(dev)) {
 		softc->ctrl_mode = 1;
 		cam_periph_unlock(periph);
 		return (0);
 	}
 
-
 	if (softc->flags & SA_FLAG_OPEN) {
 		error = EBUSY;
 	} else if (softc->flags & SA_FLAG_INVALID) {
 		error = ENXIO;
 	} else {
 		/*
+		 * Preserve whether this is a read_only open.
+		 */
+		softc->open_rdonly = (flags & O_RDWR) == O_RDONLY;
+
+		/*
 		 * The function samount ensures media is loaded and ready.
 		 * It also does a device RESERVE if the tape isn't yet mounted.
+		 *
+		 * If the mount fails and this was a non-blocking open,
+		 * make this a 'open_pending_mount' action.
 		 */
 		error = samount(periph, flags, dev);
+		if (error && (flags & O_NONBLOCK)) {
+			softc->flags |= SA_FLAG_OPEN;
+			softc->open_pending_mount = 1;
+			cam_periph_unlock(periph);
+			return (0);
+		}
 	}
 
 	if (error) {
@@ -521,6 +538,7 @@
 		return (error);
 	}
 
+	softc->open_rdonly = 0; 
 	if (SA_IS_CTRL(dev)) {
 		softc->ctrl_mode = 0;
 		cam_periph_release(periph);
@@ -528,6 +546,14 @@
 		return (0);
 	}
 
+	if (softc->open_pending_mount) {
+		softc->flags &= ~SA_FLAG_OPEN;
+		softc->open_pending_mount = 0; 
+		cam_periph_release(periph);
+		cam_periph_unlock(periph);
+		return (0);
+	}
+
 	/*
 	 * Were we writing the tape?
 	 */
@@ -681,10 +707,32 @@
 		return;
 	}
 
+	/*
+	 * This should actually never occur as the write(2)
+	 * system call traps attempts to write to a read-only
+	 * file descriptor.
+	 */
+	if (bp->bio_cmd == BIO_WRITE && softc->open_rdonly) {
+		splx(s);
+		biofinish(bp, NULL, EBADF);
+		return;
+	}
+
 	splx(s);
 
+	if (softc->open_pending_mount) {
+		int error = samount(periph, 0, bp->bio_dev);
+		if (error) {
+			biofinish(bp, NULL, ENXIO);
+			return;
+		}
+		saprevent(periph, PR_PREVENT);
+		softc->open_pending_mount = 0;
+	}
+
+
 	/*
-	 * If it's a null transfer, return immediatly
+	 * If it's a null transfer, return immediately
 	 */
 	if (bp->bio_bcount == 0) {
 		biodone(bp);
@@ -756,6 +804,17 @@
 	return;
 }
 
+
+#define	PENDING_MOUNT_CHECK(softc, periph, dev)		\
+	if (softc->open_pending_mount) {		\
+		error = samount(periph, 0, dev);	\
+		if (error) {				\
+			break;				\
+		}					\
+		saprevent(periph, PR_PREVENT);		\
+		softc->open_pending_mount = 0;		\
+	}
+
 static int
 saioctl(struct cdev *dev, u_long cmd, caddr_t arg, int flag, struct thread *td)
 {
@@ -865,7 +924,7 @@
 		 * If this isn't the control mode device, actually go out
 		 * and ask the drive again what it's set to.
 		 */
-		if (!SA_IS_CTRL(dev)) {
+		if (!SA_IS_CTRL(dev) && !softc->open_pending_mount) {
 			u_int8_t write_protect;
 			int comp_enabled, comp_supported;
 			error = sagetparams(periph, SA_PARAM_ALL,
@@ -962,7 +1021,8 @@
 		bcopy((caddr_t) &softc->last_ctl_cdb, sep->ctl_cdb,
 		    sizeof (sep->ctl_cdb));
 
-		if (SA_IS_CTRL(dev) == 0 || didlockperiph)
+		if ((SA_IS_CTRL(dev) == 0 && softc->open_pending_mount) ||
+		    didlockperiph)
 			bzero((caddr_t) &softc->errinfo,
 			    sizeof (softc->errinfo));
 		error = 0;
@@ -973,8 +1033,11 @@
 		struct mtop *mt;
 		int    count;
 
+		PENDING_MOUNT_CHECK(softc, periph, dev);
+
 		mt = (struct mtop *)arg;
 
+
 		CAM_DEBUG(periph->path, CAM_DEBUG_TRACE,
 			 ("saioctl: op=0x%x count=0x%x\n",
 			  mt->mt_op, mt->mt_count));
@@ -1067,6 +1130,7 @@
 			break;
 		}
 		case MTREW:	/* rewind */
+			PENDING_MOUNT_CHECK(softc, periph, dev);
 			(void) sacheckeod(periph);
 			error = sarewind(periph);
 			/* see above */
@@ -1076,12 +1140,14 @@
 			softc->filemarks = 0;
 			break;
 		case MTERASE:	/* erase */
+			PENDING_MOUNT_CHECK(softc, periph, dev);
 			error = saerase(periph, count);
 			softc->flags &=
 			    ~(SA_FLAG_TAPE_WRITTEN|SA_FLAG_TAPE_FROZEN);
 			softc->flags &= ~SA_FLAG_ERR_PENDING;
 			break;
 		case MTRETENS:	/* re-tension tape */
+			PENDING_MOUNT_CHECK(softc, periph, dev);
 			error = saretension(periph);		
 			softc->flags &=
 			    ~(SA_FLAG_TAPE_WRITTEN|SA_FLAG_TAPE_FROZEN);
@@ -1089,6 +1155,8 @@
 			break;
 		case MTOFFL:	/* rewind and put the drive offline */
 
+			PENDING_MOUNT_CHECK(softc, periph, dev);
+
 			(void) sacheckeod(periph);
 			/* see above */
 			softc->flags &= ~SA_FLAG_TAPE_WRITTEN;
@@ -1119,6 +1187,8 @@
 
 		case MTSETBSIZ:	/* Set block size for device */
 
+			PENDING_MOUNT_CHECK(softc, periph, dev);
+
 			error = sasetparams(periph, SA_PARAM_BLOCKSIZE, count,
 					    0, 0, 0);
 			if (error == 0) {
@@ -1161,6 +1231,8 @@
 			}
 			break;
 		case MTSETDNSTY:	/* Set density for device and mode */
+			PENDING_MOUNT_CHECK(softc, periph, dev);
+
 			if (count > UCHAR_MAX) {
 				error = EINVAL;	
 				break;
@@ -1170,6 +1242,7 @@
 			}
 			break;
 		case MTCOMP:	/* enable compression */
+			PENDING_MOUNT_CHECK(softc, periph, dev);
 			/*
 			 * Some devices don't support compression, and
 			 * don't like it if you ask them for the
@@ -1193,15 +1266,19 @@
 		error = 0;
 		break;
 	case MTIOCRDSPOS:
+		PENDING_MOUNT_CHECK(softc, periph, dev);
 		error = sardpos(periph, 0, (u_int32_t *) arg);
 		break;
 	case MTIOCRDHPOS:
+		PENDING_MOUNT_CHECK(softc, periph, dev);
 		error = sardpos(periph, 1, (u_int32_t *) arg);
 		break;
 	case MTIOCSLOCATE:
+		PENDING_MOUNT_CHECK(softc, periph, dev);
 		error = sasetpos(periph, 0, (u_int32_t *) arg);
 		break;
 	case MTIOCHLOCATE:
+		PENDING_MOUNT_CHECK(softc, periph, dev);
 		error = sasetpos(periph, 1, (u_int32_t *) arg);
 		break;
 	case MTIOCGETEOTMODEL:
@@ -3147,6 +3224,8 @@
 	int	error, nwm = 0;
 
 	softc = (struct sa_softc *)periph->softc;
+	if (softc->open_rdonly)
+		return (EBADF);
 
 	ccb = cam_periph_getccb(periph, 1);
 	/*
@@ -3364,6 +3443,8 @@
 	int error;
 
 	softc = (struct sa_softc *)periph->softc;
+	if (softc->open_rdonly)
+		return (EBADF);
 
 	ccb = cam_periph_getccb(periph, 1);
 


More information about the freebsd-scsi mailing list