]>
Commit | Line | Data |
---|---|---|
b1ab9ed8 | 1 | /* |
fa7225c8 | 2 | * Copyright (c) 2000-2001,2003-2004,2011-2012,2014-2016 Apple Inc. All Rights Reserved. |
b1ab9ed8 A |
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" | |
d87e1158 | 29 | #include <security_utilities/cfutilities.h> |
fa7225c8 | 30 | #include <security_utilities/cfmunge.h> |
b1ab9ed8 A |
31 | #include <security_utilities/memutils.h> |
32 | #include <security_utilities/debugging.h> | |
d87e1158 | 33 | #include <sys/dirent.h> |
b1ab9ed8 A |
34 | #include <sys/xattr.h> |
35 | #include <cstdarg> | |
d87e1158 A |
36 | #include <IOKit/IOKitLib.h> |
37 | #include <IOKit/IOKitKeys.h> | |
fa7225c8 | 38 | #include <IOKit/IOBSD.h> |
d87e1158 | 39 | #include <IOKit/storage/IOStorageDeviceCharacteristics.h> |
b1ab9ed8 A |
40 | |
41 | ||
42 | namespace Security { | |
43 | namespace UnixPlusPlus { | |
44 | ||
45 | using LowLevelMemoryUtilities::increment; | |
46 | ||
47 | ||
48 | // | |
49 | // Canonical open of a file descriptor. All other open operations channel through this. | |
50 | // Note that we abuse the S_IFMT mode flags as operational options. | |
51 | // | |
52 | void FileDesc::open(const char *path, int flags, mode_t mode) | |
53 | { | |
427c49bc A |
54 | if ((mFd = ::open(path, flags, mode & ~S_IFMT)) == -1) { |
55 | if (errno == ENOENT && (mode & S_IFMT) == modeMissingOk) { | |
b1ab9ed8 | 56 | return; |
427c49bc | 57 | } else { |
b1ab9ed8 | 58 | UnixError::throwMe(); |
427c49bc A |
59 | } |
60 | } | |
b1ab9ed8 | 61 | mAtEnd = false; |
fa7225c8 | 62 | secinfo("unixio", "open(%s,0x%x,0x%x) = %d", path, flags, mode, mFd); |
b1ab9ed8 A |
63 | } |
64 | ||
65 | void FileDesc::close() | |
66 | { | |
67 | if (mFd >= 0) { | |
68 | checkError(::close(mFd)); | |
fa7225c8 | 69 | secinfo("unixio", "close(%d)", mFd); |
b1ab9ed8 A |
70 | mFd = invalidFd; |
71 | } | |
72 | } | |
73 | ||
866f8763 A |
74 | void FileDesc::closeAndLog() |
75 | { | |
76 | int result = 0; | |
77 | int retryCount = 2; | |
78 | int error = 0; | |
79 | if (mFd >= 0) { | |
80 | while((result = ::close(mFd)) == -1 && retryCount) { | |
81 | error = errno; | |
82 | switch (error) { | |
83 | case EINTR: | |
84 | case EIO: | |
85 | retryCount --; | |
86 | break; | |
87 | default: | |
88 | secinfo("unixio", "close(%d) error %d", mFd, error); | |
89 | retryCount = 0; | |
90 | break; | |
91 | } | |
92 | } | |
93 | secinfo("unixio", "close(%d) err: %d", mFd, error); | |
94 | mFd = invalidFd; | |
95 | } | |
96 | } | |
b1ab9ed8 A |
97 | |
98 | // | |
99 | // Filedescoid operations | |
100 | // | |
101 | size_t FileDesc::read(void *addr, size_t length) | |
102 | { | |
103 | switch (ssize_t rc = ::read(mFd, addr, length)) { | |
104 | case 0: // end-of-source | |
105 | if (length == 0) { // check for errors, but don't set mAtEnd unless we have to | |
fa7225c8 | 106 | secinfo("unixio", "%d zero read (ignored)", mFd); |
b1ab9ed8 A |
107 | return 0; |
108 | } | |
109 | mAtEnd = true; | |
fa7225c8 | 110 | secinfo("unixio", "%d end of data", mFd); |
b1ab9ed8 A |
111 | return 0; |
112 | case -1: // error | |
113 | if (errno == EAGAIN) | |
114 | return 0; // no data, unknown end-of-source status | |
115 | UnixError::throwMe(); // throw error | |
116 | default: // have data | |
117 | return rc; | |
118 | } | |
119 | } | |
120 | ||
121 | size_t FileDesc::write(const void *addr, size_t length) | |
122 | { | |
123 | ssize_t rc = ::write(mFd, addr, length); | |
124 | if (rc == -1) { | |
125 | if (errno == EAGAIN) | |
126 | return 0; | |
127 | UnixError::throwMe(); | |
128 | } | |
129 | return rc; | |
130 | } | |
131 | ||
132 | ||
133 | // | |
134 | // I/O with integral positioning. | |
135 | // These don't affect file position and the atEnd() flag; and they | |
136 | // don't make allowances for asynchronous I/O. | |
137 | // | |
427c49bc | 138 | size_t FileDesc::read(void *addr, size_t length, size_t position) |
b1ab9ed8 A |
139 | { |
140 | return checkError(::pread(mFd, addr, length, position)); | |
141 | } | |
142 | ||
427c49bc | 143 | size_t FileDesc::write(const void *addr, size_t length, size_t position) |
b1ab9ed8 A |
144 | { |
145 | return checkError(::pwrite(mFd, addr, length, position)); | |
146 | } | |
147 | ||
148 | ||
149 | // | |
150 | // Waiting (repeating) I/O | |
151 | // | |
152 | size_t FileDesc::readAll(void *addr, size_t length) | |
153 | { | |
154 | size_t total = 0; | |
155 | while (length > 0 && !atEnd()) { | |
156 | size_t size = read(addr, length); | |
157 | addr = increment(addr, size); | |
158 | length -= size; | |
159 | total += size; | |
160 | } | |
161 | return total; | |
162 | } | |
163 | ||
164 | size_t FileDesc::readAll(string &value) | |
165 | { | |
166 | string s; | |
167 | while (!atEnd()) { | |
168 | char buffer[256]; | |
169 | if (size_t size = read(buffer, sizeof(buffer))) { | |
170 | s += string(buffer, size); | |
171 | continue; | |
172 | } | |
173 | } | |
174 | swap(value, s); | |
175 | return value.length(); | |
176 | } | |
177 | ||
178 | ||
179 | void FileDesc::writeAll(const void *addr, size_t length) | |
180 | { | |
181 | while (length > 0) { | |
182 | size_t size = write(addr, length); | |
183 | addr = increment(addr, size); | |
184 | length -= size; | |
185 | } | |
186 | } | |
187 | ||
188 | ||
fa7225c8 A |
189 | void FileDesc::truncate(size_t offset) |
190 | { | |
191 | UnixError::check(ftruncate(mFd, offset)); | |
192 | } | |
193 | ||
194 | ||
b1ab9ed8 A |
195 | // |
196 | // Seeking | |
197 | // | |
427c49bc | 198 | size_t FileDesc::seek(size_t position, int whence) |
b1ab9ed8 | 199 | { |
427c49bc | 200 | return (size_t)checkError(::lseek(mFd, position, whence)); |
b1ab9ed8 A |
201 | } |
202 | ||
203 | size_t FileDesc::position() const | |
204 | { | |
427c49bc | 205 | return (size_t)checkError(::lseek(mFd, 0, SEEK_CUR)); |
b1ab9ed8 A |
206 | } |
207 | ||
208 | ||
209 | // | |
210 | // Mmap support | |
211 | // | |
427c49bc | 212 | void *FileDesc::mmap(int prot, size_t length, int flags, size_t offset, void *addr) |
b1ab9ed8 A |
213 | { |
214 | if (!(flags & (MAP_PRIVATE | MAP_SHARED))) // one is required | |
215 | flags |= MAP_PRIVATE; | |
216 | void *result = ::mmap(addr, length ? length : fileSize(), prot, flags, mFd, offset); | |
217 | if (result == MAP_FAILED) | |
218 | UnixError::throwMe(); | |
219 | return result; | |
220 | } | |
221 | ||
222 | ||
223 | // | |
224 | // Basic fcntl support | |
225 | // | |
226 | int FileDesc::fcntl(int cmd, void *arg) const | |
227 | { | |
228 | int rc = ::fcntl(mFd, cmd, arg); | |
fa7225c8 | 229 | secinfo("unixio", "%d fcntl(%d,%p) = %d", mFd, cmd, arg, rc); |
b1ab9ed8 A |
230 | return checkError(rc); |
231 | } | |
232 | ||
233 | ||
234 | // | |
235 | // Nice fcntl forms | |
236 | // | |
237 | void FileDesc::setFlag(int flag, bool on) const | |
238 | { | |
239 | if (flag) { // if there's anything at all to do... | |
240 | int oldFlags = flags(); | |
241 | flags(on ? (oldFlags | flag) : (oldFlags & ~flag)); | |
242 | } | |
243 | } | |
244 | ||
245 | ||
246 | // | |
247 | // Duplication operations | |
248 | // | |
249 | FileDesc FileDesc::dup() const | |
250 | { | |
251 | return FileDesc(checkError(::dup(mFd)), atEnd()); | |
252 | } | |
253 | ||
254 | FileDesc FileDesc::dup(int newFd) const | |
255 | { | |
256 | return FileDesc(checkError(::dup2(mFd, newFd)), atEnd()); | |
257 | } | |
258 | ||
259 | ||
260 | // | |
261 | // Advisory locking, fcntl style | |
262 | // | |
263 | void FileDesc::lock(int type, const Pos &pos) | |
264 | { | |
265 | LockArgs args(type, pos); | |
266 | IFDEBUG(args.debug(fd(), "lock")); | |
267 | checkError(fcntl(F_SETLKW, &args)); | |
268 | } | |
269 | ||
270 | bool FileDesc::tryLock(int type, const Pos &pos) | |
271 | { | |
272 | LockArgs args(type, pos); | |
273 | IFDEBUG(args.debug(fd(), "tryLock")); | |
274 | try { | |
275 | fcntl(F_SETLK, &args); | |
276 | return true; | |
277 | } catch (const UnixError &err) { | |
278 | if (err.error == EAGAIN) | |
279 | return false; | |
280 | else | |
281 | throw; | |
282 | } | |
283 | } | |
284 | ||
285 | #if !defined(NDEBUG) | |
286 | ||
287 | void FileDesc::LockArgs::debug(int fd, const char *what) | |
288 | { | |
fa7225c8 | 289 | secinfo("fdlock", "%d %s %s:%ld(%ld)", fd, what, |
b1ab9ed8 A |
290 | (l_whence == SEEK_SET) ? "ABS" : (l_whence == SEEK_CUR) ? "REL" : "END", |
291 | long(l_start), long(l_len)); | |
292 | } | |
293 | ||
294 | #endif //NDEBUG | |
295 | ||
296 | ||
297 | // | |
298 | // ioctl support | |
299 | // | |
300 | int FileDesc::ioctl(int cmd, void *arg) const | |
301 | { | |
302 | int rc = ::ioctl(mFd, cmd, arg); | |
303 | if (rc == -1) | |
304 | UnixError::throwMe(); | |
305 | return rc; | |
306 | } | |
307 | ||
308 | ||
309 | // | |
310 | // Xattr support | |
311 | // | |
312 | void FileDesc::setAttr(const char *name, const void *value, size_t length, | |
313 | u_int32_t position /* = 0 */, int options /* = 0 */) | |
314 | { | |
315 | checkError(::fsetxattr(mFd, name, value, length, position, options)); | |
316 | } | |
317 | ||
fa7225c8 | 318 | ssize_t FileDesc::getAttrLength(const char *name, int options) |
b1ab9ed8 | 319 | { |
fa7225c8 | 320 | ssize_t rc = ::fgetxattr(mFd, name, NULL, 0, 0, options); |
b1ab9ed8 A |
321 | if (rc == -1) |
322 | switch (errno) { | |
323 | case ENOATTR: | |
324 | return -1; | |
325 | default: | |
326 | UnixError::throwMe(); | |
327 | } | |
328 | return rc; | |
329 | } | |
330 | ||
331 | ssize_t FileDesc::getAttr(const char *name, void *value, size_t length, | |
332 | u_int32_t position /* = 0 */, int options /* = 0 */) | |
333 | { | |
334 | ssize_t rc = ::fgetxattr(mFd, name, value, length, position, options); | |
335 | if (rc == -1) | |
336 | switch (errno) { | |
337 | case ENOATTR: | |
338 | return -1; | |
339 | default: | |
340 | UnixError::throwMe(); | |
341 | } | |
342 | return rc; | |
343 | } | |
344 | ||
345 | void FileDesc::removeAttr(const char *name, int options /* = 0 */) | |
346 | { | |
347 | if (::fremovexattr(mFd, name, options)) | |
348 | switch (errno) { | |
349 | case ENOATTR: | |
350 | if (!(options & XATTR_REPLACE)) // somewhat mis-using an API flag here... | |
351 | return; // attribute not found; we'll call that okay | |
352 | // fall through | |
353 | default: | |
354 | UnixError::throwMe(); | |
355 | } | |
356 | } | |
357 | ||
358 | size_t FileDesc::listAttr(char *value, size_t length, int options /* = 0 */) | |
359 | { | |
360 | return checkError(::flistxattr(mFd, value, length, options)); | |
361 | } | |
362 | ||
363 | ||
364 | void FileDesc::setAttr(const std::string &name, const std::string &value, int options /* = 0 */) | |
365 | { | |
366 | return setAttr(name, value.c_str(), value.size(), 0, options); | |
367 | } | |
368 | ||
369 | std::string FileDesc::getAttr(const std::string &name, int options /* = 0 */) | |
370 | { | |
371 | char buffer[4096]; //@@@ auto-expand? | |
372 | ssize_t length = getAttr(name, buffer, sizeof(buffer), 0, options); | |
373 | if (length >= 0) | |
374 | return string(buffer, length); | |
375 | else | |
376 | return string(); | |
377 | } | |
378 | ||
fa7225c8 A |
379 | |
380 | static bool checkFork(ssize_t rc) | |
381 | { | |
382 | switch (rc) { | |
383 | case 0: // empty fork, produced by NFS/AFP et al; ignore | |
384 | return false; | |
385 | default: // non-empty fork present, fail | |
386 | return true; | |
387 | case -1: // failed system call; let's see... | |
388 | switch (errno) { | |
389 | case ENOATTR: | |
390 | return false; // not present, no problem | |
391 | case EPERM: | |
392 | return false; // HFS+ returns that if we ask for Resource Forks on anything but plain files (e.g. directories) | |
393 | default: | |
394 | UnixError::throwMe(); | |
395 | } | |
396 | } | |
397 | } | |
398 | ||
399 | bool filehasExtendedAttribute(const char *path, const char *forkname) | |
400 | { | |
401 | return checkFork(::getxattr(path, forkname, NULL, 0, 0, 0)); | |
402 | } | |
403 | ||
404 | bool FileDesc::hasExtendedAttribute(const char *forkname) const | |
405 | { | |
406 | return checkFork(::fgetxattr(mFd, forkname, NULL, 0, 0, 0)); | |
407 | } | |
408 | ||
80e23899 A |
409 | bool FileDesc::isPlainFile(const std::string &path) |
410 | { | |
411 | UnixStat st1, st2; | |
412 | this->fstat(st1); | |
413 | if (::lstat(path.c_str(), &st2)) | |
414 | UnixError::throwMe(); | |
415 | ||
416 | return (st1.st_ino == st2.st_ino && S_ISREG(st2.st_mode)); | |
417 | } | |
418 | ||
b1ab9ed8 A |
419 | // |
420 | // Stat support | |
421 | // | |
422 | void FileDesc::fstat(UnixStat &st) const | |
423 | { | |
424 | if (::fstat(mFd, &st)) | |
425 | UnixError::throwMe(); | |
426 | } | |
427 | ||
428 | size_t FileDesc::fileSize() const | |
429 | { | |
430 | struct stat st; | |
431 | fstat(st); | |
427c49bc | 432 | return (size_t)st.st_size; |
b1ab9ed8 A |
433 | } |
434 | ||
435 | bool FileDesc::isA(int mode) const | |
436 | { | |
437 | struct stat st; | |
438 | fstat(st); | |
439 | return (st.st_mode & S_IFMT) == mode; | |
440 | } | |
441 | ||
442 | ||
443 | void FileDesc::chown(uid_t uid) | |
444 | { | |
445 | checkError(::fchown(mFd, uid, gid_t(-1))); | |
446 | } | |
447 | ||
448 | void FileDesc::chown(uid_t uid, gid_t gid) | |
449 | { | |
450 | checkError(::fchown(mFd, uid, gid)); | |
451 | } | |
452 | ||
453 | void FileDesc::chgrp(gid_t gid) | |
454 | { | |
455 | checkError(::fchown(mFd, uid_t(-1), gid)); | |
456 | } | |
457 | ||
458 | void FileDesc::chmod(mode_t mode) | |
459 | { | |
460 | checkError(::fchmod(mFd, mode)); | |
461 | } | |
462 | ||
463 | void FileDesc::chflags(u_int flags) | |
464 | { | |
465 | checkError(::fchflags(mFd, flags)); | |
466 | } | |
467 | ||
468 | ||
469 | FILE *FileDesc::fdopen(const char *form) | |
470 | { | |
471 | //@@@ pick default value for 'form' based on chracteristics of mFd | |
472 | return ::fdopen(mFd, form); | |
473 | } | |
474 | ||
866f8763 A |
475 | AutoFileDesc::AutoFileDesc(const AutoFileDesc& rhs) |
476 | { | |
477 | if (rhs.fd() != invalidFd) { | |
478 | checkSetFd(::dup(rhs.fd())); | |
479 | } | |
480 | mAtEnd = rhs.mAtEnd; | |
481 | } | |
482 | ||
b1ab9ed8 | 483 | |
d87e1158 A |
484 | // |
485 | // Device characteristics | |
486 | // | |
866f8763 | 487 | static CFDictionaryRef CF_RETURNS_RETAINED deviceCharacteristics(FileDesc &fd) |
d87e1158 A |
488 | { |
489 | // get device name | |
fa7225c8 | 490 | FileDesc::UnixStat st; |
d87e1158 | 491 | fd.fstat(st); |
fa7225c8 A |
492 | CFTemp<CFDictionaryRef> matching("{%s=%d,%s=%d}", |
493 | kIOBSDMajorKey, major(st.st_dev), | |
494 | kIOBSDMinorKey, minor(st.st_dev) | |
495 | ); | |
496 | // IOServiceGetMatchingService CONSUMES its dictionary argument(!) | |
497 | io_registry_entry_t entry = IOServiceGetMatchingService(kIOMasterPortDefault, matching.yield()); | |
498 | if (entry != IO_OBJECT_NULL) { | |
499 | // get device characteristics | |
500 | CFDictionaryRef characteristics = (CFDictionaryRef)IORegistryEntrySearchCFProperty(entry, | |
501 | kIOServicePlane, | |
502 | CFSTR(kIOPropertyDeviceCharacteristicsKey), | |
503 | NULL, | |
504 | kIORegistryIterateRecursively | kIORegistryIterateParents); | |
505 | IOObjectRelease(entry); | |
506 | return characteristics; | |
d87e1158 A |
507 | } |
508 | ||
509 | return NULL; // unable to get device characteristics | |
510 | } | |
511 | ||
512 | std::string FileDesc::mediumType() | |
513 | { | |
514 | CFRef<CFDictionaryRef> characteristics = deviceCharacteristics(*this); | |
515 | if (characteristics) { | |
516 | CFStringRef mediumType = (CFStringRef)CFDictionaryGetValue(characteristics, CFSTR(kIOPropertyMediumTypeKey)); | |
517 | if (mediumType) | |
518 | return cfString(mediumType); | |
519 | } | |
520 | return string(); | |
521 | } | |
522 | ||
523 | ||
b1ab9ed8 A |
524 | // |
525 | // Signals and signal masks | |
526 | // | |
527 | SigSet sigMask(SigSet set, int how /* = SIG_SETMASK */) | |
528 | { | |
529 | sigset_t old; | |
530 | checkError(::sigprocmask(how, &set.value(), &old)); | |
531 | return old; | |
532 | } | |
533 | ||
534 | ||
535 | // | |
536 | // Make or use a directory, open-style. | |
537 | // | |
538 | // Flags are to be interpreted like open(2) flags; particularly | |
539 | // O_CREAT make the directory if not present | |
540 | // O_EXCL fail if the directory is present | |
541 | // Other open(2) flags are currently ignored. | |
542 | // | |
543 | // Yes, it's a function. | |
544 | // | |
545 | void makedir(const char *path, int flags, mode_t mode) | |
546 | { | |
547 | struct stat st; | |
548 | if (!stat(path, &st)) { | |
549 | if (flags & O_EXCL) | |
550 | UnixError::throwMe(EEXIST); | |
551 | if (!S_ISDIR(st.st_mode)) | |
552 | UnixError::throwMe(ENOTDIR); | |
fa7225c8 | 553 | secinfo("makedir", "%s exists", path); |
b1ab9ed8 A |
554 | return; |
555 | } | |
556 | ||
557 | // stat failed | |
558 | if (errno != ENOENT || !(flags & O_CREAT)) | |
559 | UnixError::throwMe(); | |
560 | ||
561 | // ENOENT and creation enabled | |
562 | if (::mkdir(path, mode)) { | |
563 | if (errno == EEXIST && !(flags & O_EXCL)) | |
564 | return; // fine (race condition, resolved) | |
565 | UnixError::throwMe(); | |
566 | } | |
fa7225c8 | 567 | secinfo("makedir", "%s created", path); |
b1ab9ed8 A |
568 | } |
569 | ||
570 | ||
571 | // | |
572 | // Open, read/write, close a (small) file on disk | |
573 | // | |
574 | int ffprintf(const char *path, int flags, mode_t mode, const char *format, ...) | |
575 | { | |
576 | FileDesc fd(path, flags, mode); | |
577 | FILE *f = fd.fdopen("w"); | |
578 | va_list args; | |
579 | va_start(args, format); | |
580 | int rc = vfprintf(f, format, args); | |
581 | va_end(args); | |
582 | if (fclose(f)) | |
583 | UnixError::throwMe(); | |
584 | return rc; | |
585 | } | |
586 | ||
587 | int ffscanf(const char *path, const char *format, ...) | |
588 | { | |
589 | if (FILE *f = fopen(path, "r")) { | |
590 | va_list args; | |
591 | va_start(args, format); | |
592 | int rc = vfscanf(f, format, args); | |
593 | va_end(args); | |
594 | if (!fclose(f)) | |
595 | return rc; | |
596 | } | |
597 | UnixError::throwMe(); | |
598 | } | |
599 | ||
600 | ||
601 | } // end namespace IPPlusPlus | |
602 | } // end namespace Security |