unix++.cpp   [plain text]


/*
 * Copyright (c) 2000-2001,2003-2004 Apple Computer, Inc. All Rights Reserved.
 * 
 * @APPLE_LICENSE_HEADER_START@
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */


//
// unix++ - C++ layer for basic UNIX facilities
//
#include "unix++.h"
#include <security_utilities/memutils.h>
#include <security_utilities/debugging.h>
#include <sys/xattr.h>
#include <cstdarg>


namespace Security {
namespace UnixPlusPlus {

using LowLevelMemoryUtilities::increment;


//
// Canonical open of a file descriptor. All other open operations channel through this.
// Note that we abuse the S_IFMT mode flags as operational options.
//
void FileDesc::open(const char *path, int flags, mode_t mode)
{
	if ((mFd = ::open(path, flags, mode & ~S_IFMT)) == -1)
		if (errno == ENOENT && (mode & S_IFMT) == modeMissingOk)
			return;
		else
			UnixError::throwMe();
	mAtEnd = false;
    secdebug("unixio", "open(%s,0x%x,0x%x) = %d", path, flags, mode, mFd);
}

void FileDesc::close()
{
    if (mFd >= 0) {
        checkError(::close(mFd));
        secdebug("unixio", "close(%d)", mFd);
        mFd = invalidFd;
    }
}


//
// Filedescoid operations
//
size_t FileDesc::read(void *addr, size_t length)
{
    switch (ssize_t rc = ::read(mFd, addr, length)) {
    case 0:		// end-of-source
        if (length == 0) { // check for errors, but don't set mAtEnd unless we have to
            secdebug("unixio", "%d zero read (ignored)", mFd);
            return 0;
        }
        mAtEnd = true;
        secdebug("unixio", "%d end of data", mFd);
        return 0;
    case -1:	// error
        if (errno == EAGAIN)
            return 0;	// no data, unknown end-of-source status
        UnixError::throwMe(); // throw error
    default:	// have data
        return rc;
    }
}

size_t FileDesc::write(const void *addr, size_t length)
{
    ssize_t rc = ::write(mFd, addr, length);
    if (rc == -1) {
        if (errno == EAGAIN)
            return 0;
        UnixError::throwMe();
    }
    return rc;
}


//
// I/O with integral positioning.
// These don't affect file position and the atEnd() flag; and they
// don't make allowances for asynchronous I/O.
//
size_t FileDesc::read(void *addr, size_t length, off_t position)
{
	return checkError(::pread(mFd, addr, length, position));
}

size_t FileDesc::write(const void *addr, size_t length, off_t position)
{
	return checkError(::pwrite(mFd, addr, length, position));
}


//
// Waiting (repeating) I/O
//
size_t FileDesc::readAll(void *addr, size_t length)
{
	size_t total = 0;
	while (length > 0 && !atEnd()) {
		size_t size = read(addr, length);
		addr = increment(addr, size);
		length -= size;
		total += size;
	}
	return total;
}

size_t FileDesc::readAll(string &value)
{
	string s;
	while (!atEnd()) {
		char buffer[256];
		if (size_t size = read(buffer, sizeof(buffer))) {
			s += string(buffer, size);
			continue;
		}
	}
	swap(value, s);
	return value.length();
}


void FileDesc::writeAll(const void *addr, size_t length)
{
	while (length > 0) {
		size_t size = write(addr, length);
		addr = increment(addr, size);
		length -= size;
	}
}


//
// Seeking
//
size_t FileDesc::seek(off_t position, int whence)
{
    return checkError(::lseek(mFd, position, whence));
}

size_t FileDesc::position() const
{
	return checkError(::lseek(mFd, 0, SEEK_CUR));
}


//
// Mmap support
//
void *FileDesc::mmap(int prot, size_t length, int flags, off_t offset, void *addr)
{
	if (!(flags & (MAP_PRIVATE | MAP_SHARED)))	// one is required
		flags |= MAP_PRIVATE;
    void *result = ::mmap(addr, length ? length : fileSize(), prot, flags, mFd, offset);
    if (result == MAP_FAILED)
        UnixError::throwMe();
    return result;
}


//
// Basic fcntl support
//
int FileDesc::fcntl(int cmd, void *arg) const
{
    int rc = ::fcntl(mFd, cmd, arg);
    secdebug("unixio", "%d fcntl(%d,%p) = %d", mFd, cmd, arg, rc);
	return checkError(rc);
}


//
// Nice fcntl forms
//
void FileDesc::setFlag(int flag, bool on) const
{
    if (flag) {		// if there's anything at all to do...
        int oldFlags = flags();
        flags(on ? (oldFlags | flag) : (oldFlags & ~flag));
    }
}


//
// Duplication operations
//
FileDesc FileDesc::dup() const
{
	return FileDesc(checkError(::dup(mFd)), atEnd());
}

FileDesc FileDesc::dup(int newFd) const
{
	return FileDesc(checkError(::dup2(mFd, newFd)), atEnd());
}


//
// Advisory locking, fcntl style
//
void FileDesc::lock(int type, const Pos &pos)
{
	LockArgs args(type, pos);
	IFDEBUG(args.debug(fd(), "lock"));
	checkError(fcntl(F_SETLKW, &args));
}

bool FileDesc::tryLock(int type, const Pos &pos)
{
	LockArgs args(type, pos);
	IFDEBUG(args.debug(fd(), "tryLock"));
	try {
		fcntl(F_SETLK, &args);
		return true;
	} catch (const UnixError &err) {
		if (err.error == EAGAIN)
			return false;
		else
			throw;
	}
}

#if !defined(NDEBUG)

void FileDesc::LockArgs::debug(int fd, const char *what)
{
	secdebug("fdlock", "%d %s %s:%ld(%ld)", fd, what,
		(l_whence == SEEK_SET) ? "ABS" : (l_whence == SEEK_CUR) ? "REL" : "END",
		long(l_start), long(l_len));
}

#endif //NDEBUG


//
// ioctl support
//
int FileDesc::ioctl(int cmd, void *arg) const
{
    int rc = ::ioctl(mFd, cmd, arg);
    if (rc == -1)
        UnixError::throwMe();
    return rc;
}


//
// Xattr support
//
void FileDesc::setAttr(const char *name, const void *value, size_t length,
	u_int32_t position /* = 0 */, int options /* = 0 */)
{
	checkError(::fsetxattr(mFd, name, value, length, position, options));
}

ssize_t FileDesc::getAttrLength(const char *name)
{
	ssize_t rc = ::fgetxattr(mFd, name, NULL, 0, 0, 0);
	if (rc == -1)
		switch (errno) {
		case ENOATTR:
			return -1;
		default:
			UnixError::throwMe();
		}
	return rc;
}

ssize_t FileDesc::getAttr(const char *name, void *value, size_t length,
	u_int32_t position /* = 0 */, int options /* = 0 */)
{
    ssize_t rc = ::fgetxattr(mFd, name, value, length, position, options);
	if (rc == -1)
		switch (errno) {
		case ENOATTR:
			return -1;
		default:
			UnixError::throwMe();
		}
	return rc;
}

void FileDesc::removeAttr(const char *name, int options /* = 0 */)
{
	if (::fremovexattr(mFd, name, options))
		switch (errno) {
		case ENOATTR:
			if (!(options & XATTR_REPLACE))	// somewhat mis-using an API flag here...
				return;		// attribute not found; we'll call that okay
			// fall through
		default:
			UnixError::throwMe();
		}
}

size_t FileDesc::listAttr(char *value, size_t length, int options /* = 0 */)
{
	return checkError(::flistxattr(mFd, value, length, options));
}


void FileDesc::setAttr(const std::string &name, const std::string &value, int options /* = 0 */)
{
	return setAttr(name, value.c_str(), value.size(), 0, options);
}

std::string FileDesc::getAttr(const std::string &name, int options /* = 0 */)
{
	char buffer[4096];	//@@@ auto-expand?
	ssize_t length = getAttr(name, buffer, sizeof(buffer), 0, options);
	if (length >= 0)
		return string(buffer, length);
	else
		return string();
}


//
// Stat support
//
void FileDesc::fstat(UnixStat &st) const
{
    if (::fstat(mFd, &st))
        UnixError::throwMe();
}

size_t FileDesc::fileSize() const
{
    struct stat st;
    fstat(st);
    return st.st_size;
}

bool FileDesc::isA(int mode) const
{
	struct stat st;
	fstat(st);
	return (st.st_mode & S_IFMT) == mode;
}


void FileDesc::chown(uid_t uid)
{
	checkError(::fchown(mFd, uid, gid_t(-1)));
}

void FileDesc::chown(uid_t uid, gid_t gid)
{
	checkError(::fchown(mFd, uid, gid));
}

void FileDesc::chgrp(gid_t gid)
{
	checkError(::fchown(mFd, uid_t(-1), gid));
}

void FileDesc::chmod(mode_t mode)
{
	checkError(::fchmod(mFd, mode));
}

void FileDesc::chflags(u_int flags)
{
	checkError(::fchflags(mFd, flags));
}


FILE *FileDesc::fdopen(const char *form)
{
	//@@@ pick default value for 'form' based on chracteristics of mFd
    return ::fdopen(mFd, form);
}


//
// Signals and signal masks
//
SigSet sigMask(SigSet set, int how /* = SIG_SETMASK */)
{
	sigset_t old;
	checkError(::sigprocmask(how, &set.value(), &old));
	return old;
}


//
// Make or use a directory, open-style.
//
// Flags are to be interpreted like open(2) flags; particularly
//	O_CREAT		make the directory if not present
//	O_EXCL		fail if the directory is present
// Other open(2) flags are currently ignored.
//
// Yes, it's a function.
//
void makedir(const char *path, int flags, mode_t mode)
{
	struct stat st;
	if (!stat(path, &st)) {
		if (flags & O_EXCL)
			UnixError::throwMe(EEXIST);
		if (!S_ISDIR(st.st_mode))
			UnixError::throwMe(ENOTDIR);
		secdebug("makedir", "%s exists", path);
		return;
	}

	// stat failed
	if (errno != ENOENT || !(flags & O_CREAT))
		UnixError::throwMe();
	
	// ENOENT and creation enabled
	if (::mkdir(path, mode)) {
		if (errno == EEXIST && !(flags & O_EXCL))
			return;		// fine (race condition, resolved)
		UnixError::throwMe();
	}
	secdebug("makedir", "%s created", path);
}


//
// Open, read/write, close a (small) file on disk
//
int ffprintf(const char *path, int flags, mode_t mode, const char *format, ...)
{
	FileDesc fd(path, flags, mode);
	FILE *f = fd.fdopen("w");
	va_list args;
	va_start(args, format);
	int rc = vfprintf(f, format, args);
	va_end(args);
	if (fclose(f))
		UnixError::throwMe();
	return rc;
}

int ffscanf(const char *path, const char *format, ...)
{
	if (FILE *f = fopen(path, "r")) {
		va_list args;
		va_start(args, format);
		int rc = vfscanf(f, format, args);
		va_end(args);
		if (!fclose(f))
			return rc;
	}
	UnixError::throwMe();
}


}	// end namespace IPPlusPlus
}	// end namespace Security