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 }