autofs-5.0.7 - fix file descriptor leak when reloading the daemon

From: Leonardo Chiquitto <leonardo.lists@gmail.com>

A customer reported that AutoFS may leak file descriptors when some
maps are modified and the daemon reloaded. I'm able to reproduce the
problem on 5.0.7 by following these steps:

1. Configure a simple direct mount:

# cat /etc/auto.master
/-	/etc/auto.direct

# cat /etc/auto.direct
/nfs   server:/nfs

2. Start the automounter and do NOT trigger the mount

3. Replace /etc/auto.direct with:

# cat /etc/auto.direct
/nfs/1  server:/nfs
/nfs/2  server:/nfs

4. Reload:

# kill -HUP $(pidof automount)

>From now on, every reload will leak a file descriptor:

# ls -la /proc/$(pidof automount)/fd | grep /nfs
lr-x------ 1 root root 64 Aug 14 22:08 11 -> /nfs
lr-x------ 1 root root 64 Aug 14 22:08 12 -> /nfs
lr-x------ 1 root root 64 Aug 14 22:08 13 -> /nfs
lr-x------ 1 root root 64 Aug 14 22:08 14 -> /nfs
lr-x------ 1 root root 64 Aug 14 22:08 5 -> /nfs

I've investigated the problem and discovered that the leak happens in
do_umount_autofs_direct():

- edit imk
The same leak is present in umount_autofs_offset() also.
Updated patch to cover that too.
- end edit

int do_umount_autofs_direct(struct autofs_point *ap, struct mnt_list
*mnts, struct mapent *me)
{
(...)
	if (me->ioctlfd != -1) {
		if (tree_is_mounted(mnts, me->key, MNTS_REAL)) {
			error(ap->logopt,
			      "attempt to umount busy direct mount %s",
			      me->key);
			return 1;
		}
		ioctlfd = me->ioctlfd;
	} else	// ioctlfd == -1
		ops->open(ap->logopt, &ioctlfd, me->dev, me->key);  <= we open it here

	if (ioctlfd >= 0) {
		unsigned int status = 1;

		rv = ops->askumount(ap->logopt, ioctlfd, &status);
				/// at this point, rv == 0 and status == 0
		if (rv) {
			char *estr = strerror_r(errno, buf, MAX_ERR_BUF);
			error(ap->logopt, "ioctl failed: %s", estr);
			return 1;
		} else if (!status) {
				/// at this point, ap->state == ST_READMAP
			if (ap->state != ST_SHUTDOWN_FORCE) {
				error(ap->logopt,
				      "ask umount returned busy for %s",
				      me->key);
				return 1;			<= we return here, without closing the fd
			} else {
				me->ioctlfd = -1;
				ops->catatonic(ap->logopt, ioctlfd);
				ops->close(ap->logopt, ioctlfd);
				goto force_umount;
			}
(...)
---
 CHANGELOG       |    1 +
 daemon/direct.c |   19 ++++++++++++++++---
 2 files changed, 17 insertions(+), 3 deletions(-)

diff --git a/CHANGELOG b/CHANGELOG
index 46ef335..a7ed212 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -30,6 +30,7 @@
 - workaround missing GNU versionsort extension.
 - dont fail on master map self include.
 - fix wildcard multi map regression.
+- fix file descriptor leak when reloading the daemon.
 
 25/07/2012 autofs-5.0.7
 =======================
diff --git a/daemon/direct.c b/daemon/direct.c
index 3e09c5d..228a666 100644
--- a/daemon/direct.c
+++ b/daemon/direct.c
@@ -86,7 +86,8 @@ int do_umount_autofs_direct(struct autofs_point *ap, struct mnt_list *mnts, stru
 {
 	struct ioctl_ops *ops = get_ioctl_ops();
 	char buf[MAX_ERR_BUF];
-	int ioctlfd, rv, left, retries;
+	int ioctlfd = -1, rv, left, retries;
+	int opened = 0;
 
 	left = umount_multi(ap, me->key, 0);
 	if (left) {
@@ -103,8 +104,10 @@ int do_umount_autofs_direct(struct autofs_point *ap, struct mnt_list *mnts, stru
 			return 1;
 		}
 		ioctlfd = me->ioctlfd;
-	} else
+	} else {
 		ops->open(ap->logopt, &ioctlfd, me->dev, me->key);
+		opened = 1;
+	}
 
 	if (ioctlfd >= 0) {
 		unsigned int status = 1;
@@ -113,12 +116,16 @@ int do_umount_autofs_direct(struct autofs_point *ap, struct mnt_list *mnts, stru
 		if (rv) {
 			char *estr = strerror_r(errno, buf, MAX_ERR_BUF);
 			error(ap->logopt, "ioctl failed: %s", estr);
+			if (opened && ioctlfd != -1)
+				ops->close(ap->logopt, ioctlfd);
 			return 1;
 		} else if (!status) {
 			if (ap->state != ST_SHUTDOWN_FORCE) {
 				error(ap->logopt,
 				      "ask umount returned busy for %s",
 				      me->key);
+				if (opened && ioctlfd != -1)
+					ops->close(ap->logopt, ioctlfd);
 				return 1;
 			} else {
 				me->ioctlfd = -1;
@@ -536,7 +543,8 @@ int umount_autofs_offset(struct autofs_point *ap, struct mapent *me)
 {
 	struct ioctl_ops *ops = get_ioctl_ops();
 	char buf[MAX_ERR_BUF];
-	int ioctlfd, rv = 1, retries;
+	int ioctlfd = -1, rv = 1, retries;
+	int opened = 0;
 
 	if (me->ioctlfd != -1) {
 		if (is_mounted(_PATH_MOUNTED, me->key, MNTS_REAL)) {
@@ -554,6 +562,7 @@ int umount_autofs_offset(struct autofs_point *ap, struct mapent *me)
 			return 0;
 		}
 		ops->open(ap->logopt, &ioctlfd, me->dev, me->key);
+		opened = 1;
 	}
 
 	if (ioctlfd >= 0) {
@@ -563,6 +572,8 @@ int umount_autofs_offset(struct autofs_point *ap, struct mapent *me)
 		if (rv) {
 			char *estr = strerror_r(errno, buf, MAX_ERR_BUF);
 			logerr("ioctl failed: %s", estr);
+			if (opened && ioctlfd != -1)
+				ops->close(ap->logopt, ioctlfd);
 			return 1;
 		} else if (!status) {
 			if (ap->state != ST_SHUTDOWN_FORCE) {
@@ -570,6 +581,8 @@ int umount_autofs_offset(struct autofs_point *ap, struct mapent *me)
 					error(ap->logopt,
 					     "ask umount returned busy for %s",
 					     me->key);
+				if (opened && ioctlfd != -1)
+					ops->close(ap->logopt, ioctlfd);
 				return 1;
 			} else {
 				me->ioctlfd = -1;
