libutil++  1.9.3
 All Classes Functions Variables
LockFile.cpp
1 /*
2 ** libutil++
3 ** $Id: LockFile.cpp 1653 2016-02-28 19:54:59Z sella $
4 ** Copyright (c) 2011-2016 Digital Genesis, LLC. All Rights Reserved.
5 ** Released under the LGPL Version 2.1 License.
6 ** http://www.digitalgenesis.com
7 */
8 
9 static const char rcsid[] __attribute__((used)) = "$Id: LockFile.cpp 1653 2016-02-28 19:54:59Z sella $";
10 
11 #include "LockFile.h"
12 #include "CommonMacro.h"
13 #include "String.h"
14 #include "File.h"
15 
16 #include <sys/file.h>
17 
18 using namespace sella::util;
19 
20 LockFile::LockFile(const std::string &fullpath, useconds_t stale, bool removeStale, uid_t uid, gid_t gid, int mode) throw () :
21  fullpath(fullpath),
22  stale(stale),
23  removeStale(removeStale),
24  uid(uid),
25  gid(gid),
26  mode(mode),
27  fd(0)
28 { }
29 
30 LockFile::~LockFile() {
31  try {
32  unlock();
33  } catch (...) {
34  close();
35  }
36 }
37 
38 void LockFile::setStaleAge(useconds_t stale) throw () {
39  if (stale > 0) {
40  this->stale = stale;
41  } else {
42  this->stale = -1;
43  }
44 }
45 
46 void LockFile::setUID(uid_t uid) throw () {
47  if (uid > (__UID_T_TYPE) -1) {
48  this->uid = uid;
49  } else {
50  this->uid = (__UID_T_TYPE) -1;
51  }
52 }
53 
54 void LockFile::setGID(gid_t gid) throw () {
55  if (gid > (__GID_T_TYPE) -1) {
56  this->gid = gid;
57  } else {
58  this->gid = (__GID_T_TYPE) -1;
59  }
60 }
61 
62 void LockFile::setMode(int mode) throw () {
63  if (mode > -1 && mode < 7777) {
64  this->mode = mode;
65  } else {
66  this->mode = 0644;
67  }
68 }
69 
70 bool LockFile::lock(useconds_t interval) throw (FileException) {
71  return tryLockCount(-1, interval);
72 }
73 
74 bool LockFile::tryLock(void) throw (FileException) {
75  std::lock_guard<std::mutex> lg(mutex);
76 
77  return lock_nonblock();
78 }
79 
80 bool LockFile::tryLockUntil(time_t timeout, useconds_t interval) throw (FileException) {
81  time_t now = time(NULL);
82  std::lock_guard<std::mutex> lg(mutex);
83 
84  do {
85  if (lock_nonblock()) {
86  return true;
87  }
88 
89  usleep(interval);
90  } while (now < timeout);
91 
92  return false;
93 }
94 
95 bool LockFile::tryLockFor(time_t duration, useconds_t interval) throw (FileException) {
96  time_t now = time(NULL);
97  time_t timeout = now + duration;
98  std::lock_guard<std::mutex> lg(mutex);
99 
100  do {
101  if (lock_nonblock()) {
102  return true;
103  }
104 
105  usleep(interval);
106  } while (now < timeout);
107 
108  return false;
109 }
110 
111 bool LockFile::tryLockCount(int64_t retries, useconds_t interval) throw (FileException) {
112  int64_t r = 0;
113  std::lock_guard<std::mutex> lg(mutex);
114 
115  do {
116  if (lock_nonblock()) {
117  return true;
118  }
119 
120  usleep(interval);
121  } while (fd < 1 && (retries < 1 || r++ < retries));
122 
123  return false;
124 
125 }
126 
127 bool LockFile::unlock(void) throw (FileException) {
128  std::lock_guard<std::mutex> lg(mutex);
129 
130  if (fd > 0) { // Only unlock if we own the lock
131  if (!unlink()) {
132  close();
133 
134  THROW2(FileException, "Failed to remove lockfile '%s'", fullpath.c_str());
135  }
136 
137  close(); // Releases flock
138 
139  return true;
140  }
141 
142  return false;
143 }
144 
145 bool LockFile::remove(void) throw () {
146  std::lock_guard<std::mutex> lg(mutex);
147 
148  if (fd > 0) {
149  return false;
150  }
151 
152  return unlink();
153 }
154 
155 bool LockFile::ownLock(void) throw () {
156  return (fd > 0);
157 }
158 
159 bool LockFile::isLocked(void) throw () {
160  if (ownLock()) {
161  return true;
162  }
163 
164  return File::exists(fullpath, false) && File::isRegularFile(fullpath, false);
165 }
166 
167 bool LockFile::isStale(void) throw () {
168  if (stale > 0) {
169  try {
170  if (File::exists(fullpath, false) && File::isRegularFile(fullpath, false)) {
171  return (time(NULL) - File::getCTime(fullpath, false) > stale);
172  }
173  } catch (...) { } // Handle missing or unaccessable file.
174  }
175 
176  return false;
177 }
178 
179 bool LockFile::lock_nonblock() throw (FileException) {
180  int pid;
181  struct stat st0, st1;
182  const std::string directory = File::getDirName(fullpath);
183 
184  if (removeStale && isStale()) {
185  unlink();
186  close();
187  }
188 
189  if (isLocked()) { // Already locked
190  return false;
191  }
192 
193  while (1) { // Prevent race condition
194  if (File::exists(fullpath, false) && !File::isRegularFile(fullpath, false)) {
195  THROW(FileException, "Lockfile '%s' must be a regular file or not exist", fullpath.c_str());
196  } else if (!File::exists(directory, false) || !File::isDirectory(directory, false)) {
197  THROW(FileException, "Path to lockfile '%s' must exist and be a directory", fullpath.c_str());
198  }
199 
200  if ((fd = open(fullpath.c_str(), O_WRONLY|O_CREAT|O_DSYNC, mode)) < 0) {
201  THROW2(FileException, "Failed to open or create lockfile '%s'", fullpath.c_str());
202  }
203 
204  if (flock(fd, LOCK_EX|LOCK_NB) != 0) {
205  close();
206 
207  if (errno == EWOULDBLOCK) {
208  return false;
209  }
210 
211  THROW2(FileException, "Failed to lock '%s'", fullpath.c_str());
212  }
213 
214  if (fstat(fd, &st0) < 0 || stat(fullpath.c_str(), &st1) < 0) {
215  close();
216  } else if (st0.st_ino == st1.st_ino) { // Ensure we have the lock
217  break;
218  }
219  }
220 
221  pid = getpid();
222 
223  char buf[16] = { 0 };
224  snprintf(buf, sizeof(buf), "%d\n", pid);
225 
226  if (write(fd, buf, strlen(buf)) < 1) {
227  unlink();
228  close();
229 
230  THROW2(FileException, "Failed to write pid %d to lockfile '%s'", pid, fullpath.c_str());
231  }
232 
233  if (uid != (__UID_T_TYPE) -1 || gid != (__GID_T_TYPE) -1) {
234  if (fchown(fd, uid, gid) != 0) {
235  unlink();
236  close();
237 
238  THROW2(FileException, "Failed to change owner of lockfile '%s' to %d:%d", fullpath.c_str(), uid, gid);
239  }
240  }
241 
242  return true;
243 }
244 
245 bool LockFile::unlink(void) throw () {
246  int save = errno; /* Preserve errno */
247  bool res = false;
248 
249  if (File::exists(fullpath, false) && File::isRegularFile(fullpath, false)) {
250  res = (::unlink(fullpath.c_str()) == 0);
251  }
252 
253  errno = save;
254 
255  return res;
256 }
257 
258 void LockFile::close(void) throw () {
259  int save = errno; /* Preserve errno */
260 
261  if (fd > 0) {
262  ::close(fd);
263  fd = 0;
264  }
265 
266  errno = save;
267 }
268 
269 /*
270 ** vim: noet ts=3 sw=3
271 */