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 }