1 /**
2  * PID file and lock
3  *
4  * License:
5  *   This Source Code Form is subject to the terms of
6  *   the Mozilla Public License, v. 2.0. If a copy of
7  *   the MPL was not distributed with this file, You
8  *   can obtain one at http://mozilla.org/MPL/2.0/.
9  *
10  * Authors:
11  *   Vladimir Panteleev <vladimir@thecybershadow.net>
12  */
13 
14 module ae.sys.pidfile;
15 
16 import std.conv : text;
17 import std.exception;
18 import std.file : thisExePath, tempDir;
19 import std.path : baseName, buildPath;
20 import std.process : thisProcessID;
21 import std.stdio : File;
22 
23 version (Posix)
24 {
25 	import core.sys.posix.unistd : getlogin, geteuid;
26 	import std.process : environment;
27 	import std.string : fromStringz;
28 }
29 version (Windows)
30 {
31 	import core.sys.windows.lmcons : UNLEN;
32 	import core.sys.windows.winbase : GetUserNameW;
33 	import core.sys.windows.windef : DWORD;
34 	import core.sys.windows.winnt : WCHAR;
35 	import ae.sys.windows.exception : wenforce;
36 	import ae.sys.windows.text : fromWString;
37 }
38 
39 static File pidFile;
40 
41 void createPidFile(
42 	string name = defaultPidFileName(),
43 	string path = defaultPidFilePath())
44 {
45 	auto fullPath = buildPath(path, name);
46 	assert(!pidFile.isOpen(), "A PID file has already been created for this program / process");
47 	pidFile.open(fullPath, "w+b");
48 	scope(failure) pidFile.close();
49 	enforce(pidFile.tryLock(), "Failed to acquire lock on PID file " ~ fullPath ~ ". Is another instance running?");
50 	pidFile.write(thisProcessID);
51 	pidFile.flush();
52 }
53 
54 string defaultPidFileName()
55 {
56 	version (Posix)
57 		auto userName = environment.get("LOGNAME", cast(string)getlogin().fromStringz);
58 	version (Windows)
59 	{
60 		WCHAR[UNLEN + 1] buf;
61 		DWORD len = buf.length;
62 		GetUserNameW(buf.ptr, &len).wenforce("GetUserNameW");
63 		auto userName = buf[].fromWString();
64 	}
65 
66 	return text(userName, "-", thisExePath.baseName, ".pid");
67 }
68 
69 string defaultPidFilePath()
70 {
71 	version (Posix)
72 	{
73 		if (geteuid() == 0)
74 			return "/var/run";
75 		else
76 			return tempDir; // /var/run and /var/lock are usually not writable for non-root processes
77 	}
78 	version (Windows)
79 		return tempDir;
80 }
81 
82 unittest
83 {
84 	createPidFile();
85 
86 	auto realPidFile = pidFile;
87 	pidFile = File.init;
88 
89 	static void runForked(void delegate() code)
90 	{
91 		version (Posix)
92 		{
93 			// Since locks are per-process, we cannot test lock failures within
94 			// the same process. fork() is used to create a second process.
95 			import core.stdc.stdlib : exit;
96 			import core.sys.posix.sys.wait : wait;
97 			import core.sys.posix.unistd : fork;
98 			int child, status;
99 			if ((child = fork()) == 0)
100 			{
101 				code();
102 				exit(0);
103 			}
104 			else
105 			{
106 				assert(wait(&status) != -1);
107 				assert(status == 0, "Fork crashed");
108 			}
109 		}
110 		else
111 			code();
112 	}
113 
114 	runForked({
115 		assertThrown!Exception(createPidFile);
116 		assert(!pidFile.isOpen);
117 	});
118 	
119 	realPidFile.close();
120 
121 	runForked({
122 		createPidFile();
123 	});
124 }