1// libSystem functions Rust std and LLVM reach for that Mac OS X 10.6 lacks or treats
2// differently. Linked into the executable, so its own references bind here first under
3// the two-level namespace; libraries the system loads keep the real ones.
4#include <dirent.h>
5#include <dlfcn.h>
6#include <errno.h>
7#include <fcntl.h>
8#include <limits.h>
9#include <math.h>
10#include <mach/mach_time.h>
11#include <pthread.h>
12#include <stdarg.h>
13#include <stdint.h>
14#include <stdlib.h>
15#include <string.h>
16#include <sys/stat.h>
17#include <sys/time.h>
18#include <time.h>
19#include <unistd.h>
20
21#define SNOW_O_CLOEXEC 0x01000000 // 10.7
22#define SNOW_F_DUPFD_CLOEXEC 67 // 10.7
23#define SNOW_AT_FDCWD -2
24#define SNOW_AT_SYMLINK_NOFOLLOW 0x0020
25#define SNOW_AT_REMOVEDIR 0x0080
26
27// The real function this file overrides, from the next image in load order.
28#define REAL(name) \
29 static __typeof__(name) *real_##name; \
30 if (!real_##name) real_##name = (__typeof__(name) *)dlsym(RTLD_NEXT, #name)
31
32// 10.6's kernel predates O_CLOEXEC; set the flag after the fact.
33int open(const char *path, int flags, ...) {
34 REAL(open);
35 va_list ap;
36 va_start(ap, flags);
37 int mode = (flags & O_CREAT) ? va_arg(ap, int) : 0;
38 va_end(ap);
39 int fd = real_open(path, flags & ~SNOW_O_CLOEXEC, mode);
40 if (fd >= 0 && (flags & SNOW_O_CLOEXEC)) fcntl(fd, F_SETFD, FD_CLOEXEC);
41 return fd;
42}
43
44int fcntl(int fd, int cmd, ...) {
45 REAL(fcntl);
46 va_list ap;
47 va_start(ap, cmd);
48 intptr_t arg = va_arg(ap, intptr_t);
49 va_end(ap);
50 if (cmd == SNOW_F_DUPFD_CLOEXEC) {
51 int copy = real_fcntl(fd, F_DUPFD, arg);
52 if (copy >= 0) real_fcntl(copy, F_SETFD, FD_CLOEXEC);
53 return copy;
54 }
55 return real_fcntl(fd, cmd, arg);
56}
57
58// The *at family arrived in 10.10. Emulated through the directory's path, so a
59// directory renamed mid-walk is not followed as the real calls would.
60static int resolve_at(int dirfd, const char *path, char out[PATH_MAX]) {
61 if (dirfd == SNOW_AT_FDCWD || path[0] == '/') {
62 if (strlcpy(out, path, PATH_MAX) >= PATH_MAX) return errno = ENAMETOOLONG, -1;
63 return 0;
64 }
65 if (fcntl(dirfd, F_GETPATH, out) == -1) return -1;
66 if (strlcat(out, "/", PATH_MAX) >= PATH_MAX || strlcat(out, path, PATH_MAX) >= PATH_MAX)
67 return errno = ENAMETOOLONG, -1;
68 return 0;
69}
70
71int openat(int dirfd, const char *path, int flags, ...) {
72 va_list ap;
73 va_start(ap, flags);
74 int mode = (flags & O_CREAT) ? va_arg(ap, int) : 0;
75 va_end(ap);
76 char full[PATH_MAX];
77 if (resolve_at(dirfd, path, full) == -1) return -1;
78 return open(full, flags, mode);
79}
80
81int unlinkat(int dirfd, const char *path, int flags) {
82 char full[PATH_MAX];
83 if (resolve_at(dirfd, path, full) == -1) return -1;
84 return (flags & SNOW_AT_REMOVEDIR) ? rmdir(full) : unlink(full);
85}
86
87int fstatat(int dirfd, const char *path, struct stat *st, int flags)
88 __asm("_fstatat" __DARWIN_SUF_64_BIT_INO_T);
89int fstatat(int dirfd, const char *path, struct stat *st, int flags) {
90 char full[PATH_MAX];
91 if (resolve_at(dirfd, path, full) == -1) return -1;
92 return (flags & SNOW_AT_SYMLINK_NOFOLLOW) ? lstat(full, st) : stat(full, st);
93}
94
95int linkat(int fromfd, const char *from, int tofd, const char *to, int flags) {
96 char source[PATH_MAX], target[PATH_MAX];
97 (void)flags; // std passes 0
98 if (resolve_at(fromfd, from, source) == -1 || resolve_at(tofd, to, target) == -1) return -1;
99 return link(source, target);
100}
101
102// Clones are APFS-only (10.12); std's fs::copy falls back to fcopyfile on ENOTSUP.
103int fclonefileat(int fd, int dirfd, const char *to, int flags) {
104 (void)fd, (void)dirfd, (void)to, (void)flags;
105 return errno = ENOTSUP, -1;
106}
107
108// Callers keep using `fd` (std's remove_dir_all passes it to openat/unlinkat), so the
109// stream takes over that number rather than closing it.
110// 10.6 packs directory entries 4 bytes apart, and std reads the dirent's 64-bit inode
111// in place, which debug builds check for alignment. A misaligned entry is copied into a
112// per-thread aligned one, as good as the original until the next readdir.
113static pthread_key_t entry_key;
114static pthread_once_t entry_once = PTHREAD_ONCE_INIT;
115static void make_entry_key(void) { pthread_key_create(&entry_key, free); }
116
117struct dirent *readdir(DIR *dir) __asm("_readdir" __DARWIN_SUF_64_BIT_INO_T);
118struct dirent *readdir(DIR *dir) {
119 static __typeof__(readdir) *real_readdir;
120 if (!real_readdir) real_readdir = (__typeof__(readdir) *)dlsym(RTLD_NEXT, "readdir$INODE64");
121 struct dirent *entry = real_readdir(dir);
122 if (!entry || ((uintptr_t)entry & 7) == 0) return entry;
123 pthread_once(&entry_once, make_entry_key);
124 struct dirent *aligned = pthread_getspecific(entry_key);
125 if (!aligned) {
126 aligned = malloc(sizeof *aligned);
127 if (!aligned) return entry;
128 pthread_setspecific(entry_key, aligned);
129 }
130 memcpy(aligned, entry, entry->d_reclen < sizeof *aligned ? entry->d_reclen : sizeof *aligned);
131 return aligned;
132}
133
134DIR *fdopendir(int fd) __asm("_fdopendir" __DARWIN_SUF_64_BIT_INO_T __DARWIN_SUF_UNIX03);
135DIR *fdopendir(int fd) {
136 char path[PATH_MAX];
137 if (fcntl(fd, F_GETPATH, path) == -1) return NULL;
138 DIR *dir = opendir(path);
139 if (!dir) return NULL;
140 if (dup2(dir->__dd_fd, fd) == -1) {
141 closedir(dir);
142 return NULL;
143 }
144 close(dir->__dd_fd);
145 dir->__dd_fd = fd;
146 fcntl(fd, F_SETFD, FD_CLOEXEC);
147 return dir;
148}
149
150// A function only from 10.8; earlier headers define it as this macro.
151#undef dirfd
152int dirfd(DIR *dir) {
153 return dir->__dd_fd;
154}
155
156// clock_gettime arrived in 10.12. std asks for CLOCK_UPTIME_RAW (Instant) and
157// CLOCK_REALTIME (SystemTime).
158int clock_gettime(int clock, struct timespec *ts) {
159 switch (clock) {
160 case 0: { // CLOCK_REALTIME
161 struct timeval tv;
162 gettimeofday(&tv, NULL);
163 ts->tv_sec = tv.tv_sec;
164 ts->tv_nsec = tv.tv_usec * 1000;
165 return 0;
166 }
167 case 4: case 5: case 6: case 8: case 9: { // monotonic and uptime clocks
168 static mach_timebase_info_data_t base;
169 if (!base.denom) mach_timebase_info(&base);
170 uint64_t ns = mach_absolute_time() * base.numer / base.denom;
171 ts->tv_sec = ns / 1000000000;
172 ts->tv_nsec = ns % 1000000000;
173 return 0;
174 }
175 default:
176 errno = EINVAL;
177 return -1;
178 }
179}
180
181static int read_urandom(void *bytes, size_t count) {
182 static int urandom = -1;
183 if (urandom < 0) {
184 int fd = open("/dev/urandom", O_RDONLY | SNOW_O_CLOEXEC);
185 if (fd < 0) return -1;
186 if (!__sync_bool_compare_and_swap(&urandom, -1, fd)) close(fd);
187 }
188 for (uint8_t *p = bytes; count;) {
189 ssize_t n = read(urandom, p, count);
190 if (n < 0 && errno == EINTR) continue;
191 if (n <= 0) return -1;
192 p += n;
193 count -= (size_t)n;
194 }
195 return 0;
196}
197
198// CCRandomGenerateBytes (std) arrived in 10.10, getentropy (getrandom) in 10.12.
199int32_t CCRandomGenerateBytes(void *bytes, size_t count) {
200 return read_urandom(bytes, count);
201}
202
203int getentropy(void *bytes, size_t count) {
204 if (count > 256) return errno = EIO, -1;
205 return read_urandom(bytes, count);
206}
207
208// LLVM emits these for macOS 10.9+, and rustc hands LLVM at least 10.12.
209struct snow_double2 { double sinval, cosval; };
210struct snow_float2 { float sinval, cosval; };
211struct snow_double2 __sincos_stret(double x) { return (struct snow_double2){sin(x), cos(x)}; }
212struct snow_float2 __sincosf_stret(float x) { return (struct snow_float2){sinf(x), cosf(x)}; }
213double __exp10(double x) { return pow(10.0, x); }
214float __exp10f(float x) { return powf(10.0f, x); }