diff --git a/src/sbbs3/ftpsrvr.c b/src/sbbs3/ftpsrvr.c
index a38019a1fa837849b02cd68c6b455926932a6e7b..da1801f7ba9f50ae40f13e471679f41a1913d730 100644
--- a/src/sbbs3/ftpsrvr.c
+++ b/src/sbbs3/ftpsrvr.c
@@ -2166,7 +2166,7 @@ static void ctrl_thread(void* arg)
 	int			curdir=-1;
 	int			orglib;
 	int			orgdir;
-	int			mutex_file = -1;
+	fmutex_t	mutex_file = {-1};
 	long		filepos=0L;
 	long		timeleft;
 	ulong		l;
@@ -2607,9 +2607,9 @@ static void ctrl_thread(void* arg)
 			if(user.rest & FLAG('Q')) { // QWKnet accont
 				char mutex_fname[MAX_PATH + 1];
 				snprintf(mutex_fname, sizeof mutex_fname, "%suser/%04u.ftp", scfg.data_dir, user.number);
-				if((mutex_file = fmutex_open(mutex_fname, startup->host_name, /* max_age: */60 * 60, &t, /* auto_remove: */true)) < 0) {
+				if(!fmutex_open(mutex_fname, startup->host_name, /* max_age: */60 * 60, /* auto_remove: */true, &mutex_file)) {
 					lprintf(LOG_NOTICE, "%04d <%s> QWKnet account already logged-in to FTP server: %s (since %s)"
-						,sock, user.alias, mutex_fname, time_as_hhmm(&scfg, t, str));
+						,sock, user.alias, mutex_fname, time_as_hhmm(&scfg, mutex_file.time, str));
 					sockprintf(sock, sess, "421 QWKnet accounts are limited to one concurrent FTP session");
 					user.number = 0;
 					break;
diff --git a/src/sbbs3/main.cpp b/src/sbbs3/main.cpp
index a3fa5924a2fc02df80088f0f23daddc94e17c934..1b0d511aeb734105254f7c62c99667a95f958ade 100644
--- a/src/sbbs3/main.cpp
+++ b/src/sbbs3/main.cpp
@@ -2918,12 +2918,11 @@ void event_thread(void* arg)
 				sbbs->useron.number = atoi(fname+offset);
 				getuserdat(&sbbs->cfg,&sbbs->useron);
 				if(sbbs->useron.number != 0 && !(sbbs->useron.misc&(DELETED|INACTIVE))) {
-					time_t t;
 					SAFEPRINTF(lockfname,"%s.lock",fname);
-					int lockfile = fmutex_open(lockfname,startup->host_name,TIMEOUT_MUTEX_FILE, &t, true);
-					if(lockfile < 0) {
-						if(difftime(time(NULL), t) > 60)
-							sbbs->lprintf(LOG_INFO," %s exists (unpack in progress?) since %s", lockfname, time_as_hhmm(&sbbs->cfg, t, str));
+					fmutex_t lockfile;
+					if(!fmutex_open(lockfname, startup->host_name, TIMEOUT_MUTEX_FILE, true, &lockfile)) {
+						if(difftime(time(NULL), lockfile.time) > 60)
+							sbbs->lprintf(LOG_INFO," %s exists (unpack in progress?) since %s", lockfname, time_as_hhmm(&sbbs->cfg, lockfile.time, str));
 						continue;
 					}
 					sbbs->lprintf(LOG_DEBUG, "Opened %s", lockfname);
@@ -2980,7 +2979,6 @@ void event_thread(void* arg)
 				sbbs->useron.number = 0;
 				sbbs->lprintf(LOG_INFO, "QWK pack semaphore signaled: %s", fname);
 				int usernum = atoi(fname+offset);
-				time_t t;
 				sbbs->useron.number = usernum;
 				int retval = getuserdat(&sbbs->cfg,&sbbs->useron);
 				if(retval != 0) {
@@ -2989,10 +2987,10 @@ void event_thread(void* arg)
 					continue;
 				}
 				SAFEPRINTF2(lockfname,"%spack%04u.lock",sbbs->cfg.data_dir,usernum);
-				int lockfile = fmutex_open(lockfname,startup->host_name,TIMEOUT_MUTEX_FILE, &t, true);
-				if(lockfile < 0) {
-					if(difftime(time(NULL), t) > 60)
-						sbbs->lprintf(LOG_INFO,"%s exists (pack in progress?) since %s", lockfname, time_as_hhmm(&sbbs->cfg, t, str));
+				fmutex_t lockfile;
+				if(!fmutex_open(lockfname,startup->host_name,TIMEOUT_MUTEX_FILE, true, &lockfile)) {
+					if(difftime(time(NULL), lockfile.time) > 60)
+						sbbs->lprintf(LOG_INFO,"%s exists (pack in progress?) since %s", lockfname, time_as_hhmm(&sbbs->cfg, lockfile.time, str));
 					continue;
 				}
 				sbbs->lprintf(LOG_DEBUG, "Opened %s", lockfname);
diff --git a/src/sbbs3/nopen.c b/src/sbbs3/nopen.c
index cfc51fa4c71a94148cadde5e7c3b7f62599bfdd7..c176330ac3491d0a7b7ba675bde0ecb0574f0492 100644
--- a/src/sbbs3/nopen.c
+++ b/src/sbbs3/nopen.c
@@ -113,29 +113,30 @@ bool ftouch(const char* fname)
 }
 
 // Opens a mutex file and returns its file descriptor or -1 on failure
-int fmutex_open(const char* fname, const char* text, long max_age, time_t* tp, bool auto_remove)
+bool fmutex_open(const char* fname, const char* text, long max_age, bool auto_remove, fmutex_t* fm)
 {
-	int file;
-	time_t t;
 	size_t len;
 #if !defined(NO_SOCKET_SUPPORT)
 	char hostname[128];
 #endif
-#ifdef _WIN32
+#if defined _WIN32
 	DWORD attributes = FILE_ATTRIBUTE_NORMAL;
 	HANDLE h;
 #endif
 
-	if(max_age > 0 || tp != NULL) {
-		if(tp == NULL)
-			tp = &t;
-		*tp = fdate(fname);
-		if(max_age > 0 && *tp != -1 && (time(NULL) - *tp) > max_age) {
-			if(remove(fname)!=0)
-				return -1;
+	if(fm == NULL)
+		return false;
+	memset(fm, 0, sizeof *fm);
+	snprintf(fm->name, sizeof fm->name, fname);
+	fm->remove = auto_remove;
+	if(max_age > 0) {
+		fm->time = fdate(fname);
+		if(max_age > 0 && fm->time != -1 && (time(NULL) - fm->time) > max_age) {
+			if(remove(fname) != 0)
+				return false;
 		}
 	}
-#ifdef _WIN32
+#if defined _WIN32
 	if(auto_remove)
 		attributes |= FILE_FLAG_DELETE_ON_CLOSE;
 	h = CreateFileA(fname,
@@ -147,21 +148,14 @@ int fmutex_open(const char* fname, const char* text, long max_age, time_t* tp, b
 		NULL			// hTemplateFile
 		);
 	if(h == INVALID_HANDLE_VALUE)
-		return -1;
-	if((file = _open_osfhandle((intptr_t)h, O_WRONLY)) == -1) {
+		return false;
+	if((fm->fd = _open_osfhandle((intptr_t)h, O_WRONLY)) == -1) {
 		CloseHandle(h);
-		return -1;
+		return false;
 	}
 #else
-	if((file=sopen(fname, O_CREAT|O_WRONLY|O_EXCL, SH_DENYRW, DEFFILEMODE))<0)
-		return -1;
-	if(auto_remove) {
-		// the file will remain in existence until the last file descriptor referring to it is closed
-		if(remove(fname) != 0) {
-			close(file);
-			return -1;
-		}
-	}
+	if((fm->fd = sopen(fname, O_CREAT|O_WRONLY|O_EXCL, SH_DENYRW, DEFFILEMODE)) < 0)
+		return false;
 #endif
 #if !defined(NO_SOCKET_SUPPORT)
 	if(text==NULL && gethostname(hostname,sizeof(hostname))==0)
@@ -169,33 +163,43 @@ int fmutex_open(const char* fname, const char* text, long max_age, time_t* tp, b
 #endif
 	if(text!=NULL) {
 		len = strlen(text);
-		if(write(file, text, len) != len) {
-			close(file);
-			return -1;
+		if(write(fm->fd, text, len) != len) {
+			close(fm->fd);
+			return false;
 		}
 	}
-	return file;
+	return true;
 }
 
-bool fmutex_close(int* file)
+bool fmutex_close(fmutex_t* fm)
 {
-	if(file == NULL)
+	if(fm == NULL)
 		return false;
-	if(*file < 0) // already closed (or never opened)
+	if(fm->fd < 0) // already closed (or never opened)
 		return true;
-	if(close(*file) != 0)
+#if !defined _WIN32 // should only be necessary (and possible) on *nix
+	if(fm->remove) {
+		if(unlink(fm->name) != 0)
+			return false;
+	}
+#endif
+	if(close(fm->fd) != 0)
 		return false;
-	*file = -1;
+	fm->fd = -1;
 	return true;
 }
 
 // Opens and immediately closes a mutex file
 bool fmutex(const char* fname, const char* text, long max_age, time_t* tp)
 {
-	int file = fmutex_open(fname, text, max_age, tp, /* auto_remove: */false);
-	if(file < 0)
+	fmutex_t fm;
+
+	if(!fmutex_open(fname, text, max_age, /* auto_remove: */false, &fm)) {
+		if(tp != NULL)
+			*tp = fm.time;
 		return false;
-	return fmutex_close(&file);
+	}
+	return fmutex_close(&fm);
 }
 
 bool fcompare(const char* fn1, const char* fn2)
diff --git a/src/sbbs3/nopen.h b/src/sbbs3/nopen.h
index 466236d2a170941e1eca05cea32529f19bb51475..067acbd9de73ad1bf186ca5afb11cad51deb5b76 100644
--- a/src/sbbs3/nopen.h
+++ b/src/sbbs3/nopen.h
@@ -25,10 +25,18 @@
 #include <stdio.h>			/* FILE */
 #include <fcntl.h>			/* O_RDONLY */
 #include "gen_defs.h"		/* bool */
+#include "dirwrap.h"		/* MAX_PATH */
 
 #define FNOPEN_BUF_SIZE		(2*1024)
 #define LOOP_NOPEN	  100	/* Retries before file access denied			*/
 
+typedef struct {
+	int fd;
+	time_t time;
+	bool remove;
+	char name[MAX_PATH + 1];
+} fmutex_t;
+
 #ifdef __cplusplus
 extern "C" {
 #endif
@@ -37,8 +45,8 @@ int		nopen(const char* str, uint access);
 FILE *	fnopen(int* file, const char* str, uint access);
 bool	ftouch(const char* fname);
 bool	fmutex(const char* fname, const char* text, long max_age, time_t*);
-int		fmutex_open(const char* fname, const char* text, long max_age, time_t*, bool auto_remove);
-bool	fmutex_close(int* file);
+bool	fmutex_open(const char* fname, const char* text, long max_age, bool auto_remove, fmutex_t*);
+bool	fmutex_close(fmutex_t*);
 bool	fcompare(const char* fn1, const char* fn2);
 bool	backup(const char* org, int backup_level, bool ren);