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