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