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