1 /******************************************************************************* 2 3 PID file that prevents multiple instances of an app opening in the same dir. 4 5 Copyright: 6 Copyright (c) 2018 dunnhumby Germany GmbH. 7 All rights reserved. 8 9 License: 10 Boost Software License Version 1.0. See LICENSE_BOOST.txt for details. 11 Alternatively, this file may be distributed under the terms of the Tango 12 3-Clause BSD License (see LICENSE_BSD.txt for details). 13 14 *******************************************************************************/ 15 16 module ocean.application.components.PidLock; 17 18 import ocean.meta.types.Qualifiers; 19 20 /// ditto 21 public struct PidLock 22 { 23 import ocean.core.Enforce; 24 import ocean.meta.codegen.Identifier; 25 import ocean.text.convert.Formatter; 26 import ocean.text.util.StringC; 27 import ocean.sys.ErrnoException; 28 import ocean.util.config.ConfigParser; 29 30 import core.stdc.errno; 31 import core.stdc.stdio; 32 import core.stdc.stdlib; 33 import core.stdc.string; 34 import core.sys.posix.unistd: write, close, ftruncate, getpid, unlink; 35 import core.sys.posix.sys.stat; 36 import core.sys.posix.fcntl; 37 38 /*************************************************************************** 39 40 Fd referring to the pidfile. 41 42 ***************************************************************************/ 43 44 private int lock_fd; 45 46 /*************************************************************************** 47 48 Indicator if the pidfile is locked by this instance. 49 50 ***************************************************************************/ 51 52 private bool is_locked; 53 54 /*************************************************************************** 55 56 Path to the pidlock file. 57 58 ***************************************************************************/ 59 60 private mstring pidlock_path; 61 62 /*************************************************************************** 63 64 Parse the configuration file options to set up the pid lock. 65 66 Params: 67 config = configuration instance 68 69 ***************************************************************************/ 70 71 public void parseConfig ( ConfigParser config ) 72 { 73 this.pidlock_path = config.get("PidLock", "path", "").dup; 74 } 75 76 /*************************************************************************** 77 78 Tries to lock the pid file. 79 80 Throws: 81 Exception if the locking is not successful. 82 83 ***************************************************************************/ 84 85 public void lock ( ) 86 { 87 if (this.pidlock_path.length) 88 { 89 istring msg = 90 idup("Couldn't lock the pid lock file '" ~ this.pidlock_path 91 ~ "'. Probably another instance of the application is running."); 92 enforce(this.tryLockPidFile(), msg); 93 } 94 } 95 96 /*************************************************************************** 97 98 Cleans up behind and releases the lock file. 99 100 Throws: 101 ErrnoException if any of the system calls fails 102 103 ***************************************************************************/ 104 105 public void unlock ( ) 106 { 107 if (!this.is_locked) 108 { 109 return; 110 } 111 112 // releases the lock and closes the lock file 113 this.enforcePosix!(close)(this.lock_fd); 114 this.enforcePosix!(unlink)(StringC.toCString(this.pidlock_path)); 115 } 116 117 /*************************************************************************** 118 119 Called to try to create and lock the pidlock file. On success 120 this will create and lock the file pointed by `this.pidlock_path`, 121 and it will write out the application pid into it. 122 123 Params: 124 pidlock = path to the pidlock file 125 126 Returns: 127 true if the lock has been successful, false otherwise. 128 129 Throws: 130 ErrnoException if any of the system calls fails for unexpected 131 reasons 132 133 ***************************************************************************/ 134 135 private bool tryLockPidFile ( ) 136 { 137 this.lock_fd = this.enforcePosix!(open)(StringC.toCString(this.pidlock_path), 138 O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); 139 140 // TODO these will be available in tango v1.6.0 141 enum F_SETLK = 6; 142 143 // Lock the pidfile 144 flock fl; 145 fl.l_type = F_WRLCK; 146 fl.l_whence = SEEK_SET; 147 fl.l_start = 0; 148 fl.l_len = 0; 149 150 if (fcntl(this.lock_fd, F_SETLK, &fl) == -1) 151 { 152 // Region already locked, can't acquire a lock 153 if (errno == EAGAIN || errno == EACCES) 154 { 155 return false; 156 } 157 else 158 { 159 throw (new ErrnoException).useGlobalErrno("fcntl"); 160 } 161 } 162 163 // Clear the any previous contents of the file 164 this.enforcePosix!(ftruncate)(this.lock_fd, 0); 165 166 char[512] buf; 167 auto pid_string = snformat(buf, "{}\n", getpid()); 168 169 this.writeNonInterrupted(this.lock_fd, pid_string); 170 171 this.is_locked = true; 172 return true; 173 } 174 175 /*************************************************************************** 176 177 Calls the function with the provided arguments and throws 178 new ErrnoException on failure (indicated by -1 return code) 179 180 Params: 181 Func = function to call 182 args = arguments to call the function with 183 184 Returns: 185 return value of Func(args) on success 186 187 Throws: 188 new ErrnoException on failure. 189 190 ***************************************************************************/ 191 192 static private int enforcePosix(alias Func, Args...)(Args args) 193 { 194 auto ret = Func(args); 195 196 if (ret == -1) 197 { 198 throw (new ErrnoException).useGlobalErrno(identifier!(Func)); 199 } 200 201 return ret; 202 } 203 204 /*************************************************************************** 205 206 Writes the content of buffer to fd, repeating if interrupted by 207 signal. 208 209 Params: 210 fd = file descriptor to write to 211 buf = buffer contents to write 212 213 Throws: 214 ErrnoException on failure. 215 216 ***************************************************************************/ 217 218 static private void writeNonInterrupted(int fd, char[] buf) 219 { 220 int count; 221 while (count < buf.length) 222 { 223 auto written = write(fd, buf.ptr + count, 224 buf.length - count); 225 226 if (written < 0) 227 { 228 if (errno == EINTR) 229 { 230 continue; 231 } 232 else 233 { 234 throw (new ErrnoException).useGlobalErrno("write"); 235 } 236 } 237 238 count += written; 239 } 240 } 241 }