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 import ae.sys.process : getCurrentUser;
24 
25 version (Posix) import core.sys.posix.unistd : geteuid;
26 
27 static File pidFile;
28 
29 void createPidFile(
30 	string name = defaultPidFileName(),
31 	string path = defaultPidFilePath())
32 {
33 	auto fullPath = buildPath(path, name);
34 	assert(!pidFile.isOpen(), "A PID file has already been created for this program / process");
35 	pidFile.open(fullPath, "w+b");
36 	scope(failure) pidFile.close();
37 	enforce(pidFile.tryLock(), "Failed to acquire lock on PID file " ~ fullPath ~ ". Is another instance running?");
38 	pidFile.write(thisProcessID);
39 	pidFile.flush();
40 }
41 
42 string defaultPidFileName()
43 {
44 	return text(getCurrentUser(), "-", thisExePath.baseName, ".pid");
45 }
46 
47 string defaultPidFilePath()
48 {
49 	version (Posix)
50 	{
51 		if (geteuid() == 0)
52 			return "/var/run";
53 		else
54 			return tempDir; // /var/run and /var/lock are usually not writable for non-root processes
55 	}
56 	version (Windows)
57 		return tempDir;
58 }
59 
60 unittest
61 {
62 	createPidFile();
63 
64 	auto realPidFile = pidFile;
65 	pidFile = File.init;
66 
67 	static void runForked(void delegate() code)
68 	{
69 		version (Posix)
70 		{
71 			// Since locks are per-process, we cannot test lock failures within
72 			// the same process. fork() is used to create a second process.
73 			import core.sys.posix.sys.wait : wait;
74 			import core.sys.posix.unistd : fork, _exit;
75 			int child, status;
76 			if ((child = fork()) == 0)
77 			{
78 				code();
79 				_exit(0);
80 			}
81 			else
82 			{
83 				assert(wait(&status) != -1);
84 				assert(status == 0, "Fork crashed");
85 			}
86 		}
87 		else
88 			code();
89 	}
90 
91 	runForked({
92 		assertThrown!Exception(createPidFile);
93 		assert(!pidFile.isOpen);
94 	});
95 	
96 	realPidFile.close();
97 
98 	runForked({
99 		createPidFile();
100 	});
101 }