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