6453345 devfs_getattr violates devfs locking rules
authorcth
Fri, 22 Feb 2008 09:02:16 -0800
changeset 6065 b05c5c670963
parent 6064 45d29804ab93
child 6066 5e189196050c
6453345 devfs_getattr violates devfs locking rules 6527396 devfsadm -C taking hours after reconfiguration reboot 6661843 i_ddi_di_cache_invalidate() should only use taskq_dispatch on valid->invalid 6662461 reserved_links_exist() should be optimized
usr/src/cmd/devfsadm/devfsadm.c
usr/src/cmd/devfsadm/devfsadm.h
usr/src/cmd/devfsadm/disk_link.c
usr/src/cmd/truss/print.c
usr/src/lib/libdevinfo/devinfo_finddev.c
usr/src/lib/libdevinfo/libdevinfo.h
usr/src/lib/libdevinfo/mapfile-vers
usr/src/uts/common/fs/dev/sdev_subr.c
usr/src/uts/common/fs/devfs/devfs_vnops.c
usr/src/uts/common/os/devcfg.c
usr/src/uts/common/os/modctl.c
usr/src/uts/common/sys/fs/sdev_impl.h
usr/src/uts/common/sys/modctl.h
--- a/usr/src/cmd/devfsadm/devfsadm.c	Fri Feb 22 08:41:57 2008 -0800
+++ b/usr/src/cmd/devfsadm/devfsadm.c	Fri Feb 22 09:02:16 2008 -0800
@@ -3153,9 +3153,6 @@
 {
 	char *ptr, path[PATH_MAX + 1];
 	char *fcn = "rm_parent_dir_if_empty: ";
-	finddevhdl_t fhandle;
-	const char *f;
-	int rv;
 
 	vprint(REMOVE_MID, "%schecking %s if empty\n", fcn, pathname);
 
@@ -3173,17 +3170,8 @@
 
 		*ptr = '\0';
 
-		if ((rv = finddev_readdir(path, &fhandle)) != 0) {
-			err_print(OPENDIR_FAILED, path, strerror(rv));
-			return;
-		}
-
-		/*
-		 * An empty pathlist implies an empty directory
-		 */
-		f = finddev_next(fhandle);
-		finddev_close(fhandle);
-		if (f == NULL) {
+		if (finddev_emptydir(path)) {
+			/* directory is empty */
 			if (s_rmdir(path) == 0) {
 				vprint(REMOVE_MID,
 				    "%sremoving empty dir %s\n", fcn, path);
@@ -8970,6 +8958,15 @@
 }
 
 /*
+ * Return 1 if we have reserved links.
+ */
+int
+devfsadm_have_reserved()
+{
+	return (enumerate_reserved ? 1 : 0);
+}
+
+/*
  * This functions errs on the side of caution. If there is any error
  * we assume that the devlink is  *not* reserved
  */
--- a/usr/src/cmd/devfsadm/devfsadm.h	Fri Feb 22 08:41:57 2008 -0800
+++ b/usr/src/cmd/devfsadm/devfsadm.h	Fri Feb 22 09:02:16 2008 -0800
@@ -19,7 +19,7 @@
  * CDDL HEADER END
  */
 /*
- * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
+ * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
  */
 
@@ -250,6 +250,7 @@
 char *s_strdup(const char *ptr);
 
 /* Private interface between reserve subsystm and disks link generator */
+int devfsadm_have_reserved(void);
 int devfsadm_is_reserved(devlink_re_t re_array[], char *devlink);
 int devfsadm_reserve_id_cache(devlink_re_t re_array[], enumerate_file_t *head);
 
--- a/usr/src/cmd/devfsadm/disk_link.c	Fri Feb 22 08:41:57 2008 -0800
+++ b/usr/src/cmd/devfsadm/disk_link.c	Fri Feb 22 09:02:16 2008 -0800
@@ -19,7 +19,7 @@
  * CDDL HEADER END
  */
 /*
- * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
+ * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
  */
 
@@ -150,16 +150,14 @@
 	int targ;
 	int *intp;
 
-	if (di_prop_lookup_ints(DDI_DEV_T_ANY, node,
-		"target", &intp) <= 0) {
+	if (di_prop_lookup_ints(DDI_DEV_T_ANY, node, "target", &intp) <= 0) {
 		return (DEVFSADM_CONTINUE);
 	}
 	targ = *intp;
-	if (di_prop_lookup_ints(DDI_DEV_T_ANY, node,
-		    "lun", &intp) <= 0) {
-		    lun = 0;
+	if (di_prop_lookup_ints(DDI_DEV_T_ANY, node, "lun", &intp) <= 0) {
+		lun = 0;
 	} else {
-		    lun = *intp;
+		lun = *intp;
 	}
 	(void) sprintf(disk, "t%dd%d", targ, lun);
 
@@ -181,10 +179,10 @@
 
 	if (di_prop_lookup_strings(DDI_DEV_T_ANY, node,
 	    "client-guid", (char **)&wwn) > 0) {
-		if (strlcpy((char *)ascii_wwn, (char *)wwn, sizeof (ascii_wwn))
-			>= sizeof (ascii_wwn)) {
+		if (strlcpy((char *)ascii_wwn, (char *)wwn,
+		    sizeof (ascii_wwn)) >= sizeof (ascii_wwn)) {
 			devfsadm_errprint("SUNW_disk_link: GUID too long:%d",
-				strlen((char *)wwn));
+			    strlen((char *)wwn));
 			return (DEVFSADM_CONTINUE);
 		}
 		lun = 0;
@@ -468,7 +466,6 @@
 	char *s;
 	char l[PATH_MAX];
 	int switch_link = 0;
-	struct stat sb;
 	char *mn = di_minor_name(minor);
 
 	if (dvlink_cache == NULL || mn == NULL) {
@@ -476,10 +473,8 @@
 		return (DEVFSADM_FAILURE);
 	}
 
-	if (stat(ENUMERATE_RESERVED, &sb) == -1) {
-		devfsadm_print(disk_mid, "%s: No reserved file: %s. Will "
-		    "not bypass new link creation\n",
-		    modname, ENUMERATE_RESERVED);
+	if (!devfsadm_have_reserved()) {
+		devfsadm_print(disk_mid, "%s: No reserved links\n", modname);
 		return (DEVFSADM_FAILURE);
 	}
 
@@ -512,8 +507,8 @@
 			return (DEVFSADM_FAILURE);
 		}
 		(void) snprintf(s+1, sizeof (phys_path) - (s + 1 - phys_path),
-			"%s%s", *mn == *(MN_SMI) ? MN_EFI : MN_SMI,
-			strstr(s, ",raw") ? ",raw" : "");
+		    "%s%s", *mn == *(MN_SMI) ? MN_EFI : MN_SMI,
+		    strstr(s, ",raw") ? ",raw" : "");
 		(void) di_devlink_cache_walk(dvlink_cache, DISK_LINK_RE,
 		    phys_path, DI_PRIMARY_LINK, &head, dvlink_cb);
 	}
--- a/usr/src/cmd/truss/print.c	Fri Feb 22 08:41:57 2008 -0800
+++ b/usr/src/cmd/truss/print.c	Fri Feb 22 09:02:16 2008 -0800
@@ -1113,6 +1113,7 @@
 		case MODREMDRVCLEANUP:	s = "MODREMDRVCLEANUP"; break;
 		case MODDEVEXISTS:	s = "MODDEVEXISTS"; break;
 		case MODDEVREADDIR:	s = "MODDEVREADDIR"; break;
+		case MODDEVEMPTYDIR:	s = "MODDEVEMPTYDIR"; break;
 		case MODDEVNAME:	s = "MODDEVNAME"; break;
 		case MODGETDEVFSPATH_MI_LEN:
 					s = "MODGETDEVFSPATH_MI_LEN"; break;
--- a/usr/src/lib/libdevinfo/devinfo_finddev.c	Fri Feb 22 08:41:57 2008 -0800
+++ b/usr/src/lib/libdevinfo/devinfo_finddev.c	Fri Feb 22 09:02:16 2008 -0800
@@ -19,7 +19,7 @@
  * CDDL HEADER END
  */
 /*
- * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
+ * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
  */
 
@@ -256,6 +256,65 @@
 	return (finddev_readdir_alt(path, handlep));
 }
 
+/*
+ * Return true if a directory is empty
+ * Use the standard library readdir to determine if a directory is
+ * empty.
+ */
+static int
+finddev_emptydir_alt(const char *path)
+{
+	DIR		*dir;
+	struct dirent	*dp;
+
+	if ((dir = opendir(path)) == NULL)
+		return (ENOENT);
+
+	while ((dp = readdir(dir)) != NULL) {
+		if ((strcmp(dp->d_name, ".") == 0) ||
+		    (strcmp(dp->d_name, "..") == 0))
+			continue;
+		(void) closedir(dir);
+		return (0);		/* not empty */
+	}
+	(void) closedir(dir);
+	return (1);			/* empty */
+}
+
+/*
+ * Use of the dev filesystem's private readdir does (not trigger
+ * the implicit device reconfiguration) to determine if a directory
+ * is empty.
+ *
+ * Note: only useable with paths mounted on an instance of the
+ * dev filesystem.
+ *
+ * Does not return the . and .. entries.
+ * Empty directories are returned as an zero-length list.
+ * ENOENT is returned as a NULL list pointer.
+ */
+static int
+finddev_emptydir_devfs(const char *path)
+{
+	int	rv;
+	int	empty;
+
+	rv = modctl(MODDEVEMPTYDIR, path, strlen(path), &empty);
+	if (rv == 0) {
+		return (empty);
+	}
+	return (0);
+}
+
+int
+finddev_emptydir(const char *path)
+{
+	if (GLOBAL_DEV_PATH(path)) {
+		return (finddev_emptydir_devfs(path));
+	}
+	return (finddev_emptydir_alt(path));
+}
+
 void
 finddev_close(finddevhdl_t arg)
 {
--- a/usr/src/lib/libdevinfo/libdevinfo.h	Fri Feb 22 08:41:57 2008 -0800
+++ b/usr/src/lib/libdevinfo/libdevinfo.h	Fri Feb 22 09:02:16 2008 -0800
@@ -19,7 +19,7 @@
  * CDDL HEADER END
  */
 /*
- * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
+ * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
  */
 
@@ -473,6 +473,7 @@
 
 extern int		device_exists(const char *);
 extern int		finddev_readdir(const char *, finddevhdl_t *);
+extern int		finddev_emptydir(const char *);
 extern void		finddev_close(finddevhdl_t);
 extern const char	*finddev_next(finddevhdl_t);
 
--- a/usr/src/lib/libdevinfo/mapfile-vers	Fri Feb 22 08:41:57 2008 -0800
+++ b/usr/src/lib/libdevinfo/mapfile-vers	Fri Feb 22 09:02:16 2008 -0800
@@ -19,7 +19,7 @@
 # CDDL HEADER END
 #
 #
-# Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
+# Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
 # Use is subject to license terms.
 #
 # ident	"%Z%%M%	%I%	%E% SMI"
@@ -207,9 +207,10 @@
 	di_prom_prop_lookup_slot_names;
 	di_prop_find;
 	device_exists;
+	finddev_close;
+	finddev_emptydir;
+	finddev_next;
 	finddev_readdir;
-	finddev_close;
-	finddev_next;
 	di_flags;
 	di_retire_device;
 	di_unretire_device;
--- a/usr/src/uts/common/fs/dev/sdev_subr.c	Fri Feb 22 08:41:57 2008 -0800
+++ b/usr/src/uts/common/fs/dev/sdev_subr.c	Fri Feb 22 09:02:16 2008 -0800
@@ -521,7 +521,7 @@
 		if (map->dir_invalid) {
 			if (map->dir_module && map->dir_newmodule &&
 			    (strcmp(map->dir_module,
-					map->dir_newmodule) == 0)) {
+			    map->dir_newmodule) == 0)) {
 				load = 0;
 			}
 			sdev_replace_nsmap(map, map->dir_newmodule,
@@ -1230,7 +1230,7 @@
 			(void) sdev_dirdelete(nddv, *ndvp);
 			*ndvp = NULL;
 			error = VOP_RMDIR(nddv->sdev_attrvp, nnm,
-				    nddv->sdev_attrvp, cred, NULL, 0);
+			    nddv->sdev_attrvp, cred, NULL, 0);
 			if (error)
 				goto err_out;
 		} else {
@@ -1254,7 +1254,7 @@
 				error = VOP_REMOVE(nddv->sdev_attrvp,
 				    nnm, cred, NULL, 0);
 				if (error)
-				    goto err_out;
+					goto err_out;
 			}
 		}
 	}
@@ -1811,7 +1811,7 @@
 		error = 0;
 	} else if (!DEVNAME_DEVFSADM_HAS_RUN(devfsadm_state)) {
 		sdcmn_err6(("lookup %s/%s starting devfsadm, 0x%x\n",
-			ddv->sdev_name, nm, devfsadm_state));
+		    ddv->sdev_name, nm, devfsadm_state));
 
 		sdev_devfsadmd_thread(ddv, dv, kcred);
 		mutex_enter(&dv->sdev_lookup_lock);
@@ -3026,7 +3026,7 @@
 
 int
 sdev_modctl_readdir(const char *dir, char ***dirlistp,
-	int *npathsp, int *npathsp_alloc)
+	int *npathsp, int *npathsp_alloc, int checking_empty)
 {
 	char	**pathlist = NULL;
 	char	**newlist = NULL;
@@ -3081,18 +3081,17 @@
 			break;
 
 		for (dp = dbuf; ((intptr_t)dp < (intptr_t)dbuf + dbuflen);
-			dp = (dirent64_t *)((intptr_t)dp + dp->d_reclen)) {
+		    dp = (dirent64_t *)((intptr_t)dp + dp->d_reclen)) {
 
 			nm = dp->d_name;
 
 			if (strcmp(nm, ".") == 0 || strcmp(nm, "..") == 0)
 				continue;
-
 			if (npaths == npaths_alloc) {
 				npaths_alloc += 64;
 				newlist = (char **)
 				    kmem_zalloc((npaths_alloc + 1) *
-					sizeof (char *), KM_SLEEP);
+				    sizeof (char *), KM_SLEEP);
 				if (pathlist) {
 					bcopy(pathlist, newlist,
 					    npaths * sizeof (char *));
@@ -3106,6 +3105,12 @@
 			bcopy(nm, s, n);
 			pathlist[npaths++] = s;
 			sdcmn_err11(("  %s/%s\n", dir, s));
+
+			/* if checking empty, one entry is as good as many */
+			if (checking_empty) {
+				eof = 1;
+				break;
+			}
 		}
 	}
 
--- a/usr/src/uts/common/fs/devfs/devfs_vnops.c	Fri Feb 22 08:41:57 2008 -0800
+++ b/usr/src/uts/common/fs/devfs/devfs_vnops.c	Fri Feb 22 09:02:16 2008 -0800
@@ -19,7 +19,7 @@
  * CDDL HEADER END
  */
 /*
- * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
+ * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
  */
 
@@ -180,6 +180,7 @@
 		return (ENOENT);
 	}
 
+	rw_enter(&dv->dv_contents, RW_READER);
 	if (dv->dv_attr) {
 		/*
 		 * obtain from the memory version of attribute.
@@ -195,6 +196,7 @@
 		dsysdebug(error, ("vop_getattr %s %d\n", dv->dv_name, error));
 		dv_vattr_merge(dv, vap);
 	}
+	rw_exit(&dv->dv_contents);
 
 	/*
 	 * Restrict the permissions of the node fronting the console
@@ -241,7 +243,7 @@
 again:	if (dv->dv_attr) {
 
 		error = secpolicy_vnode_setattr(cr, vp, vap,
-				dv->dv_attr, flags, devfs_unlocked_access, dv);
+		    dv->dv_attr, flags, devfs_unlocked_access, dv);
 
 		if (error)
 			goto out;
@@ -281,9 +283,9 @@
 		if (vap->va_mask & (AT_MODE|AT_UID|AT_GID|AT_ATIME|AT_MTIME)) {
 			/* Set the attributes */
 			error = VOP_SETATTR(dv->dv_attrvp,
-				vap, flags, cr, NULL);
+			    vap, flags, cr, NULL);
 			dsysdebug(error,
-				("vop_setattr %s %d\n", dv->dv_name, error));
+			    ("vop_setattr %s %d\n", dv->dv_name, error));
 
 			/*
 			 * Some file systems may return EROFS for a setattr
@@ -459,8 +461,7 @@
 		ASSERT(vp->v_type != VDIR);
 		*vattrp = dv_vattr_file;
 		error = VOP_GETATTR(dv->dv_attrvp, vattrp, 0, cr, ct);
-		dsysdebug(error, ("vop_getattr %s %d\n",
-			dv->dv_name, error));
+		dsysdebug(error, ("vop_getattr %s %d\n", dv->dv_name, error));
 		if (error)
 			goto out;
 		dv->dv_attr = vattrp;
@@ -469,10 +470,10 @@
 	}
 
 	error = secpolicy_vnode_setattr(cr, vp, vap, dv->dv_attr,
-					flags, devfs_unlocked_access, dv);
+	    flags, devfs_unlocked_access, dv);
 	if (error) {
 		dsysdebug(error, ("devfs_setattr %s secpolicy error %d\n",
-			dv->dv_name, error));
+		    dv->dv_name, error));
 		goto out;
 	}
 
@@ -530,13 +531,13 @@
 		} else {
 			if (mask & AT_MODE)
 				dcmn_err5(("%s persisting mode 0%o\n",
-					dv->dv_name, vap->va_mode));
+				    dv->dv_name, vap->va_mode));
 			if (mask & AT_UID)
 				dcmn_err5(("%s persisting uid %d\n",
-					dv->dv_name, vap->va_uid));
+				    dv->dv_name, vap->va_uid));
 			if (mask & AT_GID)
 				dcmn_err5(("%s persisting gid %d\n",
-					dv->dv_name, vap->va_gid));
+				    dv->dv_name, vap->va_gid));
 
 			if (dv->dv_attrvp == NULL) {
 				dvp = DVTOV(dv->dv_dotdot);
@@ -734,16 +735,14 @@
 		return (EACCES);
 	}
 
+	rw_enter(&dv->dv_contents, RW_READER);
 	if (dv->dv_attr && ((dv->dv_flags & DV_ACL) == 0)) {
-		rw_enter(&dv->dv_contents, RW_READER);
-		if (dv->dv_attr) {
-			res = devfs_unlocked_access(dv, mode, cr);
-			rw_exit(&dv->dv_contents);
-			return (res);
-		}
-		rw_exit(&dv->dv_contents);
+		res = devfs_unlocked_access(dv, mode, cr);
+	} else {
+		res = VOP_ACCESS(dv->dv_attrvp, mode, flags, cr, ct);
 	}
-	return (VOP_ACCESS(dv->dv_attrvp, mode, flags, cr, ct));
+	rw_exit(&dv->dv_contents);
+	return (res);
 }
 
 /*
--- a/usr/src/uts/common/os/devcfg.c	Fri Feb 22 08:41:57 2008 -0800
+++ b/usr/src/uts/common/os/devcfg.c	Fri Feb 22 09:02:16 2008 -0800
@@ -7314,7 +7314,7 @@
 void
 i_ddi_di_cache_invalidate(int kmflag)
 {
-	uint_t	flag;
+	int	cache_valid;
 
 	if (!modrootloaded || !i_ddi_io_initialized()) {
 		if (di_cache_debug)
@@ -7322,16 +7322,15 @@
 		return;
 	}
 
-	/*
-	 * Invalidate the in-core cache and
-	 * increment devtree generation number
-	 */
-	atomic_and_32(&di_cache.cache_valid, 0);
+	/* Increment devtree generation number. */
 	atomic_inc_ulong(&devtree_gen);
 
-	flag = (kmflag == KM_SLEEP) ? TQ_SLEEP : TQ_NOSLEEP;
-
-	(void) taskq_dispatch(system_taskq, free_cache_task, NULL, flag);
+	/* Invalidate the in-core cache and dispatch free on valid->invalid */
+	cache_valid = atomic_swap_uint(&di_cache.cache_valid, 0);
+	if (cache_valid) {
+		(void) taskq_dispatch(system_taskq, free_cache_task, NULL,
+		    (kmflag == KM_SLEEP) ? TQ_SLEEP : TQ_NOSLEEP);
+	}
 
 	if (di_cache_debug) {
 		cmn_err(CE_NOTE, "invalidation with km_flag: %s",
--- a/usr/src/uts/common/os/modctl.c	Fri Feb 22 08:41:57 2008 -0800
+++ b/usr/src/uts/common/os/modctl.c	Fri Feb 22 09:02:16 2008 -0800
@@ -20,7 +20,7 @@
  */
 
 /*
- * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
+ * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
  */
 
@@ -2046,7 +2046,7 @@
 		goto err;
 
 	if ((ret = sdev_modctl_readdir(dir, &dirlist,
-	    &npaths, &npaths_alloc)) != 0) {
+	    &npaths, &npaths_alloc, 0)) != 0) {
 		ASSERT(dirlist == NULL);
 		goto err;
 	}
@@ -2093,6 +2093,42 @@
 	return (ret);
 }
 
+static int
+modctl_devemptydir(const char *udir, int udirlen, int *uempty)
+{
+	char	*dir;
+	int	ret;
+	char	**dirlist = NULL;
+	int	npaths;
+	int	npaths_alloc;
+	int	empty;
+
+	/*
+	 * copyin the /dev path including terminating null
+	 */
+	udirlen++;
+	if (udirlen <= 1 || udirlen > MAXPATHLEN)
+		return (EINVAL);
+	dir = kmem_zalloc(udirlen + 1, KM_SLEEP);
+	if ((ret = copyinstr(udir, dir, udirlen, NULL)) != 0)
+		goto err;
+
+	if ((ret = sdev_modctl_readdir(dir, &dirlist,
+	    &npaths, &npaths_alloc, 1)) != 0) {
+		goto err;
+	}
+
+	empty = npaths ? 0 : 1;
+	if (copyout(&empty, uempty, sizeof (empty)))
+		ret = EFAULT;
+
+err:
+	if (dirlist)
+		sdev_modctl_readdir_free(dirlist, npaths, npaths_alloc);
+	kmem_free(dir, udirlen + 1);
+	return (ret);
+}
+
 int
 modctl_moddevname(int subcmd, uintptr_t a1, uintptr_t a2)
 {
@@ -2347,6 +2383,11 @@
 		    (char *)a3, (int64_t *)a4);
 		break;
 
+	case MODDEVEMPTYDIR:	/* non-reconfiguring /dev emptydir */
+		error = modctl_devemptydir((const char *)a1, (size_t)a2,
+		    (int *)a3);
+		break;
+
 	case MODDEVNAME:
 		error = modctl_moddevname((int)a1, a2, a3);
 		break;
--- a/usr/src/uts/common/sys/fs/sdev_impl.h	Fri Feb 22 08:41:57 2008 -0800
+++ b/usr/src/uts/common/sys/fs/sdev_impl.h	Fri Feb 22 09:02:16 2008 -0800
@@ -624,7 +624,7 @@
 /*
  * devinfo helpers
  */
-extern int sdev_modctl_readdir(const char *, char ***, int *, int *);
+extern int sdev_modctl_readdir(const char *, char ***, int *, int *, int);
 extern void sdev_modctl_readdir_free(char **, int, int);
 extern int sdev_modctl_devexists(const char *);
 
--- a/usr/src/uts/common/sys/modctl.h	Fri Feb 22 08:41:57 2008 -0800
+++ b/usr/src/uts/common/sys/modctl.h	Fri Feb 22 09:02:16 2008 -0800
@@ -19,7 +19,7 @@
  * CDDL HEADER END
  */
 /*
- * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
+ * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
  */
 
@@ -269,6 +269,7 @@
 #define	MODRETIRE		40
 #define	MODUNRETIRE		41
 #define	MODISRETIRED		42
+#define	MODDEVEMPTYDIR		43
 
 /*
  * sub cmds for MODEVENTS