1/*
2 * Copyright (c) 2000-2001,2003-2004 Apple Computer, Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24
25//
26// unix++ - C++ layer for basic UNIX facilities
27//
28#include "unix++.h"
29#include <security_utilities/memutils.h>
30#include <security_utilities/debugging.h>
31#include <sys/xattr.h>
32#include <cstdarg>
33
34
35namespace Security {
36namespace UnixPlusPlus {
37
38using LowLevelMemoryUtilities::increment;
39
40
41//
42// Canonical open of a file descriptor. All other open operations channel through this.
43// Note that we abuse the S_IFMT mode flags as operational options.
44//
45void FileDesc::open(const char *path, int flags, mode_t mode)
46{
47	if ((mFd = ::open(path, flags, mode & ~S_IFMT)) == -1) {
48		if (errno == ENOENT && (mode & S_IFMT) == modeMissingOk) {
49			return;
50		} else {
51			UnixError::throwMe();
52        }
53    }
54	mAtEnd = false;
55    secdebug("unixio", "open(%s,0x%x,0x%x) = %d", path, flags, mode, mFd);
56}
57
58void FileDesc::close()
59{
60    if (mFd >= 0) {
61        checkError(::close(mFd));
62        secdebug("unixio", "close(%d)", mFd);
63        mFd = invalidFd;
64    }
65}
66
67
68//
69// Filedescoid operations
70//
71size_t FileDesc::read(void *addr, size_t length)
72{
73    switch (ssize_t rc = ::read(mFd, addr, length)) {
74    case 0:		// end-of-source
75        if (length == 0) { // check for errors, but don't set mAtEnd unless we have to
76            secdebug("unixio", "%d zero read (ignored)", mFd);
77            return 0;
78        }
79        mAtEnd = true;
80        secdebug("unixio", "%d end of data", mFd);
81        return 0;
82    case -1:	// error
83        if (errno == EAGAIN)
84            return 0;	// no data, unknown end-of-source status
85        UnixError::throwMe(); // throw error
86    default:	// have data
87        return rc;
88    }
89}
90
91size_t FileDesc::write(const void *addr, size_t length)
92{
93    ssize_t rc = ::write(mFd, addr, length);
94    if (rc == -1) {
95        if (errno == EAGAIN)
96            return 0;
97        UnixError::throwMe();
98    }
99    return rc;
100}
101
102
103//
104// I/O with integral positioning.
105// These don't affect file position and the atEnd() flag; and they
106// don't make allowances for asynchronous I/O.
107//
108size_t FileDesc::read(void *addr, size_t length, size_t position)
109{
110	return checkError(::pread(mFd, addr, length, position));
111}
112
113size_t FileDesc::write(const void *addr, size_t length, size_t position)
114{
115	return checkError(::pwrite(mFd, addr, length, position));
116}
117
118
119//
120// Waiting (repeating) I/O
121//
122size_t FileDesc::readAll(void *addr, size_t length)
123{
124	size_t total = 0;
125	while (length > 0 && !atEnd()) {
126		size_t size = read(addr, length);
127		addr = increment(addr, size);
128		length -= size;
129		total += size;
130	}
131	return total;
132}
133
134size_t FileDesc::readAll(string &value)
135{
136	string s;
137	while (!atEnd()) {
138		char buffer[256];
139		if (size_t size = read(buffer, sizeof(buffer))) {
140			s += string(buffer, size);
141			continue;
142		}
143	}
144	swap(value, s);
145	return value.length();
146}
147
148
149void FileDesc::writeAll(const void *addr, size_t length)
150{
151	while (length > 0) {
152		size_t size = write(addr, length);
153		addr = increment(addr, size);
154		length -= size;
155	}
156}
157
158
159//
160// Seeking
161//
162#warning Cast to size_t may loose precision, only a problem for large files.
163
164size_t FileDesc::seek(size_t position, int whence)
165{
166    return (size_t)checkError(::lseek(mFd, position, whence));
167}
168
169size_t FileDesc::position() const
170{
171	return (size_t)checkError(::lseek(mFd, 0, SEEK_CUR));
172}
173
174
175//
176// Mmap support
177//
178void *FileDesc::mmap(int prot, size_t length, int flags, size_t offset, void *addr)
179{
180	if (!(flags & (MAP_PRIVATE | MAP_SHARED)))	// one is required
181		flags |= MAP_PRIVATE;
182    void *result = ::mmap(addr, length ? length : fileSize(), prot, flags, mFd, offset);
183    if (result == MAP_FAILED)
184        UnixError::throwMe();
185    return result;
186}
187
188
189//
190// Basic fcntl support
191//
192int FileDesc::fcntl(int cmd, void *arg) const
193{
194    int rc = ::fcntl(mFd, cmd, arg);
195    secdebug("unixio", "%d fcntl(%d,%p) = %d", mFd, cmd, arg, rc);
196	return checkError(rc);
197}
198
199
200//
201// Nice fcntl forms
202//
203void FileDesc::setFlag(int flag, bool on) const
204{
205    if (flag) {		// if there's anything at all to do...
206        int oldFlags = flags();
207        flags(on ? (oldFlags | flag) : (oldFlags & ~flag));
208    }
209}
210
211
212//
213// Duplication operations
214//
215FileDesc FileDesc::dup() const
216{
217	return FileDesc(checkError(::dup(mFd)), atEnd());
218}
219
220FileDesc FileDesc::dup(int newFd) const
221{
222	return FileDesc(checkError(::dup2(mFd, newFd)), atEnd());
223}
224
225
226//
227// Advisory locking, fcntl style
228//
229void FileDesc::lock(int type, const Pos &pos)
230{
231	LockArgs args(type, pos);
232	IFDEBUG(args.debug(fd(), "lock"));
233	checkError(fcntl(F_SETLKW, &args));
234}
235
236bool FileDesc::tryLock(int type, const Pos &pos)
237{
238	LockArgs args(type, pos);
239	IFDEBUG(args.debug(fd(), "tryLock"));
240	try {
241		fcntl(F_SETLK, &args);
242		return true;
243	} catch (const UnixError &err) {
244		if (err.error == EAGAIN)
245			return false;
246		else
247			throw;
248	}
249}
250
251#if !defined(NDEBUG)
252
253void FileDesc::LockArgs::debug(int fd, const char *what)
254{
255	secdebug("fdlock", "%d %s %s:%ld(%ld)", fd, what,
256		(l_whence == SEEK_SET) ? "ABS" : (l_whence == SEEK_CUR) ? "REL" : "END",
257		long(l_start), long(l_len));
258}
259
260#endif //NDEBUG
261
262
263//
264// ioctl support
265//
266int FileDesc::ioctl(int cmd, void *arg) const
267{
268    int rc = ::ioctl(mFd, cmd, arg);
269    if (rc == -1)
270        UnixError::throwMe();
271    return rc;
272}
273
274
275//
276// Xattr support
277//
278void FileDesc::setAttr(const char *name, const void *value, size_t length,
279	u_int32_t position /* = 0 */, int options /* = 0 */)
280{
281	checkError(::fsetxattr(mFd, name, value, length, position, options));
282}
283
284ssize_t FileDesc::getAttrLength(const char *name)
285{
286	ssize_t rc = ::fgetxattr(mFd, name, NULL, 0, 0, 0);
287	if (rc == -1)
288		switch (errno) {
289		case ENOATTR:
290			return -1;
291		default:
292			UnixError::throwMe();
293		}
294	return rc;
295}
296
297ssize_t FileDesc::getAttr(const char *name, void *value, size_t length,
298	u_int32_t position /* = 0 */, int options /* = 0 */)
299{
300    ssize_t rc = ::fgetxattr(mFd, name, value, length, position, options);
301	if (rc == -1)
302		switch (errno) {
303		case ENOATTR:
304			return -1;
305		default:
306			UnixError::throwMe();
307		}
308	return rc;
309}
310
311void FileDesc::removeAttr(const char *name, int options /* = 0 */)
312{
313	if (::fremovexattr(mFd, name, options))
314		switch (errno) {
315		case ENOATTR:
316			if (!(options & XATTR_REPLACE))	// somewhat mis-using an API flag here...
317				return;		// attribute not found; we'll call that okay
318			// fall through
319		default:
320			UnixError::throwMe();
321		}
322}
323
324size_t FileDesc::listAttr(char *value, size_t length, int options /* = 0 */)
325{
326	return checkError(::flistxattr(mFd, value, length, options));
327}
328
329
330void FileDesc::setAttr(const std::string &name, const std::string &value, int options /* = 0 */)
331{
332	return setAttr(name, value.c_str(), value.size(), 0, options);
333}
334
335std::string FileDesc::getAttr(const std::string &name, int options /* = 0 */)
336{
337	char buffer[4096];	//@@@ auto-expand?
338	ssize_t length = getAttr(name, buffer, sizeof(buffer), 0, options);
339	if (length >= 0)
340		return string(buffer, length);
341	else
342		return string();
343}
344
345bool FileDesc::isPlainFile(const std::string &path)
346{
347	UnixStat st1, st2;
348	this->fstat(st1);
349	if (::lstat(path.c_str(), &st2))
350		UnixError::throwMe();
351
352	return (st1.st_ino == st2.st_ino && S_ISREG(st2.st_mode));
353}
354
355
356//
357// Stat support
358//
359void FileDesc::fstat(UnixStat &st) const
360{
361    if (::fstat(mFd, &st))
362        UnixError::throwMe();
363}
364
365size_t FileDesc::fileSize() const
366{
367    struct stat st;
368    fstat(st);
369    return (size_t)st.st_size;
370}
371
372bool FileDesc::isA(int mode) const
373{
374	struct stat st;
375	fstat(st);
376	return (st.st_mode & S_IFMT) == mode;
377}
378
379
380void FileDesc::chown(uid_t uid)
381{
382	checkError(::fchown(mFd, uid, gid_t(-1)));
383}
384
385void FileDesc::chown(uid_t uid, gid_t gid)
386{
387	checkError(::fchown(mFd, uid, gid));
388}
389
390void FileDesc::chgrp(gid_t gid)
391{
392	checkError(::fchown(mFd, uid_t(-1), gid));
393}
394
395void FileDesc::chmod(mode_t mode)
396{
397	checkError(::fchmod(mFd, mode));
398}
399
400void FileDesc::chflags(u_int flags)
401{
402	checkError(::fchflags(mFd, flags));
403}
404
405
406FILE *FileDesc::fdopen(const char *form)
407{
408	//@@@ pick default value for 'form' based on chracteristics of mFd
409    return ::fdopen(mFd, form);
410}
411
412
413//
414// Signals and signal masks
415//
416SigSet sigMask(SigSet set, int how /* = SIG_SETMASK */)
417{
418	sigset_t old;
419	checkError(::sigprocmask(how, &set.value(), &old));
420	return old;
421}
422
423
424//
425// Make or use a directory, open-style.
426//
427// Flags are to be interpreted like open(2) flags; particularly
428//	O_CREAT		make the directory if not present
429//	O_EXCL		fail if the directory is present
430// Other open(2) flags are currently ignored.
431//
432// Yes, it's a function.
433//
434void makedir(const char *path, int flags, mode_t mode)
435{
436	struct stat st;
437	if (!stat(path, &st)) {
438		if (flags & O_EXCL)
439			UnixError::throwMe(EEXIST);
440		if (!S_ISDIR(st.st_mode))
441			UnixError::throwMe(ENOTDIR);
442		secdebug("makedir", "%s exists", path);
443		return;
444	}
445
446	// stat failed
447	if (errno != ENOENT || !(flags & O_CREAT))
448		UnixError::throwMe();
449
450	// ENOENT and creation enabled
451	if (::mkdir(path, mode)) {
452		if (errno == EEXIST && !(flags & O_EXCL))
453			return;		// fine (race condition, resolved)
454		UnixError::throwMe();
455	}
456	secdebug("makedir", "%s created", path);
457}
458
459
460//
461// Open, read/write, close a (small) file on disk
462//
463int ffprintf(const char *path, int flags, mode_t mode, const char *format, ...)
464{
465	FileDesc fd(path, flags, mode);
466	FILE *f = fd.fdopen("w");
467	va_list args;
468	va_start(args, format);
469	int rc = vfprintf(f, format, args);
470	va_end(args);
471	if (fclose(f))
472		UnixError::throwMe();
473	return rc;
474}
475
476int ffscanf(const char *path, const char *format, ...)
477{
478	if (FILE *f = fopen(path, "r")) {
479		va_list args;
480		va_start(args, format);
481		int rc = vfscanf(f, format, args);
482		va_end(args);
483		if (!fclose(f))
484			return rc;
485	}
486	UnixError::throwMe();
487}
488
489
490}	// end namespace IPPlusPlus
491}	// end namespace Security
492