]> git.saurik.com Git - apple/security.git/blob - libsecurity_filedb/lib/AtomicFile.cpp
Security-55163.44.tar.gz
[apple/security.git] / libsecurity_filedb / lib / AtomicFile.cpp
1 /*
2 * Copyright (c) 2000-2012 Apple 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_filedb/AtomicFile.h>
20
21 #include <security_utilities/devrandom.h>
22 #include <CommonCrypto/CommonDigest.h>
23 #include <security_cdsa_utilities/cssmerrors.h>
24 #include <Security/cssm.h>
25 #include <errno.h>
26 #include <fcntl.h>
27 #include <limits.h>
28 #include <syslog.h>
29 #include <sys/param.h>
30 #include <sys/types.h>
31 #include <sys/mount.h>
32 #include <sys/file.h>
33 #include <sys/stat.h>
34 #include <sys/time.h>
35 #include <sys/mman.h>
36 #include <copyfile.h>
37 #include <sandbox.h>
38 #include <set>
39
40 #define kAtomicFileMaxBlockSize INT_MAX
41
42
43 //
44 // AtomicFile.cpp
45 //
46 AtomicFile::AtomicFile(const std::string &inPath) :
47 mPath(inPath)
48 {
49 pathSplit(inPath, mDir, mFile);
50
51 if (mDir.length() == 0)
52 {
53 const char* buffer = getwd(NULL);
54 mDir = buffer;
55 free((void*) buffer);
56 }
57
58 mDir += '/';
59
60 // determine if the path is on a local or a networked volume
61 struct statfs info;
62 int result = statfs(mDir.c_str(), &info);
63 if (result == -1) // error on opening?
64 {
65 mIsLocalFileSystem = false; // revert to the old ways if we can't tell what kind of system we have
66 }
67 else
68 {
69 mIsLocalFileSystem = (info.f_flags & MNT_LOCAL) != 0;
70 if (mIsLocalFileSystem)
71 {
72 // compute the name of the lock file for this file
73 CC_SHA1_CTX ctx;
74 CC_SHA1_Init(&ctx);
75 CC_SHA1_Update(&ctx, (const void*) mFile.c_str(), mFile.length());
76 u_int8_t digest[CC_SHA1_DIGEST_LENGTH];
77 CC_SHA1_Final(digest, &ctx);
78
79 u_int32_t hash = (digest[0] << 24) | (digest[1] << 16) | (digest[2] << 8) | digest[3];
80
81 char buffer[256];
82 sprintf(buffer, "%08X", hash);
83 mLockFilePath = mDir + ".fl" + buffer;
84 }
85 }
86 }
87
88 AtomicFile::~AtomicFile()
89 {
90 }
91
92 // Aquire the write lock and remove the file.
93 void
94 AtomicFile::performDelete()
95 {
96 AtomicLockedFile lock(*this);
97 if (::unlink(mPath.c_str()) != 0)
98 {
99 int error = errno;
100 secdebug("atomicfile", "unlink %s: %s", mPath.c_str(), strerror(error));
101 if (error == ENOENT)
102 CssmError::throwMe(CSSMERR_DL_DATASTORE_DOESNOT_EXIST);
103 else
104 UnixError::throwMe(error);
105 }
106
107 // unlink our lock file
108 ::unlink(mLockFilePath.c_str());
109 }
110
111 // Aquire the write lock and rename the file (and bump the version and stuff).
112 void
113 AtomicFile::rename(const std::string &inNewPath)
114 {
115 const char *path = mPath.c_str();
116 const char *newPath = inNewPath.c_str();
117
118 // @@@ lock the destination file too.
119 AtomicLockedFile lock(*this);
120 if (::rename(path, newPath) != 0)
121 {
122 int error = errno;
123 secdebug("atomicfile", "rename(%s, %s): %s", path, newPath, strerror(error));
124 UnixError::throwMe(error);
125 }
126 }
127
128 // Lock the file for writing and return a newly created AtomicTempFile.
129 RefPointer<AtomicTempFile>
130 AtomicFile::create(mode_t mode)
131 {
132 const char *path = mPath.c_str();
133
134 // First make sure the directory to this file exists and is writable
135 mkpath(mDir);
136
137 RefPointer<AtomicLockedFile> lock(new AtomicLockedFile(*this));
138 int fileRef = ropen(path, O_WRONLY|O_CREAT|O_EXCL, mode);
139 if (fileRef == -1)
140 {
141 int error = errno;
142 secdebug("atomicfile", "open %s: %s", path, strerror(error));
143
144 // Do the obvious error code translations here.
145 // @@@ Consider moving these up a level.
146 if (error == EACCES)
147 CssmError::throwMe(CSSM_ERRCODE_OS_ACCESS_DENIED);
148 else if (error == EEXIST)
149 CssmError::throwMe(CSSMERR_DL_DATASTORE_ALREADY_EXISTS);
150 else
151 UnixError::throwMe(error);
152 }
153 rclose(fileRef);
154
155 try
156 {
157 // Now that we have created the lock and the new db file create a tempfile
158 // object.
159 RefPointer<AtomicTempFile> temp(new AtomicTempFile(*this, lock, mode));
160 secdebug("atomicfile", "%p created %s", this, path);
161 return temp;
162 }
163 catch (...)
164 {
165 // Creating the temp file failed so remove the db file we just created too.
166 if (::unlink(path) == -1)
167 {
168 secdebug("atomicfile", "unlink %s: %s", path, strerror(errno));
169 }
170 throw;
171 }
172 }
173
174 // Lock the database file for writing and return a newly created AtomicTempFile.
175 // If the parent directory allows the write we're going to allow this. Previous
176 // versions checked for writability of the db file and that caused problems when
177 // setuid programs had made entries. As long as the db (keychain) file is readable
178 // this function can make the newer keychain file with the correct owner just by virtue
179 // of the copy that takes place.
180
181 RefPointer<AtomicTempFile>
182 AtomicFile::write()
183 {
184
185 RefPointer<AtomicLockedFile> lock(new AtomicLockedFile(*this));
186 return new AtomicTempFile(*this, lock);
187 }
188
189 // Return a bufferedFile containing current version of the file for reading.
190 RefPointer<AtomicBufferedFile>
191 AtomicFile::read()
192 {
193 return new AtomicBufferedFile(mPath, mIsLocalFileSystem);
194 }
195
196 mode_t
197 AtomicFile::mode() const
198 {
199 const char *path = mPath.c_str();
200 struct stat st;
201 if (::stat(path, &st) == -1)
202 {
203 int error = errno;
204 secdebug("atomicfile", "stat %s: %s", path, strerror(error));
205 UnixError::throwMe(error);
206 }
207 return st.st_mode;
208 }
209
210 // Split full into a dir and file component.
211 void
212 AtomicFile::pathSplit(const std::string &inFull, std::string &outDir, std::string &outFile)
213 {
214 std::string::size_type slash, len = inFull.size();
215 slash = inFull.rfind('/');
216 if (slash == std::string::npos)
217 {
218 outDir = "";
219 outFile = inFull;
220 }
221 else if (slash + 1 == len)
222 {
223 outDir = inFull;
224 outFile = "";
225 }
226 else
227 {
228 outDir = inFull.substr(0, slash + 1);
229 outFile = inFull.substr(slash + 1, len);
230 }
231 }
232
233 static std::string RemoveDoubleSlashes(const std::string &path)
234 {
235 std::string result;
236 unsigned i;
237 for (i = 0; i < path.length(); ++i)
238 {
239 result += path[i];
240 if ((i < path.length() - 2) && path[i] == '/' && path[i + 1] == '/')
241 {
242 i += 1; // skip a second '/'
243 }
244 }
245
246 return result;
247 }
248
249
250
251 //
252 // Make sure the directory up to inDir exists inDir *must* end in a slash.
253 //
254 void
255 AtomicFile::mkpath(const std::string &inDir, mode_t mode)
256 {
257 for (std::string::size_type pos = 0; (pos = inDir.find('/', pos + 1)) != std::string::npos;)
258 {
259 std::string path = inDir.substr(0, pos);
260 const char *cpath = path.c_str();
261 struct stat sb;
262 if (::stat(cpath, &sb))
263 {
264 // if we are creating a path in the user's home directory, override the user's mode
265 std::string homedir = getenv("HOME");
266
267 // canonicalize the path (remove double slashes)
268 string canonPath = RemoveDoubleSlashes(cpath);
269
270 if (canonPath.find(homedir, 0) == 0)
271 {
272 mode = 0700;
273 }
274
275 if (errno != ENOENT || ::mkdir(cpath, mode))
276 UnixError::throwMe(errno);
277 }
278 else if (!S_ISDIR(sb.st_mode))
279 CssmError::throwMe(CSSM_ERRCODE_OS_ACCESS_DENIED); // @@@ Should be is a directory
280 }
281 }
282
283 int
284 AtomicFile::ropen(const char *const name, int flags, mode_t mode)
285 {
286 bool isCreate = (flags & O_CREAT) != 0;
287
288 /*
289 The purpose of checkForRead and checkForWrite is to mitigate
290 spamming of the log when a user has installed certain third
291 party software packages which create additional keychains.
292 Certain applications use a custom sandbox profile which do not
293 permit this and so the user gets a ton of spam in the log.
294 This turns into a serious performance problem.
295
296 We handle this situation by checking two factors:
297
298 1: If the user is trying to create a file, we send the
299 request directly to open. This is the right thing
300 to do, as we don't want most applications creating
301 keychains unless they have been expressly authorized
302 to do so.
303
304 The layers above this one only set O_CREAT when a file
305 doesn't exist, so the case where O_CREAT can be called
306 on an existing file is irrelevant.
307
308 2: If the user is trying to open the file for reading or
309 writing, we check with the sandbox mechanism to see if
310 the operation will be permitted (and tell it not to
311 log if it the operation will fail).
312
313 If the operation is not permitted, we return -1 which
314 emulates the behavior of open. sandbox_check sets
315 errno properly, so the layers which call this function
316 will be able to act as though open had been called.
317 */
318
319 bool checkForRead = false;
320 bool checkForWrite = false;
321
322 int fd, tries_left = 4 /* kNoResRetry */;
323
324 if (!isCreate)
325 {
326 switch (flags & O_ACCMODE)
327 {
328 case O_RDONLY:
329 checkForRead = true;
330 break;
331 case O_WRONLY:
332 checkForWrite = true;
333 break;
334 case O_RDWR:
335 checkForRead = true;
336 checkForWrite = true;
337 break;
338 }
339
340 if (checkForRead)
341 {
342 int result = sandbox_check(getpid(), "file-read-data", (sandbox_filter_type) (SANDBOX_FILTER_PATH | SANDBOX_CHECK_NO_REPORT), name);
343 if (result != 0)
344 {
345 return -1;
346 }
347 }
348
349 if (checkForWrite)
350 {
351 int result = sandbox_check(getpid(), "file-write-data", (sandbox_filter_type) (SANDBOX_FILTER_PATH | SANDBOX_CHECK_NO_REPORT), name);
352 if (result != 0)
353 {
354 return -1;
355 }
356 }
357 }
358
359 do
360 {
361 fd = ::open(name, flags, mode);
362 } while (fd < 0 && (errno == EINTR || (errno == ENFILE && --tries_left >= 0)));
363
364 return fd;
365 }
366
367 int
368 AtomicFile::rclose(int fd)
369 {
370 int result;
371 do
372 {
373 result = ::close(fd);
374 } while(result && errno == EINTR);
375
376 return result;
377 }
378
379 //
380 // AtomicBufferedFile - This represents an instance of a file opened for reading.
381 // The file is read into memory and closed after this is done.
382 // The memory is released when this object is destroyed.
383 //
384 AtomicBufferedFile::AtomicBufferedFile(const std::string &inPath, bool isLocal) :
385 mPath(inPath),
386 mFileRef(-1),
387 mBuffer(NULL),
388 mLength(0),
389 mIsMapped(isLocal)
390 {
391 }
392
393 AtomicBufferedFile::~AtomicBufferedFile()
394 {
395 if (mFileRef >= 0)
396 {
397 AtomicFile::rclose(mFileRef);
398 secdebug("atomicfile", "%p closed %s", this, mPath.c_str());
399 }
400
401 if (mBuffer)
402 {
403 secdebug("atomicfile", "%p free %s buffer %p", this, mPath.c_str(), mBuffer);
404 unloadBuffer();
405 }
406 }
407
408 //
409 // Open the file and return the length in bytes.
410 //
411 off_t
412 AtomicBufferedFile::open()
413 {
414 const char *path = mPath.c_str();
415 if (mFileRef >= 0)
416 {
417 secdebug("atomicfile", "open %s: already open, closing and reopening", path);
418 close();
419 }
420
421 mFileRef = AtomicFile::ropen(path, O_RDONLY, 0);
422 if (mFileRef == -1)
423 {
424 int error = errno;
425 secdebug("atomicfile", "open %s: %s", path, strerror(error));
426
427 // Do the obvious error code translations here.
428 // @@@ Consider moving these up a level.
429 if (error == ENOENT)
430 CssmError::throwMe(CSSMERR_DL_DATASTORE_DOESNOT_EXIST);
431 else if (error == EACCES)
432 CssmError::throwMe(CSSM_ERRCODE_OS_ACCESS_DENIED);
433 else
434 UnixError::throwMe(error);
435 }
436
437 struct stat st;
438 int result = fstat(mFileRef, &st);
439 if (result == 0)
440 {
441 mLength = st.st_size;
442 }
443 else
444 {
445 int error = errno;
446 secdebug("atomicfile", "lseek(%s, END): %s", path, strerror(error));
447 AtomicFile::rclose(mFileRef);
448 UnixError::throwMe(error);
449 }
450
451 secdebug("atomicfile", "%p opened %s: %qd bytes", this, path, mLength);
452
453 return mLength;
454 }
455
456 //
457 // Unload the contents of the file.
458 //
459 void
460 AtomicBufferedFile::unloadBuffer()
461 {
462 if (!mIsMapped)
463 {
464 delete [] mBuffer;
465 }
466 else
467 {
468 munmap(mBuffer, mLength);
469 }
470 }
471
472 //
473 // Load the contents of the file into memory.
474 // If we are on a local file system, we mmap the file. Otherwise, we
475 // read it all into memory
476 void
477 AtomicBufferedFile::loadBuffer()
478 {
479 if (!mIsMapped)
480 {
481 // make a buffer big enough to hold the entire file
482 mBuffer = new uint8[mLength];
483 lseek(mFileRef, 0, SEEK_SET);
484 ssize_t pos = 0;
485
486 ssize_t bytesToRead = mLength;
487 while (bytesToRead > 0)
488 {
489 ssize_t bytesRead = ::read(mFileRef, mBuffer + pos, bytesToRead);
490 if (bytesRead == -1)
491 {
492 if (errno != EINTR)
493 {
494 int error = errno;
495 secdebug("atomicfile", "lseek(%s, END): %s", mPath.c_str(), strerror(error));
496 AtomicFile::rclose(mFileRef);
497 UnixError::throwMe(error);
498 }
499 }
500 else
501 {
502 bytesToRead -= bytesRead;
503 pos += bytesRead;
504 }
505 }
506 }
507 else
508 {
509 // mmap the buffer into place
510 mBuffer = (uint8*) mmap(NULL, mLength, PROT_READ, MAP_PRIVATE, mFileRef, 0);
511 if (mBuffer == (uint8*) -1)
512 {
513 int error = errno;
514 secdebug("atomicfile", "lseek(%s, END): %s", mPath.c_str(), strerror(error));
515 AtomicFile::rclose(mFileRef);
516 UnixError::throwMe(error);
517 }
518 }
519 }
520
521
522
523 //
524 // Read the file starting at inOffset for inLength bytes into the buffer and return
525 // a pointer to it. On return outLength contain the actual number of bytes read, it
526 // will only ever be less than inLength if EOF was reached, and it will never be more
527 // than inLength.
528 //
529 const uint8 *
530 AtomicBufferedFile::read(off_t inOffset, off_t inLength, off_t &outLength)
531 {
532 if (mFileRef < 0)
533 {
534 secdebug("atomicfile", "read %s: file yet not opened, opening", mPath.c_str());
535 open();
536 }
537
538 off_t bytesLeft = inLength;
539 if (mBuffer)
540 {
541 secdebug("atomicfile", "%p free %s buffer %p", this, mPath.c_str(), mBuffer);
542 unloadBuffer();
543 }
544
545 loadBuffer();
546
547 secdebug("atomicfile", "%p allocated %s buffer %p size %qd", this, mPath.c_str(), mBuffer, bytesLeft);
548
549 ssize_t maxEnd = inOffset + inLength;
550 if (maxEnd > mLength)
551 {
552 maxEnd = mLength;
553 }
554
555 outLength = maxEnd - inOffset;
556
557 return mBuffer + inOffset;
558 }
559
560 void
561 AtomicBufferedFile::close()
562 {
563 if (mFileRef < 0)
564 {
565 secdebug("atomicfile", "close %s: already closed", mPath.c_str());
566 }
567 else
568 {
569 int result = AtomicFile::rclose(mFileRef);
570 mFileRef = -1;
571 if (result == -1)
572 {
573 int error = errno;
574 secdebug("atomicfile", "close %s: %s", mPath.c_str(), strerror(errno));
575 UnixError::throwMe(error);
576 }
577
578 secdebug("atomicfile", "%p closed %s", this, mPath.c_str());
579 }
580 }
581
582
583 //
584 // AtomicTempFile - A temporary file to write changes to.
585 //
586 AtomicTempFile::AtomicTempFile(AtomicFile &inFile, const RefPointer<AtomicLockedFile> &inLockedFile, mode_t mode) :
587 mFile(inFile),
588 mLockedFile(inLockedFile),
589 mCreating(true)
590 {
591 create(mode);
592 }
593
594 AtomicTempFile::AtomicTempFile(AtomicFile &inFile, const RefPointer<AtomicLockedFile> &inLockedFile) :
595 mFile(inFile),
596 mLockedFile(inLockedFile),
597 mCreating(false)
598 {
599 create(mFile.mode());
600 }
601
602 AtomicTempFile::~AtomicTempFile()
603 {
604 // rollback if we didn't commit yet.
605 if (mFileRef >= 0)
606 rollback();
607 }
608
609 //
610 // Open the file and return the length in bytes.
611 //
612 void
613 AtomicTempFile::create(mode_t mode)
614 {
615 // we now generate our temporary file name through sandbox API's.
616
617 // put the dir into a canonical form
618 string dir = mFile.dir();
619 int i = dir.length() - 1;
620
621 // walk backwards until we get to a non / character
622 while (i >= 0 && dir[i] == '/')
623 {
624 i -= 1;
625 }
626
627 // point one beyond the string
628 i += 1;
629
630 const char* temp = _amkrtemp((dir.substr(0, i) + "/" + mFile.file()).c_str());
631 if (temp == NULL)
632 {
633 UnixError::throwMe(errno);
634 }
635
636 mPath = temp;
637 free((void*) temp);
638
639 const char *path = mPath.c_str();
640
641 mFileRef = AtomicFile::ropen(path, O_WRONLY|O_CREAT|O_TRUNC, mode);
642 if (mFileRef == -1)
643 {
644 int error = errno;
645 secdebug("atomicfile", "open %s: %s", path, strerror(error));
646
647 // Do the obvious error code translations here.
648 // @@@ Consider moving these up a level.
649 if (error == EACCES)
650 CssmError::throwMe(CSSM_ERRCODE_OS_ACCESS_DENIED);
651 else
652 UnixError::throwMe(error);
653 }
654
655 // If we aren't creating the inital file, make sure we preserve
656 // the mode of the old file regardless of the current umask.
657 // If we are creating the inital file we respect the users
658 // current umask.
659 if (!mCreating)
660 {
661 if (::fchmod(mFileRef, mode))
662 {
663 int error = errno;
664 secdebug("atomicfile", "fchmod %s: %s", path, strerror(error));
665 UnixError::throwMe(error);
666 }
667 }
668
669 secdebug("atomicfile", "%p created %s", this, path);
670 }
671
672 void
673 AtomicTempFile::write(AtomicFile::OffsetType inOffsetType, off_t inOffset, const uint32 inData)
674 {
675 uint32 aData = htonl(inData);
676 write(inOffsetType, inOffset, reinterpret_cast<uint8 *>(&aData), sizeof(aData));
677 }
678
679 void
680 AtomicTempFile::write(AtomicFile::OffsetType inOffsetType, off_t inOffset,
681 const uint32 *inData, uint32 inCount)
682 {
683 #ifdef HOST_LONG_IS_NETWORK_LONG
684 // Optimize this for the case where hl == nl
685 const uint32 *aBuffer = inData;
686 #else
687 auto_array<uint32> aBuffer(inCount);
688 for (uint32 i = 0; i < inCount; i++)
689 aBuffer.get()[i] = htonl(inData[i]);
690 #endif
691
692 write(inOffsetType, inOffset, reinterpret_cast<const uint8 *>(aBuffer.get()),
693 inCount * sizeof(*inData));
694 }
695
696 void
697 AtomicTempFile::write(AtomicFile::OffsetType inOffsetType, off_t inOffset, const uint8 *inData, size_t inLength)
698 {
699 off_t pos;
700 if (inOffsetType == AtomicFile::FromEnd)
701 {
702 pos = ::lseek(mFileRef, 0, SEEK_END);
703 if (pos == -1)
704 {
705 int error = errno;
706 secdebug("atomicfile", "lseek(%s, %qd): %s", mPath.c_str(), inOffset, strerror(error));
707 UnixError::throwMe(error);
708 }
709 }
710 else if (inOffsetType == AtomicFile::FromStart)
711 pos = inOffset;
712 else
713 CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR);
714
715 off_t bytesLeft = inLength;
716 const uint8 *ptr = inData;
717 while (bytesLeft)
718 {
719 size_t toWrite = bytesLeft > kAtomicFileMaxBlockSize ? kAtomicFileMaxBlockSize : size_t(bytesLeft);
720 ssize_t bytesWritten = ::pwrite(mFileRef, ptr, toWrite, pos);
721 if (bytesWritten == -1)
722 {
723 int error = errno;
724 if (error == EINTR)
725 {
726 // We got interrupted by a signal, so try again.
727 secdebug("atomicfile", "write %s: interrupted, retrying", mPath.c_str());
728 continue;
729 }
730
731 secdebug("atomicfile", "write %s: %s", mPath.c_str(), strerror(error));
732 UnixError::throwMe(error);
733 }
734
735 // Write returning 0 is bad mmkay.
736 if (bytesWritten == 0)
737 {
738 secdebug("atomicfile", "write %s: 0 bytes written", mPath.c_str());
739 CssmError::throwMe(CSSMERR_DL_INTERNAL_ERROR);
740 }
741
742 secdebug("atomicfile", "%p wrote %s %ld bytes from %p", this, mPath.c_str(), bytesWritten, ptr);
743
744 bytesLeft -= bytesWritten;
745 ptr += bytesWritten;
746 pos += bytesWritten;
747 }
748 }
749
750 void
751 AtomicTempFile::fsync()
752 {
753 if (mFileRef < 0)
754 {
755 secdebug("atomicfile", "fsync %s: already closed", mPath.c_str());
756 }
757 else
758 {
759 int result;
760 do
761 {
762 result = ::fsync(mFileRef);
763 } while (result && errno == EINTR);
764
765 if (result == -1)
766 {
767 int error = errno;
768 secdebug("atomicfile", "fsync %s: %s", mPath.c_str(), strerror(errno));
769 UnixError::throwMe(error);
770 }
771
772 secdebug("atomicfile", "%p fsynced %s", this, mPath.c_str());
773 }
774 }
775
776 void
777 AtomicTempFile::close()
778 {
779 if (mFileRef < 0)
780 {
781 secdebug("atomicfile", "close %s: already closed", mPath.c_str());
782 }
783 else
784 {
785 int result = AtomicFile::rclose(mFileRef);
786 mFileRef = -1;
787 if (result == -1)
788 {
789 int error = errno;
790 secdebug("atomicfile", "close %s: %s", mPath.c_str(), strerror(errno));
791 UnixError::throwMe(error);
792 }
793
794 secdebug("atomicfile", "%p closed %s", this, mPath.c_str());
795 }
796 }
797
798 // Commit the current create or write and close the write file. Note that a throw during the commit does an automatic rollback.
799 void
800 AtomicTempFile::commit()
801 {
802 try
803 {
804 fsync();
805 close();
806 const char *oldPath = mPath.c_str();
807 const char *newPath = mFile.path().c_str();
808
809 // <rdar://problem/6991037>
810 // Copy the security parameters of one file to another
811 // Adding this to guard against setuid utilities that are re-writing a user's keychain. We don't want to leave them root-owned.
812 // In order to not break backward compatability we'll make a best effort, but continue if these efforts fail.
813 //
814 // To clear something up - newPath is the name the keychain will become - which is the name of the file being replaced
815 // oldPath is the "temp filename".
816
817 copyfile_state_t s;
818 s = copyfile_state_alloc();
819
820 if(copyfile(newPath, oldPath, s, COPYFILE_SECURITY | COPYFILE_NOFOLLOW) == -1) // Not fatal
821 secdebug("atomicfile", "copyfile (%s, %s): %s", oldPath, newPath, strerror(errno));
822
823 copyfile_state_free(s);
824 // END <rdar://problem/6991037>
825
826 ::utimes(oldPath, NULL);
827
828 if (::rename(oldPath, newPath) == -1)
829 {
830 int error = errno;
831 secdebug("atomicfile", "rename (%s, %s): %s", oldPath, newPath, strerror(errno));
832 UnixError::throwMe(error);
833 }
834
835 // Unlock the lockfile
836 mLockedFile = NULL;
837
838 secdebug("atomicfile", "%p commited %s", this, oldPath);
839 }
840 catch (...)
841 {
842 rollback();
843 throw;
844 }
845 }
846
847 // Rollback the current create or write (happens automatically if commit() isn't called before the destructor is.
848 void
849 AtomicTempFile::rollback() throw()
850 {
851 if (mFileRef >= 0)
852 {
853 AtomicFile::rclose(mFileRef);
854 mFileRef = -1;
855 }
856
857 // @@@ Log errors if this fails.
858 const char *path = mPath.c_str();
859 if (::unlink(path) == -1)
860 {
861 secdebug("atomicfile", "unlink %s: %s", path, strerror(errno));
862 // rollback can't throw
863 }
864
865 // @@@ Think about this. Depending on how we do locking we might not need this.
866 if (mCreating)
867 {
868 const char *path = mFile.path().c_str();
869 if (::unlink(path) == -1)
870 {
871 secdebug("atomicfile", "unlink %s: %s", path, strerror(errno));
872 // rollback can't throw
873 }
874 }
875 }
876
877
878 //
879 // An advisory write lock for inFile.
880 //
881 FileLocker::~FileLocker()
882 {
883 }
884
885
886
887 LocalFileLocker::LocalFileLocker(AtomicFile &inFile) :
888 mPath(inFile.lockFileName())
889 {
890 }
891
892
893 LocalFileLocker::~LocalFileLocker()
894 {
895 }
896
897
898
899 #ifndef NDEBUG
900 static double GetTime()
901 {
902 struct timeval t;
903 gettimeofday(&t, NULL);
904 return ((double) t.tv_sec) + ((double) t.tv_usec) / 1000000.0;
905 }
906 #endif
907
908
909
910 void
911 LocalFileLocker::lock(mode_t mode)
912 {
913 struct stat st;
914
915 do
916 {
917 // if the lock file doesn't exist, create it
918 mLockFile = open(mPath.c_str(), O_RDONLY | O_CREAT, mode);
919
920 // if we can't open or create the file, something is wrong
921 if (mLockFile == -1)
922 {
923 UnixError::throwMe(errno);
924 }
925
926 // try to get exclusive access to the file
927 IFDEBUG(double startTime = GetTime());
928 int result = flock(mLockFile, LOCK_EX);
929 IFDEBUG(double endTime = GetTime());
930
931 IFDEBUG(secdebug("atomictime", "Waited %.4f milliseconds for file lock", (endTime - startTime) * 1000.0));
932
933 // errors at this point are bad
934 if (result == -1)
935 {
936 UnixError::throwMe(errno);
937 }
938
939 // check and see if the file we have access to still exists. If not, another file shared our file lock
940 // due to a hash collision and has thrown our lock away -- that, or a user blew the lock file away himself.
941
942 result = fstat(mLockFile, &st);
943
944 // errors at this point are bad
945 if (result == -1)
946 {
947 UnixError::throwMe(errno);
948 }
949
950 if (st.st_nlink == 0) // we've been unlinked!
951 {
952 close(mLockFile);
953 }
954 } while (st.st_nlink == 0);
955 }
956
957
958 void
959 LocalFileLocker::unlock()
960 {
961 flock(mLockFile, LOCK_UN);
962 close(mLockFile);
963 }
964
965
966
967 NetworkFileLocker::NetworkFileLocker(AtomicFile &inFile) :
968 mDir(inFile.dir()),
969 mPath(inFile.dir() + "lck~" + inFile.file())
970 {
971 }
972
973 NetworkFileLocker::~NetworkFileLocker()
974 {
975 }
976
977 std::string
978 NetworkFileLocker::unique(mode_t mode)
979 {
980 static const int randomPart = 16;
981 DevRandomGenerator randomGen;
982 std::string::size_type dirSize = mDir.size();
983 std::string fullname(dirSize + randomPart + 2, '\0');
984 fullname.replace(0, dirSize, mDir);
985 fullname[dirSize] = '~'; /* UNIQ_PREFIX */
986 char buf[randomPart];
987 struct stat filebuf;
988 int result, fd = -1;
989
990 for (int retries = 0; retries < 10; ++retries)
991 {
992 /* Make a random filename. */
993 randomGen.random(buf, randomPart);
994 for (int ix = 0; ix < randomPart; ++ix)
995 {
996 char ch = buf[ix] & 0x3f;
997 fullname[ix + dirSize + 1] = ch +
998 ( ch < 26 ? 'A'
999 : ch < 26 + 26 ? 'a' - 26
1000 : ch < 26 + 26 + 10 ? '0' - 26 - 26
1001 : ch == 26 + 26 + 10 ? '-' - 26 - 26 - 10
1002 : '_' - 26 - 26 - 11);
1003 }
1004
1005 result = lstat(fullname.c_str(), &filebuf);
1006 if (result && errno == ENAMETOOLONG)
1007 {
1008 do
1009 fullname.erase(fullname.end() - 1);
1010 while((result = lstat(fullname.c_str(), &filebuf)) && errno == ENAMETOOLONG && fullname.size() > dirSize + 8);
1011 } /* either it stopped being a problem or we ran out of filename */
1012
1013 if (result && errno == ENOENT)
1014 {
1015 fd = AtomicFile::ropen(fullname.c_str(), O_WRONLY|O_CREAT|O_EXCL, mode);
1016 if (fd >= 0 || errno != EEXIST)
1017 break;
1018 }
1019 }
1020
1021 if (fd < 0)
1022 {
1023 int error = errno;
1024 ::syslog(LOG_ERR, "Couldn't create temp file %s: %s", fullname.c_str(), strerror(error));
1025 secdebug("atomicfile", "Couldn't create temp file %s: %s", fullname.c_str(), strerror(error));
1026 UnixError::throwMe(error);
1027 }
1028
1029 /* @@@ Check for EINTR. */
1030 write(fd, "0", 1); /* pid 0, `works' across networks */
1031
1032 AtomicFile::rclose(fd);
1033
1034 return fullname;
1035 }
1036
1037 /* 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. */
1038 int
1039 NetworkFileLocker::rlink(const char *const old, const char *const newn, struct stat &sto)
1040 {
1041 int result = ::link(old,newn);
1042 if (result)
1043 {
1044 int serrno = errno;
1045 if (::lstat(old, &sto) == 0)
1046 {
1047 struct stat stn;
1048 if (::lstat(newn, &stn) == 0
1049 && sto.st_dev == stn.st_dev
1050 && sto.st_ino == stn.st_ino
1051 && sto.st_uid == stn.st_uid
1052 && sto.st_gid == stn.st_gid
1053 && !S_ISLNK(sto.st_mode))
1054 {
1055 /* Link failed but files are the same so the link really went ok. */
1056 return 0;
1057 }
1058 else
1059 result = 1;
1060 }
1061 errno = serrno; /* Restore errno from link() */
1062 }
1063
1064 return result;
1065 }
1066
1067 /* NFS-resistant rename()
1068 * rename with fallback for systems that don't support it
1069 * Note that this does not preserve the contents of the file. */
1070 int
1071 NetworkFileLocker::myrename(const char *const old, const char *const newn)
1072 {
1073 struct stat stbuf;
1074 int fd = -1;
1075 int ret;
1076
1077 /* Try a real hardlink */
1078 ret = rlink(old, newn, stbuf);
1079 if (ret > 0)
1080 {
1081 if (stbuf.st_nlink < 2 && (errno == EXDEV || errno == ENOTSUP))
1082 {
1083 /* Hard link failed so just create a new file with O_EXCL instead. */
1084 fd = AtomicFile::ropen(newn, O_WRONLY|O_CREAT|O_EXCL, stbuf.st_mode);
1085 if (fd >= 0)
1086 ret = 0;
1087 }
1088 }
1089
1090 /* We want the errno from the link or the ropen, not that of the unlink. */
1091 int serrno = errno;
1092
1093 /* Unlink the temp file. */
1094 ::unlink(old);
1095 if (fd > 0)
1096 AtomicFile::rclose(fd);
1097
1098 errno = serrno;
1099 return ret;
1100 }
1101
1102 int
1103 NetworkFileLocker::xcreat(const char *const name, mode_t mode, time_t &tim)
1104 {
1105 std::string uniqueName = unique(mode);
1106 const char *uniquePath = uniqueName.c_str();
1107 struct stat stbuf; /* return the filesystem time to the caller */
1108 stat(uniquePath, &stbuf);
1109 tim = stbuf.st_mtime;
1110 return myrename(uniquePath, name);
1111 }
1112
1113 void
1114 NetworkFileLocker::lock(mode_t mode)
1115 {
1116 const char *path = mPath.c_str();
1117 bool triedforce = false;
1118 struct stat stbuf;
1119 time_t t, locktimeout = 1024; /* DEFlocktimeout, 17 minutes. */
1120 bool doSyslog = false;
1121 bool failed = false;
1122 int retries = 0;
1123
1124 while (!failed)
1125 {
1126 /* Don't syslog first time through. */
1127 if (doSyslog)
1128 ::syslog(LOG_NOTICE, "Locking %s", path);
1129 else
1130 doSyslog = true;
1131
1132 secdebug("atomicfile", "Locking %s", path); /* in order to cater for clock skew: get */
1133 if (!xcreat(path, mode, t)) /* time t from the filesystem */
1134 {
1135 /* lock acquired, hurray! */
1136 break;
1137 }
1138 switch(errno)
1139 {
1140 case EEXIST: /* check if it's time for a lock override */
1141 if (!lstat(path, &stbuf) && stbuf.st_size <= 16 /* MAX_locksize */ && locktimeout
1142 && !lstat(path, &stbuf) && locktimeout < t - stbuf.st_mtime)
1143 /* stat() till unlink() should be atomic, but can't guarantee that. */
1144 {
1145 if (triedforce)
1146 {
1147 /* Already tried, force lock override, not trying again */
1148 failed = true;
1149 break;
1150 }
1151 else if (S_ISDIR(stbuf.st_mode) || ::unlink(path))
1152 {
1153 triedforce=true;
1154 ::syslog(LOG_ERR, "Forced unlock denied on %s", path);
1155 secdebug("atomicfile", "Forced unlock denied on %s", path);
1156 }
1157 else
1158 {
1159 ::syslog(LOG_ERR, "Forcing lock on %s", path);
1160 secdebug("atomicfile", "Forcing lock on %s", path);
1161 sleep(16 /* DEFsuspend */);
1162 break;
1163 }
1164 }
1165 else
1166 triedforce = false; /* legitimate iteration, clear flag */
1167
1168 /* Reset retry counter. */
1169 retries = 0;
1170 usleep(250000);
1171 break;
1172
1173 case ENOSPC: /* no space left, treat it as a transient */
1174 #ifdef EDQUOT /* NFS failure */
1175 case EDQUOT: /* maybe it was a short term shortage? */
1176 #endif
1177 case ENOENT:
1178 case ENOTDIR:
1179 case EIO:
1180 /*case EACCES:*/
1181 if(++retries < (256 + 1)) /* nfsTRY number of times+1 to ignore spurious NFS errors */
1182 usleep(250000);
1183 else
1184 failed = true;
1185 break;
1186
1187 #ifdef ENAMETOOLONG
1188 case ENAMETOOLONG: /* Filename is too long, shorten and retry */
1189 if (mPath.size() > mDir.size() + 8)
1190 {
1191 secdebug("atomicfile", "Truncating %s and retrying lock", path);
1192 mPath.erase(mPath.end() - 1);
1193 path = mPath.c_str();
1194 /* Reset retry counter. */
1195 retries = 0;
1196 break;
1197 }
1198 /* DROPTHROUGH */
1199 #endif
1200 default:
1201 failed = true;
1202 break;
1203 }
1204 }
1205
1206 if (failed)
1207 {
1208 int error = errno;
1209 ::syslog(LOG_ERR, "Lock failure on %s: %s", path, strerror(error));
1210 secdebug("atomicfile", "Lock failure on %s: %s", path, strerror(error));
1211 UnixError::throwMe(error);
1212 }
1213 }
1214
1215 void
1216 NetworkFileLocker::unlock()
1217 {
1218 const char *path = mPath.c_str();
1219 if (::unlink(path) == -1)
1220 {
1221 secdebug("atomicfile", "unlink %s: %s", path, strerror(errno));
1222 // unlock can't throw
1223 }
1224 }
1225
1226
1227
1228 AtomicLockedFile::AtomicLockedFile(AtomicFile &inFile)
1229 {
1230 if (inFile.isOnLocalFileSystem())
1231 {
1232 mFileLocker = new LocalFileLocker(inFile);
1233 }
1234 else
1235 {
1236 mFileLocker = new NetworkFileLocker(inFile);
1237 }
1238
1239 lock();
1240 }
1241
1242
1243
1244 AtomicLockedFile::~AtomicLockedFile()
1245 {
1246 unlock();
1247 delete mFileLocker;
1248 }
1249
1250
1251
1252 void
1253 AtomicLockedFile::lock(mode_t mode)
1254 {
1255 mFileLocker->lock(mode);
1256 }
1257
1258
1259
1260 void AtomicLockedFile::unlock() throw()
1261 {
1262 mFileLocker->unlock();
1263 }
1264
1265
1266
1267 #undef kAtomicFileMaxBlockSize