]> git.saurik.com Git - apple/configd.git/blob - SystemConfiguration.fproj/SCPLock.c
configd-802.20.7.tar.gz
[apple/configd.git] / SystemConfiguration.fproj / SCPLock.c
1 /*
2 * Copyright (c) 2000-2010, 2013, 2015 Apple 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 * Modification History
26 *
27 * June 1, 2001 Allan Nathanson <ajn@apple.com>
28 * - public API conversion
29 *
30 * November 9, 2000 Allan Nathanson <ajn@apple.com>
31 * - initial revision
32 */
33
34 #include <TargetConditionals.h>
35 #include <SystemConfiguration/SystemConfiguration.h>
36 #include <SystemConfiguration/SCPrivate.h>
37 #include "SCPreferencesInternal.h"
38 #include "SCHelper_client.h"
39
40 #include <grp.h>
41 #include <fcntl.h>
42 #include <stdio.h>
43 #include <unistd.h>
44 #include <pthread.h>
45 #include <sys/attr.h>
46 #include <sys/errno.h>
47 #include <sys/mount.h>
48 #include <sys/param.h>
49
50
51
52 static Boolean
53 __SCPreferencesLock_helper(SCPreferencesRef prefs, Boolean wait)
54 {
55 Boolean ok;
56 SCPreferencesPrivateRef prefsPrivate = (SCPreferencesPrivateRef)prefs;
57 uint32_t status = kSCStatusOK;
58 CFDataRef reply = NULL;
59
60 if (prefsPrivate->helper_port == MACH_PORT_NULL) {
61 ok = __SCPreferencesCreate_helper(prefs);
62 if (!ok) {
63 return FALSE;
64 }
65 }
66
67 // have the helper "lock" the prefs
68 status = kSCStatusOK;
69 reply = NULL;
70 ok = _SCHelperExec(prefsPrivate->helper_port,
71 wait ? SCHELPER_MSG_PREFS_LOCKWAIT : SCHELPER_MSG_PREFS_LOCK,
72 prefsPrivate->signature,
73 &status,
74 NULL);
75 if (!ok) {
76 goto fail;
77 }
78
79 if (status != kSCStatusOK) {
80 goto error;
81 }
82
83 prefsPrivate->locked = TRUE;
84 return TRUE;
85
86 fail :
87
88 // close helper
89 if (prefsPrivate->helper_port != MACH_PORT_NULL) {
90 _SCHelperClose(&prefsPrivate->helper_port);
91 }
92
93 status = kSCStatusAccessError;
94
95 error :
96
97 // return error
98 _SCErrorSet(status);
99 return FALSE;
100 }
101
102
103 static int
104 createParentDirectory(const char *path)
105 {
106 char dir[PATH_MAX];
107 int ret;
108 char *scan;
109 char *slash;
110
111 // get parent directory path
112 if (strlcpy(dir, path, sizeof(dir)) >= sizeof(dir)) {
113 errno = ENOENT;
114 return -1;
115 }
116
117 slash = strrchr(dir, '/');
118 if ((slash == NULL) || (slash == dir)) {
119 errno = ENOENT;
120 return -1;
121 }
122 *slash = '\0';
123
124 // create parent directories
125 for (scan = dir; TRUE; scan = slash) {
126 mode_t mode = S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH; // 755
127 char sep = '\0';
128
129 if (slash != NULL) {
130 sep = *slash;
131 *slash = '\0';
132 }
133
134 ret = mkdir(dir, mode);
135 if (ret == 0) {
136 static gid_t group = -1;
137
138 // set group
139 if (group == -1) {
140 char buf[256];
141 struct group grp;
142 struct group *grpP = NULL;
143
144 if ((getgrnam_r("wheel", &grp, buf, sizeof(buf), &grpP) == 0) &&
145 (grpP != NULL)) {
146 group = grpP->gr_gid;
147 } else {
148 SC_log(LOG_NOTICE, "getgrnam_r() failed: %s", strerror(errno));
149 group = 0; // wheel
150 }
151 }
152
153 if (chown(dir, -1, group) == -1) {
154 SC_log(LOG_NOTICE, "chown() failed: %s", strerror(errno));
155 }
156
157 // set [force] mode
158 if (chmod(dir, mode) == -1) {
159 SC_log(LOG_NOTICE, "chmod() failed: %s", strerror(errno));
160 }
161
162 if ((slash == NULL) || (scan == dir)) {
163 return 0;
164 }
165 } else if ((errno == ENOENT) && (scan == dir)) {
166 // the initial mkdir (of the full dir path) can fail
167 ;
168 } else if (errno == EROFS) {
169 return -1;
170 } else if (errno != EEXIST) {
171 break;
172 }
173
174 if (slash != NULL) {
175 *slash = sep;
176 } else {
177 break;
178 }
179 slash = strchr(scan + 1, '/');
180 }
181
182 SC_log(LOG_NOTICE, "mkdir() failed: %s", strerror(errno));
183 return -1;
184 }
185
186
187 static void
188 reportDelay(SCPreferencesRef prefs, struct timeval *delay, Boolean isStale)
189 {
190 asl_object_t m;
191 SCPreferencesPrivateRef prefsPrivate = (SCPreferencesPrivateRef)prefs;
192 char str[256];
193
194 m = asl_new(ASL_TYPE_MSG);
195 asl_set(m, "com.apple.message.domain", "com.apple.SystemConfiguration.SCPreferencesLock");
196 (void) _SC_cfstring_to_cstring(prefsPrivate->name, str, sizeof(str), kCFStringEncodingUTF8);
197 asl_set(m, "com.apple.message.signature", str);
198 (void) _SC_cfstring_to_cstring(prefsPrivate->prefsID, str, sizeof(str), kCFStringEncodingUTF8);
199 asl_set(m, "com.apple.message.signature2", str);
200 (void) snprintf(str, sizeof(str),
201 "%d.%3.3d",
202 (int)delay->tv_sec,
203 delay->tv_usec / 1000);
204 asl_set(m, "com.apple.message.value", str);
205 SCLOG(NULL, m, ASL_LEVEL_DEBUG,
206 CFSTR("SCPreferences(%@:%@) lock delayed for %d.%3.3d seconds%s"),
207 prefsPrivate->name,
208 prefsPrivate->prefsID,
209 (int)delay->tv_sec,
210 delay->tv_usec / 1000,
211 isStale ? " (stale)" : "");
212 asl_release(m);
213
214 return;
215 }
216
217
218 static Boolean
219 has_O_EXLOCK(SCPreferencesPrivateRef prefsPrivate)
220 {
221 #pragma pack(push, 4)
222 struct {
223 u_int32_t size;
224 vol_capabilities_attr_t capabilities;
225 } attrbuf;
226 #pragma pack(pop)
227 struct attrlist attrs;
228 int fd;
229 int ret;
230 struct statfs statbuf;
231
232 fd = open(prefsPrivate->lockPath, O_WRONLY|O_CREAT, 0644);
233 if (fd == -1) {
234 SC_log(LOG_NOTICE, "open() failed: %s", strerror(errno));
235 return FALSE;
236 }
237
238 ret = fstatfs(fd, &statbuf);
239 unlink(prefsPrivate->lockPath);
240 close(fd);
241 if (ret == -1) {
242 SC_log(LOG_NOTICE, "fstatfs() failed: %s", strerror(errno));
243 return FALSE;
244 }
245
246 bzero(&attrs, sizeof(attrs));
247 attrs.bitmapcount = ATTR_BIT_MAP_COUNT;
248 attrs.volattr = ATTR_VOL_INFO | ATTR_VOL_CAPABILITIES;
249 bzero(&attrbuf, sizeof(attrbuf));
250 ret = getattrlist(statbuf.f_mntonname, // path (of mount point)
251 &attrs, // attribute list
252 &attrbuf, // attribute buffer
253 sizeof(attrbuf),
254 0); // options
255 if (ret == -1) {
256 SC_log(LOG_NOTICE, "getattrlist() failed: %s", strerror(errno));
257 return FALSE;
258 }
259
260 if ((attrbuf.capabilities.capabilities[VOL_CAPABILITIES_INTERFACES] & VOL_CAP_INT_FLOCK) &&
261 (attrbuf.capabilities.valid [VOL_CAPABILITIES_INTERFACES] & VOL_CAP_INT_FLOCK)) {
262 return TRUE;
263 }
264
265 return FALSE;
266 }
267
268
269 static Boolean
270 lockWithSCDynamicStore(SCPreferencesRef prefs, Boolean wait)
271 {
272 CFArrayRef changes;
273 Boolean locked = FALSE;
274 Boolean ok;
275 SCPreferencesPrivateRef prefsPrivate = (SCPreferencesPrivateRef)prefs;
276 int sc_status = kSCStatusOK;
277
278 // add SCDynamicStore session (for lock monitoring)
279 ok = __SCPreferencesAddSession(prefs);
280 if (!ok) {
281 return FALSE;
282 }
283
284 // add [lock] notification
285 ok = SCDynamicStoreAddWatchedKey(prefsPrivate->session,
286 prefsPrivate->sessionKeyLock,
287 FALSE);
288 if (!ok) {
289 sc_status = SCError();
290 SC_log(LOG_INFO, "SCDynamicStoreAddWatchedKey() failed");
291 }
292
293 // add SCDynamicStore session (for the actual lock)
294 if (ok) {
295 prefsPrivate->sessionNoO_EXLOCK = SCDynamicStoreCreate(NULL, prefsPrivate->name, NULL, NULL);
296 if (prefsPrivate->sessionNoO_EXLOCK == NULL) {
297 sc_status = SCError();
298 SC_log(LOG_INFO, "SCDynamicStoreCreate() failed");
299 ok = FALSE;
300 }
301 }
302
303 while (ok) {
304 CFDateRef value;
305
306 // Attempt to acquire the lock
307 value = CFDateCreate(NULL, CFAbsoluteTimeGetCurrent());
308 ok = SCDynamicStoreAddTemporaryValue(prefsPrivate->sessionNoO_EXLOCK,
309 prefsPrivate->sessionKeyLock,
310 value);
311 CFRelease(value);
312 if (ok) {
313 locked = TRUE;
314 break;
315 }
316
317 if (!wait) {
318 sc_status = kSCStatusPrefsBusy;
319 break;
320 }
321
322 // wait for the lock to be released
323 ok = SCDynamicStoreNotifyWait(prefsPrivate->session);
324 if (!ok) {
325 sc_status = SCError();
326 SC_log(LOG_INFO, "SCDynamicStoreNotifyWait() failed");
327 break;
328 }
329
330 // clear out any notifications
331 changes = SCDynamicStoreCopyNotifiedKeys(prefsPrivate->session);
332 if (changes != NULL) {
333 CFRelease(changes);
334 } else {
335 SC_log(LOG_INFO, "SCDynamicStoreCopyNotifiedKeys() failed");
336 break;
337 }
338 }
339
340 // remove [lock] notification
341 (void) SCDynamicStoreRemoveWatchedKey(prefsPrivate->session,
342 prefsPrivate->sessionKeyLock,
343 0);
344
345 // clear out any notifications
346 changes = SCDynamicStoreCopyNotifiedKeys(prefsPrivate->session);
347 if (changes != NULL) {
348 CFRelease(changes);
349 }
350
351 __SCPreferencesRemoveSession(prefs);
352
353 if (!locked && (prefsPrivate->sessionNoO_EXLOCK != NULL)) {
354 CFRelease(prefsPrivate->sessionNoO_EXLOCK);
355 prefsPrivate->sessionNoO_EXLOCK = NULL;
356 }
357
358 if (sc_status != kSCStatusOK) {
359 _SCErrorSet(sc_status);
360 }
361
362 return locked;
363 }
364
365
366 Boolean
367 SCPreferencesLock(SCPreferencesRef prefs, Boolean wait)
368 {
369 char buf[32];
370 struct timeval lockStart;
371 struct timeval lockElapsed;
372 SCPreferencesPrivateRef prefsPrivate = (SCPreferencesPrivateRef)prefs;
373 int sc_status = kSCStatusFailed;
374 struct stat statBuf;
375 struct stat statBuf2;
376
377 if (prefs == NULL) {
378 /* sorry, you must provide a session */
379 _SCErrorSet(kSCStatusNoPrefsSession);
380 return FALSE;
381 }
382
383 if (prefsPrivate->locked) {
384 /* sorry, you already have the lock */
385 _SCErrorSet(kSCStatusLocked);
386 return FALSE;
387 }
388
389 if (prefsPrivate->authorizationData != NULL) {
390 return __SCPreferencesLock_helper(prefs, wait);
391 }
392
393 if (!prefsPrivate->isRoot) {
394 _SCErrorSet(kSCStatusAccessError);
395 return FALSE;
396 }
397
398
399 pthread_mutex_lock(&prefsPrivate->lock);
400
401 __SCPreferencesAddSessionKeys(prefs);
402
403 if (prefsPrivate->lockPath == NULL) {
404 char *path;
405 CFIndex pathLen;
406
407 path = prefsPrivate->newPath ? prefsPrivate->newPath : prefsPrivate->path;
408 pathLen = strlen(path) + sizeof("-lock");
409 prefsPrivate->lockPath = CFAllocatorAllocate(NULL, pathLen, 0);
410 snprintf(prefsPrivate->lockPath, pathLen, "%s-lock", path);
411 }
412
413 (void)gettimeofday(&lockStart, NULL);
414
415 retry :
416
417 if (prefsPrivate->sessionKeyLock != NULL) {
418 if (lockWithSCDynamicStore(prefs, wait)) {
419 goto locked;
420 }
421
422 goto error;
423 }
424
425 prefsPrivate->lockFD = open(prefsPrivate->lockPath,
426 wait ? O_WRONLY|O_CREAT|O_EXLOCK
427 : O_WRONLY|O_CREAT|O_EXLOCK|O_NONBLOCK,
428 0644);
429 if (prefsPrivate->lockFD == -1) {
430 switch (errno) {
431 case ENOENT :
432 if ((prefsPrivate->prefsID == NULL) ||
433 !CFStringHasPrefix(prefsPrivate->prefsID, CFSTR("/"))) {
434 int ret;
435
436 // create parent (/Library/Preferences/SystemConfiguration)
437 ret = createParentDirectory(prefsPrivate->lockPath);
438 if (ret == 0) {
439 SC_log(LOG_INFO, "created directory for \"%s\"",
440 prefsPrivate->newPath ? prefsPrivate->newPath : prefsPrivate->path);
441 goto retry;
442 } else if (errno == EROFS) {
443 goto locked;
444 }
445 }
446 break;
447 case EROFS :
448 // if read-only filesystem
449 goto locked;
450 case EWOULDBLOCK :
451 // if already locked (and we are not blocking)
452 sc_status = kSCStatusPrefsBusy;
453 goto error;
454 case ENOTSUP :
455 if (!has_O_EXLOCK(prefsPrivate)) {
456 // O_EXLOCK *not* available, use SCDynamicStore
457 prefsPrivate->sessionKeyLock = _SCPNotificationKey(NULL,
458 prefsPrivate->prefsID,
459 kSCPreferencesKeyLock);
460 goto retry;
461 }
462 errno = ENOTSUP;
463 break;
464 default :
465 break;
466 }
467
468 sc_status = errno;
469 SC_log(LOG_NOTICE, "open() failed: %s", strerror(errno));
470 goto error;
471 }
472
473 if ((stat(prefsPrivate->lockPath, &statBuf) == -1) ||
474 (fstat(prefsPrivate->lockFD, &statBuf2) == -1) ||
475 (statBuf.st_dev != statBuf2.st_dev) ||
476 (statBuf.st_ino != statBuf2.st_ino)) {
477 // if the lock file was unlinked or re-created
478 close(prefsPrivate->lockFD);
479 prefsPrivate->lockFD = -1;
480 goto retry;
481 }
482
483 // we have the lock
484
485 snprintf(buf, sizeof(buf), "%d\n", getpid());
486 write(prefsPrivate->lockFD, buf, strlen(buf));
487
488 locked :
489
490 (void)gettimeofday(&prefsPrivate->lockTime, NULL);
491 timersub(&prefsPrivate->lockTime, &lockStart, &lockElapsed);
492
493 if (prefsPrivate->accessed) {
494 CFDataRef currentSignature;
495 Boolean match;
496
497 /*
498 * the preferences have been accessed since the
499 * session was created so we need to compare
500 * the signature of the stored preferences.
501 */
502 if (stat(prefsPrivate->path, &statBuf) == -1) {
503 if (errno == ENOENT) {
504 bzero(&statBuf, sizeof(statBuf));
505 } else {
506 SC_log(LOG_INFO, "stat() failed: %s",
507 strerror(errno));
508 goto stale;
509 }
510 }
511
512 currentSignature = __SCPSignatureFromStatbuf(&statBuf);
513 match = CFEqual(prefsPrivate->signature, currentSignature);
514 CFRelease(currentSignature);
515 if (!match) {
516 /*
517 * the preferences have been updated since the
518 * session was accessed so we've got no choice
519 * but to deny the lock request.
520 */
521 goto stale;
522 }
523 // } else {
524 // /*
525 // * the file contents have changed but since we
526 // * haven't accessed any of the preference data we
527 // * don't need to return an error. Simply proceed.
528 // */
529 }
530
531 if (lockElapsed.tv_sec > 0) {
532 // if we waited more than 1 second to acquire the lock
533 reportDelay(prefs, &lockElapsed, FALSE);
534 }
535
536 prefsPrivate->locked = TRUE;
537 pthread_mutex_unlock(&prefsPrivate->lock);
538 return TRUE;
539
540 stale :
541
542 sc_status = kSCStatusStale;
543 unlink(prefsPrivate->lockPath);
544
545 if (lockElapsed.tv_sec > 0) {
546 // if we waited more than 1 second to acquire the lock
547 reportDelay(prefs, &lockElapsed, TRUE);
548 }
549
550 error :
551
552 if (prefsPrivate->lockFD != -1) {
553 close(prefsPrivate->lockFD);
554 prefsPrivate->lockFD = -1;
555 }
556
557 pthread_mutex_unlock(&prefsPrivate->lock);
558 _SCErrorSet(sc_status);
559 return FALSE;
560 }