]> git.saurik.com Git - apple/security.git/blob - cdsa/cdsa_utilities/AtomicFile.cpp
Security-179.tar.gz
[apple/security.git] / cdsa / cdsa_utilities / AtomicFile.cpp
1 /*
2 * Copyright (c) 2000-2001, 2003 Apple Computer, Inc. All Rights Reserved.
3 *
4 * The contents of this file constitute Original Code as defined in and are
5 * subject to the Apple Public Source License Version 1.2 (the 'License').
6 * You may not use this file except in compliance with the License. Please obtain
7 * a copy of the License at http://www.apple.com/publicsource and read it before
8 * using this file.
9 *
10 * This Original Code and all software distributed under the License are
11 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS
12 * OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT
13 * LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
14 * PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see the License for the
15 * specific language governing rights and limitations under the License.
16 */
17
18
19 #include <Security/AtomicFile.h>
20
21 #include <Security/devrandom.h>
22
23 #include <errno.h>
24 #include <fcntl.h>
25 #include <limits.h>
26 #include <syslog.h>
27 #include <sys/param.h>
28 #include <sys/types.h>
29 #include <unistd.h>
30
31
32 #define kAtomicFileMaxBlockSize INT_MAX
33
34
35 //
36 // AtomicFile.cpp - Description t.b.d.
37 //
38 AtomicFile::AtomicFile(const std::string &inPath) :
39 mPath(inPath)
40 {
41 pathSplit(inPath, mDir, mFile);
42 }
43
44 AtomicFile::~AtomicFile()
45 {
46 }
47
48 // Aquire the write lock and remove the file.
49 void
50 AtomicFile::performDelete()
51 {
52 AtomicLockedFile lock(*this);
53 if (::unlink(mPath.c_str()) != 0)
54 {
55 int error = errno;
56 secdebug("atomicfile", "unlink %s: %s", mPath.c_str(), strerror(error));
57 if (error == ENOENT)
58 CssmError::throwMe(CSSMERR_DL_DATASTORE_DOESNOT_EXIST);
59 else
60 UnixError::throwMe(error);
61 }
62 }
63
64 // Aquire the write lock and rename the file (and bump the version and stuff).
65 void
66 AtomicFile::rename(const std::string &inNewPath)
67 {
68 const char *path = mPath.c_str();
69 const char *newPath = inNewPath.c_str();
70
71 // @@@ lock the destination file too.
72 AtomicLockedFile lock(*this);
73 if (::rename(path, newPath) != 0)
74 {
75 int error = errno;
76 secdebug("atomicfile", "rename(%s, %s): %s", path, newPath, strerror(error));
77 UnixError::throwMe(error);
78 }
79 }
80
81 // Lock the file for writing and return a newly created AtomicTempFile.
82 RefPointer<AtomicTempFile>
83 AtomicFile::create(mode_t mode)
84 {
85 const char *path = mPath.c_str();
86
87 // First make sure the directory to this file exists and is writable
88 mkpath(mDir);
89
90 RefPointer<AtomicLockedFile> lock(new AtomicLockedFile(*this));
91 int fileRef = ropen(path, O_WRONLY|O_CREAT|O_EXCL, mode);
92 if (fileRef == -1)
93 {
94 int error = errno;
95 secdebug("atomicfile", "open %s: %s", path, strerror(error));
96
97 // Do the obvious error code translations here.
98 // @@@ Consider moving these up a level.
99 if (error == EACCES)
100 CssmError::throwMe(CSSM_ERRCODE_OS_ACCESS_DENIED);
101 else if (error == EEXIST)
102 CssmError::throwMe(CSSMERR_DL_DATASTORE_ALREADY_EXISTS);
103 else
104 UnixError::throwMe(error);
105 }
106 rclose(fileRef);
107
108 try
109 {
110 // Now that we have created the lock and the new db file create a tempfile
111 // object.
112 RefPointer<AtomicTempFile> temp(new AtomicTempFile(*this, lock, mode));
113 secdebug("atomicfile", "%p created %s", this, path);
114 return temp;
115 }
116 catch (...)
117 {
118 // Creating the temp file failed so remove the db file we just created too.
119 if (::unlink(path) == -1)
120 {
121 secdebug("atomicfile", "unlink %s: %s", path, strerror(errno));
122 }
123 throw;
124 }
125 }
126
127 // Lock the database file for writing and return a newly created AtomicTempFile.
128 RefPointer<AtomicTempFile>
129 AtomicFile::write()
130 {
131 RefPointer<AtomicLockedFile> lock(new AtomicLockedFile(*this));
132 return new AtomicTempFile(*this, lock);
133 }
134
135 // Return a bufferedFile containing current version of the file for reading.
136 RefPointer<AtomicBufferedFile>
137 AtomicFile::read()
138 {
139 return new AtomicBufferedFile(mPath);
140 }
141
142 mode_t
143 AtomicFile::mode() const
144 {
145 const char *path = mPath.c_str();
146 struct stat st;
147 if (::stat(path, &st) == -1)
148 {
149 int error = errno;
150 secdebug("atomicfile", "stat %s: %s", path, strerror(error));
151 UnixError::throwMe(error);
152 }
153 return st.st_mode;
154 }
155
156 // Split full into a dir and file component.
157 void
158 AtomicFile::pathSplit(const std::string &inFull, std::string &outDir, std::string &outFile)
159 {
160 std::string::size_type slash, len = inFull.size();
161 slash = inFull.rfind('/');
162 if (slash == std::string::npos)
163 {
164 outDir = "";
165 outFile = inFull;
166 }
167 else if (slash + 1 == len)
168 {
169 outDir = inFull;
170 outFile = "";
171 }
172 else
173 {
174 outDir = inFull.substr(0, slash + 1);
175 outFile = inFull.substr(slash + 1, len);
176 }
177 }
178
179 //
180 // Make sure the directory up to inDir exists inDir *must* end in a slash.
181 //
182 void
183 AtomicFile::mkpath(const std::string &inDir, mode_t mode)
184 {
185 for (std::string::size_type pos = 0; (pos = inDir.find('/', pos + 1)) != std::string::npos;)
186 {
187 std::string path = inDir.substr(0, pos);
188 const char *cpath = path.c_str();
189 struct stat sb;
190 if (::stat(cpath, &sb))
191 {
192 if (errno != ENOENT || ::mkdir(cpath, mode))
193 UnixError::throwMe(errno);
194 }
195 else if (!S_ISDIR(sb.st_mode))
196 CssmError::throwMe(CSSM_ERRCODE_OS_ACCESS_DENIED); // @@@ Should be is a directory
197 }
198 }
199
200 int
201 AtomicFile::ropen(const char *const name, int flags, mode_t mode)
202 {
203 int fd, tries_left = 4 /* kNoResRetry */;
204 do
205 {
206 fd = ::open(name, flags, mode);
207 } while (fd < 0 && (errno == EINTR || errno == ENFILE && --tries_left >= 0));
208
209 return fd;
210 }
211
212 int
213 AtomicFile::rclose(int fd)
214 {
215 int result;
216 do
217 {
218 result = ::close(fd);
219 } while(result && errno == EINTR);
220
221 return result;
222 }
223
224 //
225 // AtomicBufferedFile - This represents an instance of a file opened for reading.
226 // The file is read into memory and closed after this is done.
227 // The memory is released when this object is destroyed.
228 //
229 AtomicBufferedFile::AtomicBufferedFile(const std::string &inPath) :
230 mPath(inPath),
231 mFileRef(-1),
232 mBuffer(NULL),
233 mLength(0)
234 {
235 }
236
237 AtomicBufferedFile::~AtomicBufferedFile()
238 {
239 if (mFileRef >= 0)
240 {
241 AtomicFile::rclose(mFileRef);
242 secdebug("atomicfile", "%p closed %s", this, mPath.c_str());
243 }
244
245 if (mBuffer)
246 {
247 secdebug("atomicfile", "%p free %s buffer %p", this, mPath.c_str(), mBuffer);
248 free(mBuffer);
249 }
250 }
251
252 //
253 // Open the file and return the length in bytes.
254 //
255 off_t
256 AtomicBufferedFile::open()
257 {
258 const char *path = mPath.c_str();
259 if (mFileRef >= 0)
260 {
261 secdebug("atomicfile", "open %s: already open, closing and reopening", path);
262 close();
263 }
264
265 mFileRef = AtomicFile::ropen(path, O_RDONLY, 0);
266 if (mFileRef == -1)
267 {
268 int error = errno;
269 secdebug("atomicfile", "open %s: %s", path, strerror(error));
270
271 // Do the obvious error code translations here.
272 // @@@ Consider moving these up a level.
273 if (error == ENOENT)
274 CssmError::throwMe(CSSMERR_DL_DATASTORE_DOESNOT_EXIST);
275 else if (error == EACCES)
276 CssmError::throwMe(CSSM_ERRCODE_OS_ACCESS_DENIED);
277 else
278 UnixError::throwMe(error);
279 }
280
281 mLength = ::lseek(mFileRef, 0, SEEK_END);
282 if (mLength == -1)
283 {
284 int error = errno;
285 secdebug("atomicfile", "lseek(%s, END): %s", path, strerror(error));
286 AtomicFile::rclose(mFileRef);
287 UnixError::throwMe(error);
288 }
289
290 secdebug("atomicfile", "%p opened %s: %qd bytes", this, path, mLength);
291
292 return mLength;
293 }
294
295 //
296 // Read the file starting at inOffset for inLength bytes into the buffer and return
297 // a pointer to it. On return outLength contain the actual number of bytes read, it
298 // will only ever be less than inLength if EOF was reached, and it will never be more
299 // than inLength.
300 //
301 const uint8 *
302 AtomicBufferedFile::read(off_t inOffset, off_t inLength, off_t &outLength)
303 {
304 if (mFileRef < 0)
305 {
306 secdebug("atomicfile", "read %s: file yet not opened, opening", mPath.c_str());
307 open();
308 }
309
310 off_t bytesLeft = inLength;
311 uint8 *ptr;
312 if (mBuffer)
313 {
314 secdebug("atomicfile", "%p free %s buffer %p", this, mPath.c_str(), mBuffer);
315 free(mBuffer);
316 }
317
318 mBuffer = ptr = reinterpret_cast<uint8 *>(malloc(bytesLeft));
319 secdebug("atomicfile", "%p allocated %s buffer %p size %qd", this, mPath.c_str(), mBuffer, bytesLeft);
320 off_t pos = inOffset;
321 while (bytesLeft)
322 {
323 size_t toRead = bytesLeft > kAtomicFileMaxBlockSize ? kAtomicFileMaxBlockSize : size_t(bytesLeft);
324 ssize_t bytesRead = ::pread(mFileRef, ptr, toRead, pos);
325 if (bytesRead == -1)
326 {
327 int error = errno;
328 if (error == EINTR)
329 {
330 // We got interrupted by a signal, so try again.
331 secdebug("atomicfile", "pread %s: interrupted, retrying", mPath.c_str());
332 continue;
333 }
334
335 secdebug("atomicfile", "pread %s: %s", mPath.c_str(), strerror(error));
336 free(mBuffer);
337 mBuffer = NULL;
338 UnixError::throwMe(error);
339 }
340
341 // Read returning 0 means EOF was reached so we're done.
342 if (bytesRead == 0)
343 break;
344
345 secdebug("atomicfile", "%p read %s: %d bytes to %p", this, mPath.c_str(), bytesRead, ptr);
346
347 bytesLeft -= bytesRead;
348 ptr += bytesRead;
349 pos += bytesRead;
350 }
351
352 // Compute length
353 outLength = ptr - mBuffer;
354
355 return mBuffer;
356 }
357
358 void
359 AtomicBufferedFile::close()
360 {
361 if (mFileRef < 0)
362 {
363 secdebug("atomicfile", "close %s: already closed", mPath.c_str());
364 }
365 else
366 {
367 int result = AtomicFile::rclose(mFileRef);
368 mFileRef = -1;
369 if (result == -1)
370 {
371 int error = errno;
372 secdebug("atomicfile", "close %s: %s", mPath.c_str(), strerror(errno));
373 UnixError::throwMe(error);
374 }
375
376 secdebug("atomicfile", "%p closed %s", this, mPath.c_str());
377 }
378 }
379
380
381 //
382 // AtomicTempFile - A temporary file to write changes to.
383 //
384 AtomicTempFile::AtomicTempFile(AtomicFile &inFile, const RefPointer<AtomicLockedFile> &inLockedFile, mode_t mode) :
385 mFile(inFile),
386 mLockedFile(inLockedFile),
387 mCreating(true)
388 {
389 create(mode);
390 }
391
392 AtomicTempFile::AtomicTempFile(AtomicFile &inFile, const RefPointer<AtomicLockedFile> &inLockedFile) :
393 mFile(inFile),
394 mLockedFile(inLockedFile),
395 mCreating(false)
396 {
397 create(mFile.mode());
398 }
399
400 AtomicTempFile::~AtomicTempFile()
401 {
402 // rollback if we didn't commit yet.
403 if (mFileRef >= 0)
404 rollback();
405 }
406
407 //
408 // Open the file and return the length in bytes.
409 //
410 void
411 AtomicTempFile::create(mode_t mode)
412 {
413 mPath = mFile.dir() + "," + mFile.file();
414 const char *path = mPath.c_str();
415
416 mFileRef = AtomicFile::ropen(path, O_WRONLY|O_CREAT|O_TRUNC, mode);
417 if (mFileRef == -1)
418 {
419 int error = errno;
420 secdebug("atomicfile", "open %s: %s", path, strerror(error));
421
422 // Do the obvious error code translations here.
423 // @@@ Consider moving these up a level.
424 if (error == EACCES)
425 CssmError::throwMe(CSSM_ERRCODE_OS_ACCESS_DENIED);
426 else
427 UnixError::throwMe(error);
428 }
429
430 secdebug("atomicfile", "%p created %s", this, path);
431 }
432
433 void
434 AtomicTempFile::write(AtomicFile::OffsetType inOffsetType, off_t inOffset, const uint32 inData)
435 {
436 uint32 aData = htonl(inData);
437 write(inOffsetType, inOffset, reinterpret_cast<uint8 *>(&aData), sizeof(aData));
438 }
439
440 void
441 AtomicTempFile::write(AtomicFile::OffsetType inOffsetType, off_t inOffset,
442 const uint32 *inData, uint32 inCount)
443 {
444 #ifdef HOST_LONG_IS_NETWORK_LONG
445 // Optimize this for the case where hl == nl
446 const uint32 *aBuffer = inData;
447 #else
448 auto_array<uint32> aBuffer(inCount);
449 for (uint32 i = 0; i < inCount; i++)
450 aBuffer.get()[i] = htonl(inData[i]);
451 #endif
452
453 write(inOffsetType, inOffset, reinterpret_cast<const uint8 *>(aBuffer.get()),
454 inCount * sizeof(*inData));
455 }
456
457 void
458 AtomicTempFile::write(AtomicFile::OffsetType inOffsetType, off_t inOffset, const uint8 *inData, size_t inLength)
459 {
460 off_t pos;
461 if (inOffsetType == AtomicFile::FromEnd)
462 {
463 pos = ::lseek(mFileRef, 0, SEEK_END);
464 if (pos == -1)
465 {
466 int error = errno;
467 secdebug("atomicfile", "lseek(%s, %qd): %s", mPath.c_str(), inOffset, strerror(error));
468 UnixError::throwMe(error);
469 }
470 }
471 else if (inOffsetType == AtomicFile::FromStart)
472 pos = inOffset;
473 else
474 CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR);
475
476 off_t bytesLeft = inLength;
477 const uint8 *ptr = inData;
478 while (bytesLeft)
479 {
480 size_t toWrite = bytesLeft > kAtomicFileMaxBlockSize ? kAtomicFileMaxBlockSize : size_t(bytesLeft);
481 ssize_t bytesWritten = ::pwrite(mFileRef, ptr, toWrite, pos);
482 if (bytesWritten == -1)
483 {
484 int error = errno;
485 if (error == EINTR)
486 {
487 // We got interrupted by a signal, so try again.
488 secdebug("atomicfile", "write %s: interrupted, retrying", mPath.c_str());
489 continue;
490 }
491
492 secdebug("atomicfile", "write %s: %s", mPath.c_str(), strerror(error));
493 UnixError::throwMe(error);
494 }
495
496 // Write returning 0 is bad mmkay.
497 if (bytesWritten == 0)
498 {
499 secdebug("atomicfile", "write %s: 0 bytes written", mPath.c_str());
500 CssmError::throwMe(CSSMERR_DL_INTERNAL_ERROR);
501 }
502
503 secdebug("atomicfile", "%p wrote %s %d bytes from %p", this, mPath.c_str(), bytesWritten, ptr);
504
505 bytesLeft -= bytesWritten;
506 ptr += bytesWritten;
507 pos += bytesWritten;
508 }
509 }
510
511 void
512 AtomicTempFile::fsync()
513 {
514 if (mFileRef < 0)
515 {
516 secdebug("atomicfile", "fsync %s: already closed", mPath.c_str());
517 }
518 else
519 {
520 int result;
521 do
522 {
523 result = ::fsync(mFileRef);
524 } while (result && errno == EINTR);
525
526 if (result == -1)
527 {
528 int error = errno;
529 secdebug("atomicfile", "fsync %s: %s", mPath.c_str(), strerror(errno));
530 UnixError::throwMe(error);
531 }
532
533 secdebug("atomicfile", "%p fsynced %s", this, mPath.c_str());
534 }
535 }
536
537 void
538 AtomicTempFile::close()
539 {
540 if (mFileRef < 0)
541 {
542 secdebug("atomicfile", "close %s: already closed", mPath.c_str());
543 }
544 else
545 {
546 int result = AtomicFile::rclose(mFileRef);
547 mFileRef = -1;
548 if (result == -1)
549 {
550 int error = errno;
551 secdebug("atomicfile", "close %s: %s", mPath.c_str(), strerror(errno));
552 UnixError::throwMe(error);
553 }
554
555 secdebug("atomicfile", "%p closed %s", this, mPath.c_str());
556 }
557 }
558
559 // Commit the current create or write and close the write file. Note that a throw during the commit does an automatic rollback.
560 void
561 AtomicTempFile::commit()
562 {
563 try
564 {
565 fsync();
566 close();
567 const char *oldPath = mPath.c_str();
568 const char *newPath = mFile.path().c_str();
569 if (::rename(oldPath, newPath) == -1)
570 {
571 int error = errno;
572 secdebug("atomicfile", "rename (%s, %s): %s", oldPath, newPath, strerror(errno));
573 UnixError::throwMe(error);
574 }
575
576 // Unlock the lockfile
577 mLockedFile = NULL;
578
579 secdebug("atomicfile", "%p commited %s", this, oldPath);
580 }
581 catch (...)
582 {
583 rollback();
584 throw;
585 }
586 }
587
588 // Rollback the current create or write (happens automatically if commit() isn't called before the destructor is.
589 void
590 AtomicTempFile::rollback() throw()
591 {
592 if (mFileRef >= 0)
593 {
594 AtomicFile::rclose(mFileRef);
595 mFileRef = -1;
596 }
597
598 // @@@ Log errors if this fails.
599 const char *path = mPath.c_str();
600 if (::unlink(path) == -1)
601 {
602 secdebug("atomicfile", "unlink %s: %s", path, strerror(errno));
603 // rollback can't throw
604 }
605
606 // @@@ Think about this. Depending on how we do locking we might not need this.
607 if (mCreating)
608 {
609 const char *path = mFile.path().c_str();
610 if (::unlink(path) == -1)
611 {
612 secdebug("atomicfile", "unlink %s: %s", path, strerror(errno));
613 // rollback can't throw
614 }
615 }
616 }
617
618
619 //
620 // An advisory write lock for inFile.
621 //
622 AtomicLockedFile::AtomicLockedFile(AtomicFile &inFile) :
623 mDir(inFile.dir()),
624 mPath(inFile.dir() + "lck~" + inFile.file())
625 {
626 lock();
627 }
628
629 AtomicLockedFile::~AtomicLockedFile()
630 {
631 unlock();
632 }
633
634 std::string
635 AtomicLockedFile::unique(mode_t mode)
636 {
637 static const int randomPart = 16;
638 DevRandomGenerator randomGen;
639 std::string::size_type dirSize = mDir.size();
640 std::string fullname(dirSize + randomPart + 2, '\0');
641 fullname.replace(0, dirSize, mDir);
642 fullname[dirSize] = '~'; /* UNIQ_PREFIX */
643 char buf[randomPart];
644 struct stat filebuf;
645 int result, fd = -1;
646
647 for (int retries = 0; retries < 10; ++retries)
648 {
649 /* Make a random filename. */
650 randomGen.random(buf, randomPart);
651 for (int ix = 0; ix < randomPart; ++ix)
652 {
653 char ch = buf[ix] & 0x3f;
654 fullname[ix + dirSize + 1] = ch +
655 ( ch < 26 ? 'A'
656 : ch < 26 + 26 ? 'a' - 26
657 : ch < 26 + 26 + 10 ? '0' - 26 - 26
658 : ch == 26 + 26 + 10 ? '-' - 26 - 26 - 10
659 : '_' - 26 - 26 - 11);
660 }
661
662 result = lstat(fullname.c_str(), &filebuf);
663 if (result && errno == ENAMETOOLONG)
664 {
665 do
666 fullname.erase(fullname.end() - 1);
667 while((result = lstat(fullname.c_str(), &filebuf)) && errno == ENAMETOOLONG && fullname.size() > dirSize + 8);
668 } /* either it stopped being a problem or we ran out of filename */
669
670 if (result && errno == ENOENT)
671 {
672 fd = AtomicFile::ropen(fullname.c_str(), O_WRONLY|O_CREAT|O_EXCL, mode);
673 if (fd >= 0 || errno != EEXIST)
674 break;
675 }
676 }
677
678 if (fd < 0)
679 {
680 int error = errno;
681 ::syslog(LOG_ERR, "Couldn't create temp file %s: %s", fullname.c_str(), strerror(error));
682 secdebug("atomicfile", "Couldn't create temp file %s: %s", fullname.c_str(), strerror(error));
683 UnixError::throwMe(error);
684 }
685
686 /* @@@ Check for EINTR. */
687 write(fd, "0", 1); /* pid 0, `works' across networks */
688
689 AtomicFile::rclose(fd);
690
691 return fullname;
692 }
693
694 /* Return 0 on success and 1 on failure if st is set to the result of stat(old) and -1 on failure if the stat(old) failed. */
695 int
696 AtomicLockedFile::rlink(const char *const old, const char *const newn, struct stat &sto)
697 {
698 int result = ::link(old,newn);
699 if (result)
700 {
701 int serrno = errno;
702 if (::lstat(old, &sto) == 0)
703 {
704 struct stat stn;
705 if (::lstat(newn, &stn) == 0
706 && sto.st_dev == stn.st_dev
707 && sto.st_ino == stn.st_ino
708 && sto.st_uid == stn.st_uid
709 && sto.st_gid == stn.st_gid
710 && !S_ISLNK(sto.st_mode))
711 {
712 /* Link failed but files are the same so the link really went ok. */
713 return 0;
714 }
715 else
716 result = 1;
717 }
718 errno = serrno; /* Restore errno from link() */
719 }
720
721 return result;
722 }
723
724 /* NFS-resistant rename()
725 * rename with fallback for systems that don't support it
726 * Note that this does not preserve the contents of the file. */
727 int
728 AtomicLockedFile::myrename(const char *const old, const char *const newn)
729 {
730 struct stat stbuf;
731 int fd = -1;
732 int ret;
733
734 /* Try a real hardlink */
735 ret = rlink(old, newn, stbuf);
736 if (ret > 0)
737 {
738 if (stbuf.st_nlink < 2 && (errno == EXDEV || errno == ENOTSUP))
739 {
740 /* Hard link failed so just create a new file with O_EXCL instead. */
741 fd = AtomicFile::ropen(newn, O_WRONLY|O_CREAT|O_EXCL, stbuf.st_mode);
742 if (fd >= 0)
743 ret = 0;
744 }
745 }
746
747 /* We want the errno from the link or the ropen, not that of the unlink. */
748 int serrno = errno;
749
750 /* Unlink the temp file. */
751 ::unlink(old);
752 if (fd > 0)
753 AtomicFile::rclose(fd);
754
755 errno = serrno;
756 return ret;
757 }
758
759 int
760 AtomicLockedFile::xcreat(const char *const name, mode_t mode, time_t &tim)
761 {
762 std::string uniqueName = unique(mode);
763 const char *uniquePath = uniqueName.c_str();
764 struct stat stbuf; /* return the filesystem time to the caller */
765 stat(uniquePath, &stbuf);
766 tim = stbuf.st_mtime;
767 return myrename(uniquePath, name);
768 }
769
770 void
771 AtomicLockedFile::lock(mode_t mode)
772 {
773 const char *path = mPath.c_str();
774 bool triedforce = false;
775 struct stat stbuf;
776 time_t t, locktimeout = 1024; /* DEFlocktimeout, 17 minutes. */
777 bool doSyslog = false;
778 bool failed = false;
779 int retries = 0;
780
781 while (!failed)
782 {
783 /* Don't syslog first time through. */
784 if (doSyslog)
785 ::syslog(LOG_NOTICE, "Locking %s", path);
786 else
787 doSyslog = true;
788
789 secdebug("atomicfile", "Locking %s", path); /* in order to cater for clock skew: get */
790 if (!xcreat(path, mode, t)) /* time t from the filesystem */
791 {
792 /* lock acquired, hurray! */
793 break;
794 }
795 switch(errno)
796 {
797 case EEXIST: /* check if it's time for a lock override */
798 if (!lstat(path, &stbuf) && stbuf.st_size <= 16 /* MAX_locksize */ && locktimeout
799 && !lstat(path, &stbuf) && locktimeout < t - stbuf.st_mtime)
800 /* stat() till unlink() should be atomic, but can't guarantee that. */
801 {
802 if (triedforce)
803 {
804 /* Already tried, force lock override, not trying again */
805 failed = true;
806 break;
807 }
808 else if (S_ISDIR(stbuf.st_mode) || ::unlink(path))
809 {
810 triedforce=true;
811 ::syslog(LOG_ERR, "Forced unlock denied on %s", path);
812 secdebug("atomicfile", "Forced unlock denied on %s", path);
813 }
814 else
815 {
816 ::syslog(LOG_ERR, "Forcing lock on %s", path);
817 secdebug("atomicfile", "Forcing lock on %s", path);
818 sleep(16 /* DEFsuspend */);
819 break;
820 }
821 }
822 else
823 triedforce = false; /* legitimate iteration, clear flag */
824
825 /* Reset retry counter. */
826 retries = 0;
827 sleep(8 /* DEFlocksleep */);
828 break;
829
830 case ENOSPC: /* no space left, treat it as a transient */
831 #ifdef EDQUOT /* NFS failure */
832 case EDQUOT: /* maybe it was a short term shortage? */
833 #endif
834 case ENOENT:
835 case ENOTDIR:
836 case EIO:
837 /*case EACCES:*/
838 if(++retries < (7 + 1)) /* nfsTRY number of times+1 to ignore spurious NFS errors */
839 sleep(8 /* DEFlocksleep */);
840 else
841 failed = true;
842 break;
843
844 #ifdef ENAMETOOLONG
845 case ENAMETOOLONG: /* Filename is too long, shorten and retry */
846 if (mPath.size() > mDir.size() + 8)
847 {
848 secdebug("atomicfile", "Truncating %s and retrying lock", path);
849 mPath.erase(mPath.end() - 1);
850 path = mPath.c_str();
851 /* Reset retry counter. */
852 retries = 0;
853 break;
854 }
855 /* DROPTHROUGH */
856 #endif
857 default:
858 failed = true;
859 break;
860 }
861 }
862
863 if (failed)
864 {
865 int error = errno;
866 ::syslog(LOG_ERR, "Lock failure on %s: %s", path, strerror(error));
867 secdebug("atomicfile", "Lock failure on %s: %s", path, strerror(error));
868 UnixError::throwMe(error);
869 }
870 }
871
872 void
873 AtomicLockedFile::unlock() throw()
874 {
875 const char *path = mPath.c_str();
876 if (::unlink(path) == -1)
877 {
878 secdebug("atomicfile", "unlink %s: %s", path, strerror(errno));
879 // unlock can't throw
880 }
881 }
882
883
884 #undef kAtomicFileMaxBlockSize