diff --git a/src/sbbs3/js_global.c b/src/sbbs3/js_global.c
index f2e2f6c58f2cb633a1dc65c6211530365ce102e0..3ae0f8110e7cb9f6e2c511e919aa9e519b3aa5e0 100644
--- a/src/sbbs3/js_global.c
+++ b/src/sbbs3/js_global.c
@@ -8,7 +8,7 @@
  * @format.tab-size 4		(Plain Text/Source Code File Header)			*
  * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
  *																			*
- * Copyright 2003 Rob Swindell - http://www.synchro.net/copyright.html		*
+ * Copyright 2004 Rob Swindell - http://www.synchro.net/copyright.html		*
  *																			*
  * This program is free software; you can redistribute it and/or			*
  * modify it under the terms of the GNU General Public License				*
@@ -1907,16 +1907,33 @@ js_flength(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
 
 
 static JSBool
-js_touch(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+js_ftouch(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
 {
-	char*		p;
+	char*		fname;
 
-	if((p=JS_GetStringBytes(JS_ValueToString(cx, argv[0])))==NULL) {
+	if((fname=JS_GetStringBytes(JS_ValueToString(cx, argv[0])))==NULL) {
 		*rval = BOOLEAN_TO_JSVAL(JS_FALSE);
 		return(JS_TRUE);
 	}
 
-	*rval = BOOLEAN_TO_JSVAL(ftouch(p));
+	*rval = BOOLEAN_TO_JSVAL(ftouch(fname));
+	return(JS_TRUE);
+}
+
+static JSBool
+js_fmutex(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+{
+	char*		fname;
+	char*		text=NULL;
+
+	if((fname=JS_GetStringBytes(JS_ValueToString(cx, argv[0])))==NULL) {
+		*rval = BOOLEAN_TO_JSVAL(JS_FALSE);
+		return(JS_TRUE);
+	}
+	if(argc>1)
+		text=JS_GetStringBytes(JS_ValueToString(cx,argv[1]));
+
+	*rval = BOOLEAN_TO_JSVAL(fmutex(fname,text));
 	return(JS_TRUE);
 }
 		
@@ -2329,11 +2346,16 @@ static jsSyncMethodSpec js_global_functions[] = {
 		"or change to current time")
 	,311
 	},
-	{"file_touch",		js_touch,			1,	JSTYPE_BOOLEAN,	JSDOCSTR("string filename")
+	{"file_touch",		js_ftouch,			1,	JSTYPE_BOOLEAN,	JSDOCSTR("string filename")
 	,JSDOCSTR("updates a file's last modification date/time to current time, "
 		"creating an empty file if it doesn't already exist")
 	,311
 	},
+	{"file_mutex",		js_fmutex,			1,	JSTYPE_BOOLEAN,	JSDOCSTR("string filename [,text]")
+	,JSDOCSTR("attempts to create an exclusive (e.g. lock) file, "
+		"optinally with the contents of <i>text</i>")
+	,311
+	},
 	{"directory",		js_directory,		1,	JSTYPE_ARRAY,	JSDOCSTR("string pattern [,flags]")
 	,JSDOCSTR("returns an array of directory entries, "
 		"<i>pattern</i> is the path and filename or wildcards to search for (e.g. '/subdir/*.txt'), "
diff --git a/src/sbbs3/main.cpp b/src/sbbs3/main.cpp
index cc7903c86aa53c44ea3196e671a7cad30ecb2164..6386bb87b6301cb57636d3d0dcb3bd4ab163e7f7 100644
--- a/src/sbbs3/main.cpp
+++ b/src/sbbs3/main.cpp
@@ -1604,7 +1604,7 @@ void event_thread(void* arg)
 				getuserdat(&sbbs->cfg,&sbbs->useron);
 				if(sbbs->useron.number && flength(g.gl_pathv[i])>0) {
 					sprintf(semfile,"%s.lock",g.gl_pathv[i]);
-					if((file=open(semfile,O_CREAT|O_WRONLY|O_EXCL,S_IREAD|S_IWRITE))==-1)
+					if(!fmutex(semfile,startup->host_name))
 						continue;
 					sbbs->online=ON_LOCAL;
 					eprintf(LOG_INFO,"Un-packing QWK Reply packet from %s",sbbs->useron.alias);
@@ -1615,7 +1615,6 @@ void event_thread(void* arg)
 					
 					/* putuserdat? */
 					remove(g.gl_pathv[i]);
-					close(file);
 					remove(semfile);
 				}
 			}
@@ -1628,7 +1627,7 @@ void event_thread(void* arg)
 			for(i=0;i<(int)g.gl_pathc;i++) {
 				sbbs->useron.number=atoi(g.gl_pathv[i]+offset);
 				sprintf(semfile,"%spack%04u.lock",sbbs->cfg.data_dir,sbbs->useron.number);
-				if((file=open(semfile,O_CREAT|O_WRONLY|O_EXCL,S_IREAD|S_IWRITE))==-1)
+				if(!fmutex(semfile,startup->host_name))
 					continue;
 				getuserdat(&sbbs->cfg,&sbbs->useron);
 				if(sbbs->useron.number && !(sbbs->useron.misc&(DELETED|INACTIVE))) {
@@ -1656,7 +1655,6 @@ void event_thread(void* arg)
 					sbbs->online=0;
 				}
 				remove(g.gl_pathv[i]);
-				close(file);
 				remove(semfile);
 			}
 			globfree(&g);
diff --git a/src/sbbs3/nopen.c b/src/sbbs3/nopen.c
index 3d758ca5021bc5d30b9ca825b4f9e294d67c8a9a..4ca29e9647d0d5d4a5c053ae99a0deb877d30442 100644
--- a/src/sbbs3/nopen.c
+++ b/src/sbbs3/nopen.c
@@ -105,10 +105,21 @@ BOOL ftouch(const char* fname)
 {
 	int file;
 
-	file=nopen(fname,O_WRONLY|O_CREAT);
-	if(file<0)
+	if((file=nopen(fname,O_WRONLY|O_CREAT))<0)
 		return(FALSE);
 	close(file);
 
 	return(TRUE);
 }
+
+BOOL fmutex(const char* fname, const char* text)
+{
+	int file;
+
+	if((file=open(fname,O_CREAT|O_WRONLY|O_EXCL,S_IREAD|S_IWRITE))<0)
+		return(FALSE);
+	if(text!=NULL)
+		write(file,text,strlen(text));
+	close(file);
+	return(TRUE);
+}
diff --git a/src/sbbs3/sbbs.h b/src/sbbs3/sbbs.h
index f1a0b303d2f473087613fa466341f857652e57ae..1414526195950c132eb9631877f1cf4f6a7acb53 100644
--- a/src/sbbs3/sbbs.h
+++ b/src/sbbs3/sbbs.h
@@ -995,6 +995,7 @@ int		strsame(char *str1, char *str2);	/* Compares number of same chars */
 int		nopen(const char* str, int access);
 FILE *	fnopen(int* file, const char* str, int access);
 BOOL	ftouch(const char* fname);
+BOOL	fmutex(const char* fname, const char* text);
 
 /* load_cfg.c */
 BOOL 	md(char *path);