2 * Copyright (c) 2000-2001, 2003 Apple Computer, Inc. All Rights Reserved.
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
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.
19 #include <Security/AtomicFile.h>
21 #include <Security/devrandom.h>
27 #include <sys/param.h>
28 #include <sys/types.h>
32 #define kAtomicFileMaxBlockSize INT_MAX
36 // AtomicFile.cpp - Description t.b.d.
38 AtomicFile::AtomicFile(const std::string
&inPath
) :
41 pathSplit(inPath
, mDir
, mFile
);
44 AtomicFile::~AtomicFile()
48 // Aquire the write lock and remove the file.
50 AtomicFile::performDelete()
52 AtomicLockedFile
lock(*this);
53 if (::unlink(mPath
.c_str()) != 0)
56 secdebug("atomicfile", "unlink %s: %s", mPath
.c_str(), strerror(error
));
58 CssmError::throwMe(CSSMERR_DL_DATASTORE_DOESNOT_EXIST
);
60 UnixError::throwMe(error
);
64 // Aquire the write lock and rename the file (and bump the version and stuff).
66 AtomicFile::rename(const std::string
&inNewPath
)
68 const char *path
= mPath
.c_str();
69 const char *newPath
= inNewPath
.c_str();
71 // @@@ lock the destination file too.
72 AtomicLockedFile
lock(*this);
73 if (::rename(path
, newPath
) != 0)
76 secdebug("atomicfile", "rename(%s, %s): %s", path
, newPath
, strerror(error
));
77 UnixError::throwMe(error
);
81 // Lock the file for writing and return a newly created AtomicTempFile.
82 RefPointer
<AtomicTempFile
>
83 AtomicFile::create(mode_t mode
)
85 const char *path
= mPath
.c_str();
87 // First make sure the directory to this file exists and is writable
90 RefPointer
<AtomicLockedFile
> lock(new AtomicLockedFile(*this));
91 int fileRef
= ropen(path
, O_WRONLY
|O_CREAT
|O_EXCL
, mode
);
95 secdebug("atomicfile", "open %s: %s", path
, strerror(error
));
97 // Do the obvious error code translations here.
98 // @@@ Consider moving these up a level.
100 CssmError::throwMe(CSSM_ERRCODE_OS_ACCESS_DENIED
);
101 else if (error
== EEXIST
)
102 CssmError::throwMe(CSSMERR_DL_DATASTORE_ALREADY_EXISTS
);
104 UnixError::throwMe(error
);
110 // Now that we have created the lock and the new db file create a tempfile
112 RefPointer
<AtomicTempFile
> temp(new AtomicTempFile(*this, lock
, mode
));
113 secdebug("atomicfile", "%p created %s", this, path
);
118 // Creating the temp file failed so remove the db file we just created too.
119 if (::unlink(path
) == -1)
121 secdebug("atomicfile", "unlink %s: %s", path
, strerror(errno
));
127 // Lock the database file for writing and return a newly created AtomicTempFile.
128 RefPointer
<AtomicTempFile
>
131 RefPointer
<AtomicLockedFile
> lock(new AtomicLockedFile(*this));
132 return new AtomicTempFile(*this, lock
);
135 // Return a bufferedFile containing current version of the file for reading.
136 RefPointer
<AtomicBufferedFile
>
139 return new AtomicBufferedFile(mPath
);
143 AtomicFile::mode() const
145 const char *path
= mPath
.c_str();
147 if (::stat(path
, &st
) == -1)
150 secdebug("atomicfile", "stat %s: %s", path
, strerror(error
));
151 UnixError::throwMe(error
);
156 // Split full into a dir and file component.
158 AtomicFile::pathSplit(const std::string
&inFull
, std::string
&outDir
, std::string
&outFile
)
160 std::string::size_type slash
, len
= inFull
.size();
161 slash
= inFull
.rfind('/');
162 if (slash
== std::string::npos
)
167 else if (slash
+ 1 == len
)
174 outDir
= inFull
.substr(0, slash
+ 1);
175 outFile
= inFull
.substr(slash
+ 1, len
);
180 // Make sure the directory up to inDir exists inDir *must* end in a slash.
183 AtomicFile::mkpath(const std::string
&inDir
, mode_t mode
)
185 for (std::string::size_type pos
= 0; (pos
= inDir
.find('/', pos
+ 1)) != std::string::npos
;)
187 std::string path
= inDir
.substr(0, pos
);
188 const char *cpath
= path
.c_str();
190 if (::stat(cpath
, &sb
))
192 if (errno
!= ENOENT
|| ::mkdir(cpath
, mode
))
193 UnixError::throwMe(errno
);
195 else if (!S_ISDIR(sb
.st_mode
))
196 CssmError::throwMe(CSSM_ERRCODE_OS_ACCESS_DENIED
); // @@@ Should be is a directory
201 AtomicFile::ropen(const char *const name
, int flags
, mode_t mode
)
203 int fd
, tries_left
= 4 /* kNoResRetry */;
206 fd
= ::open(name
, flags
, mode
);
207 } while (fd
< 0 && (errno
== EINTR
|| errno
== ENFILE
&& --tries_left
>= 0));
213 AtomicFile::rclose(int fd
)
218 result
= ::close(fd
);
219 } while(result
&& errno
== EINTR
);
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.
229 AtomicBufferedFile::AtomicBufferedFile(const std::string
&inPath
) :
237 AtomicBufferedFile::~AtomicBufferedFile()
241 AtomicFile::rclose(mFileRef
);
242 secdebug("atomicfile", "%p closed %s", this, mPath
.c_str());
247 secdebug("atomicfile", "%p free %s buffer %p", this, mPath
.c_str(), mBuffer
);
253 // Open the file and return the length in bytes.
256 AtomicBufferedFile::open()
258 const char *path
= mPath
.c_str();
261 secdebug("atomicfile", "open %s: already open, closing and reopening", path
);
265 mFileRef
= AtomicFile::ropen(path
, O_RDONLY
, 0);
269 secdebug("atomicfile", "open %s: %s", path
, strerror(error
));
271 // Do the obvious error code translations here.
272 // @@@ Consider moving these up a level.
274 CssmError::throwMe(CSSMERR_DL_DATASTORE_DOESNOT_EXIST
);
275 else if (error
== EACCES
)
276 CssmError::throwMe(CSSM_ERRCODE_OS_ACCESS_DENIED
);
278 UnixError::throwMe(error
);
281 mLength
= ::lseek(mFileRef
, 0, SEEK_END
);
285 secdebug("atomicfile", "lseek(%s, END): %s", path
, strerror(error
));
286 AtomicFile::rclose(mFileRef
);
287 UnixError::throwMe(error
);
290 secdebug("atomicfile", "%p opened %s: %qd bytes", this, path
, mLength
);
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
302 AtomicBufferedFile::read(off_t inOffset
, off_t inLength
, off_t
&outLength
)
306 secdebug("atomicfile", "read %s: file yet not opened, opening", mPath
.c_str());
310 off_t bytesLeft
= inLength
;
314 secdebug("atomicfile", "%p free %s buffer %p", this, mPath
.c_str(), mBuffer
);
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
;
323 size_t toRead
= bytesLeft
> kAtomicFileMaxBlockSize
? kAtomicFileMaxBlockSize
: size_t(bytesLeft
);
324 ssize_t bytesRead
= ::pread(mFileRef
, ptr
, toRead
, pos
);
330 // We got interrupted by a signal, so try again.
331 secdebug("atomicfile", "pread %s: interrupted, retrying", mPath
.c_str());
335 secdebug("atomicfile", "pread %s: %s", mPath
.c_str(), strerror(error
));
338 UnixError::throwMe(error
);
341 // Read returning 0 means EOF was reached so we're done.
345 secdebug("atomicfile", "%p read %s: %d bytes to %p", this, mPath
.c_str(), bytesRead
, ptr
);
347 bytesLeft
-= bytesRead
;
353 outLength
= ptr
- mBuffer
;
359 AtomicBufferedFile::close()
363 secdebug("atomicfile", "close %s: already closed", mPath
.c_str());
367 int result
= AtomicFile::rclose(mFileRef
);
372 secdebug("atomicfile", "close %s: %s", mPath
.c_str(), strerror(errno
));
373 UnixError::throwMe(error
);
376 secdebug("atomicfile", "%p closed %s", this, mPath
.c_str());
382 // AtomicTempFile - A temporary file to write changes to.
384 AtomicTempFile::AtomicTempFile(AtomicFile
&inFile
, const RefPointer
<AtomicLockedFile
> &inLockedFile
, mode_t mode
) :
386 mLockedFile(inLockedFile
),
392 AtomicTempFile::AtomicTempFile(AtomicFile
&inFile
, const RefPointer
<AtomicLockedFile
> &inLockedFile
) :
394 mLockedFile(inLockedFile
),
397 create(mFile
.mode());
400 AtomicTempFile::~AtomicTempFile()
402 // rollback if we didn't commit yet.
408 // Open the file and return the length in bytes.
411 AtomicTempFile::create(mode_t mode
)
413 mPath
= mFile
.dir() + "," + mFile
.file();
414 const char *path
= mPath
.c_str();
416 mFileRef
= AtomicFile::ropen(path
, O_WRONLY
|O_CREAT
|O_TRUNC
, mode
);
420 secdebug("atomicfile", "open %s: %s", path
, strerror(error
));
422 // Do the obvious error code translations here.
423 // @@@ Consider moving these up a level.
425 CssmError::throwMe(CSSM_ERRCODE_OS_ACCESS_DENIED
);
427 UnixError::throwMe(error
);
430 secdebug("atomicfile", "%p created %s", this, path
);
434 AtomicTempFile::write(AtomicFile::OffsetType inOffsetType
, off_t inOffset
, const uint32 inData
)
436 uint32 aData
= htonl(inData
);
437 write(inOffsetType
, inOffset
, reinterpret_cast<uint8
*>(&aData
), sizeof(aData
));
441 AtomicTempFile::write(AtomicFile::OffsetType inOffsetType
, off_t inOffset
,
442 const uint32
*inData
, uint32 inCount
)
444 #ifdef HOST_LONG_IS_NETWORK_LONG
445 // Optimize this for the case where hl == nl
446 const uint32
*aBuffer
= inData
;
448 auto_array
<uint32
> aBuffer(inCount
);
449 for (uint32 i
= 0; i
< inCount
; i
++)
450 aBuffer
.get()[i
] = htonl(inData
[i
]);
453 write(inOffsetType
, inOffset
, reinterpret_cast<const uint8
*>(aBuffer
.get()),
454 inCount
* sizeof(*inData
));
458 AtomicTempFile::write(AtomicFile::OffsetType inOffsetType
, off_t inOffset
, const uint8
*inData
, size_t inLength
)
461 if (inOffsetType
== AtomicFile::FromEnd
)
463 pos
= ::lseek(mFileRef
, 0, SEEK_END
);
467 secdebug("atomicfile", "lseek(%s, %qd): %s", mPath
.c_str(), inOffset
, strerror(error
));
468 UnixError::throwMe(error
);
471 else if (inOffsetType
== AtomicFile::FromStart
)
474 CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR
);
476 off_t bytesLeft
= inLength
;
477 const uint8
*ptr
= inData
;
480 size_t toWrite
= bytesLeft
> kAtomicFileMaxBlockSize
? kAtomicFileMaxBlockSize
: size_t(bytesLeft
);
481 ssize_t bytesWritten
= ::pwrite(mFileRef
, ptr
, toWrite
, pos
);
482 if (bytesWritten
== -1)
487 // We got interrupted by a signal, so try again.
488 secdebug("atomicfile", "write %s: interrupted, retrying", mPath
.c_str());
492 secdebug("atomicfile", "write %s: %s", mPath
.c_str(), strerror(error
));
493 UnixError::throwMe(error
);
496 // Write returning 0 is bad mmkay.
497 if (bytesWritten
== 0)
499 secdebug("atomicfile", "write %s: 0 bytes written", mPath
.c_str());
500 CssmError::throwMe(CSSMERR_DL_INTERNAL_ERROR
);
503 secdebug("atomicfile", "%p wrote %s %d bytes from %p", this, mPath
.c_str(), bytesWritten
, ptr
);
505 bytesLeft
-= bytesWritten
;
512 AtomicTempFile::fsync()
516 secdebug("atomicfile", "fsync %s: already closed", mPath
.c_str());
523 result
= ::fsync(mFileRef
);
524 } while (result
&& errno
== EINTR
);
529 secdebug("atomicfile", "fsync %s: %s", mPath
.c_str(), strerror(errno
));
530 UnixError::throwMe(error
);
533 secdebug("atomicfile", "%p fsynced %s", this, mPath
.c_str());
538 AtomicTempFile::close()
542 secdebug("atomicfile", "close %s: already closed", mPath
.c_str());
546 int result
= AtomicFile::rclose(mFileRef
);
551 secdebug("atomicfile", "close %s: %s", mPath
.c_str(), strerror(errno
));
552 UnixError::throwMe(error
);
555 secdebug("atomicfile", "%p closed %s", this, mPath
.c_str());
559 // Commit the current create or write and close the write file. Note that a throw during the commit does an automatic rollback.
561 AtomicTempFile::commit()
567 const char *oldPath
= mPath
.c_str();
568 const char *newPath
= mFile
.path().c_str();
569 if (::rename(oldPath
, newPath
) == -1)
572 secdebug("atomicfile", "rename (%s, %s): %s", oldPath
, newPath
, strerror(errno
));
573 UnixError::throwMe(error
);
576 // Unlock the lockfile
579 secdebug("atomicfile", "%p commited %s", this, oldPath
);
588 // Rollback the current create or write (happens automatically if commit() isn't called before the destructor is.
590 AtomicTempFile::rollback() throw()
594 AtomicFile::rclose(mFileRef
);
598 // @@@ Log errors if this fails.
599 const char *path
= mPath
.c_str();
600 if (::unlink(path
) == -1)
602 secdebug("atomicfile", "unlink %s: %s", path
, strerror(errno
));
603 // rollback can't throw
606 // @@@ Think about this. Depending on how we do locking we might not need this.
609 const char *path
= mFile
.path().c_str();
610 if (::unlink(path
) == -1)
612 secdebug("atomicfile", "unlink %s: %s", path
, strerror(errno
));
613 // rollback can't throw
620 // An advisory write lock for inFile.
622 AtomicLockedFile::AtomicLockedFile(AtomicFile
&inFile
) :
624 mPath(inFile
.dir() + "lck~" + inFile
.file())
629 AtomicLockedFile::~AtomicLockedFile()
635 AtomicLockedFile::unique(mode_t mode
)
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
];
647 for (int retries
= 0; retries
< 10; ++retries
)
649 /* Make a random filename. */
650 randomGen
.random(buf
, randomPart
);
651 for (int ix
= 0; ix
< randomPart
; ++ix
)
653 char ch
= buf
[ix
] & 0x3f;
654 fullname
[ix
+ dirSize
+ 1] = ch
+
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);
662 result
= lstat(fullname
.c_str(), &filebuf
);
663 if (result
&& errno
== ENAMETOOLONG
)
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 */
670 if (result
&& errno
== ENOENT
)
672 fd
= AtomicFile::ropen(fullname
.c_str(), O_WRONLY
|O_CREAT
|O_EXCL
, mode
);
673 if (fd
>= 0 || errno
!= EEXIST
)
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
);
686 /* @@@ Check for EINTR. */
687 write(fd
, "0", 1); /* pid 0, `works' across networks */
689 AtomicFile::rclose(fd
);
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. */
696 AtomicLockedFile::rlink(const char *const old
, const char *const newn
, struct stat
&sto
)
698 int result
= ::link(old
,newn
);
702 if (::lstat(old
, &sto
) == 0)
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
))
712 /* Link failed but files are the same so the link really went ok. */
718 errno
= serrno
; /* Restore errno from link() */
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. */
728 AtomicLockedFile::myrename(const char *const old
, const char *const newn
)
734 /* Try a real hardlink */
735 ret
= rlink(old
, newn
, stbuf
);
738 if (stbuf
.st_nlink
< 2 && (errno
== EXDEV
|| errno
== ENOTSUP
))
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
);
747 /* We want the errno from the link or the ropen, not that of the unlink. */
750 /* Unlink the temp file. */
753 AtomicFile::rclose(fd
);
760 AtomicLockedFile::xcreat(const char *const name
, mode_t mode
, time_t &tim
)
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
);
771 AtomicLockedFile::lock(mode_t mode
)
773 const char *path
= mPath
.c_str();
774 bool triedforce
= false;
776 time_t t
, locktimeout
= 1024; /* DEFlocktimeout, 17 minutes. */
777 bool doSyslog
= false;
783 /* Don't syslog first time through. */
785 ::syslog(LOG_NOTICE
, "Locking %s", path
);
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 */
792 /* lock acquired, hurray! */
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. */
804 /* Already tried, force lock override, not trying again */
808 else if (S_ISDIR(stbuf
.st_mode
) || ::unlink(path
))
811 ::syslog(LOG_ERR
, "Forced unlock denied on %s", path
);
812 secdebug("atomicfile", "Forced unlock denied on %s", path
);
816 ::syslog(LOG_ERR
, "Forcing lock on %s", path
);
817 secdebug("atomicfile", "Forcing lock on %s", path
);
818 sleep(16 /* DEFsuspend */);
823 triedforce
= false; /* legitimate iteration, clear flag */
825 /* Reset retry counter. */
827 sleep(8 /* DEFlocksleep */);
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? */
838 if(++retries
< (7 + 1)) /* nfsTRY number of times+1 to ignore spurious NFS errors */
839 sleep(8 /* DEFlocksleep */);
845 case ENAMETOOLONG
: /* Filename is too long, shorten and retry */
846 if (mPath
.size() > mDir
.size() + 8)
848 secdebug("atomicfile", "Truncating %s and retrying lock", path
);
849 mPath
.erase(mPath
.end() - 1);
850 path
= mPath
.c_str();
851 /* Reset retry counter. */
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
);
873 AtomicLockedFile::unlock() throw()
875 const char *path
= mPath
.c_str();
876 if (::unlink(path
) == -1)
878 secdebug("atomicfile", "unlink %s: %s", path
, strerror(errno
));
879 // unlock can't throw
884 #undef kAtomicFileMaxBlockSize