2 * Copyright (c) 2000-2010, 2013, 2015-2018 Apple Inc. All rights reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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.
21 * @APPLE_LICENSE_HEADER_END@
25 * Modification History
27 * June 1, 2001 Allan Nathanson <ajn@apple.com>
28 * - public API conversion
30 * November 9, 2000 Allan Nathanson <ajn@apple.com>
34 #include "SCPreferencesInternal.h"
35 #include "SCHelper_client.h"
43 #include <sys/errno.h>
44 #include <sys/mount.h>
45 #include <sys/param.h>
49 __SCPreferencesLock_helper(SCPreferencesRef prefs
, Boolean wait
)
52 SCPreferencesPrivateRef prefsPrivate
= (SCPreferencesPrivateRef
)prefs
;
53 uint32_t status
= kSCStatusOK
;
54 CFDataRef reply
= NULL
;
56 if (prefsPrivate
->helper_port
== MACH_PORT_NULL
) {
57 ok
= __SCPreferencesCreate_helper(prefs
);
63 // have the helper "lock" the prefs
66 ok
= _SCHelperExec(prefsPrivate
->helper_port
,
67 wait
? SCHELPER_MSG_PREFS_LOCKWAIT
: SCHELPER_MSG_PREFS_LOCK
,
68 prefsPrivate
->signature
,
75 if (status
!= kSCStatusOK
) {
79 __SCPreferencesUpdateLockedState(prefs
, TRUE
);
85 if (prefsPrivate
->helper_port
!= MACH_PORT_NULL
) {
86 _SCHelperClose(&prefsPrivate
->helper_port
);
89 status
= kSCStatusAccessError
;
100 createParentDirectory(const char *path
)
107 // get parent directory path
108 if (strlcpy(dir
, path
, sizeof(dir
)) >= sizeof(dir
)) {
113 slash
= strrchr(dir
, '/');
114 if ((slash
== NULL
) || (slash
== dir
)) {
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
130 ret
= mkdir(dir
, mode
);
132 static gid_t group
= -1;
135 if (group
== (gid_t
)-1) {
138 struct group
*grpP
= NULL
;
140 if ((getgrnam_r("wheel", &grp
, buf
, sizeof(buf
), &grpP
) == 0) &&
142 group
= grpP
->gr_gid
;
144 SC_log(LOG_NOTICE
, "getgrnam_r() failed: %s", strerror(errno
));
149 if (chown(dir
, -1, group
) == -1) {
150 SC_log(LOG_NOTICE
, "chown() failed: %s", strerror(errno
));
154 if (chmod(dir
, mode
) == -1) {
155 SC_log(LOG_NOTICE
, "chmod() failed: %s", strerror(errno
));
158 if ((slash
== NULL
) || (scan
== dir
)) {
161 } else if ((errno
== ENOENT
) && (scan
== dir
)) {
162 // the initial mkdir (of the full dir path) can fail
164 } else if (errno
== EROFS
) {
166 } else if (errno
!= EEXIST
) {
175 slash
= strchr(scan
+ 1, '/');
178 SC_log(LOG_NOTICE
, "mkdir() failed: %s", strerror(errno
));
184 reportDelay(SCPreferencesRef prefs
, struct timeval
*delay
, Boolean isStale
)
186 SCPreferencesPrivateRef prefsPrivate
= (SCPreferencesPrivateRef
)prefs
;
189 "SCPreferences(%@:%@) lock delayed for %d.%3.3d seconds%s",
191 prefsPrivate
->prefsID
,
193 delay
->tv_usec
/ 1000,
194 isStale
? " (stale)" : "");
200 has_O_EXLOCK(SCPreferencesPrivateRef prefsPrivate
)
202 #pragma pack(push, 4)
205 vol_capabilities_attr_t capabilities
;
208 struct attrlist attrs
;
211 struct statfs statbuf
;
213 fd
= open(prefsPrivate
->lockPath
, O_WRONLY
|O_CREAT
, 0644);
215 SC_log(LOG_NOTICE
, "open() failed: %s", strerror(errno
));
219 ret
= fstatfs(fd
, &statbuf
);
220 unlink(prefsPrivate
->lockPath
);
223 SC_log(LOG_NOTICE
, "fstatfs() failed: %s", strerror(errno
));
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
237 SC_log(LOG_NOTICE
, "getattrlist() failed: %s", strerror(errno
));
241 if ((attrbuf
.capabilities
.capabilities
[VOL_CAPABILITIES_INTERFACES
] & VOL_CAP_INT_FLOCK
) &&
242 (attrbuf
.capabilities
.valid
[VOL_CAPABILITIES_INTERFACES
] & VOL_CAP_INT_FLOCK
)) {
251 lockWithSCDynamicStore(SCPreferencesRef prefs
, Boolean wait
)
254 Boolean locked
= FALSE
;
256 SCPreferencesPrivateRef prefsPrivate
= (SCPreferencesPrivateRef
)prefs
;
257 int sc_status
= kSCStatusOK
;
259 // add SCDynamicStore session (for lock monitoring)
260 ok
= __SCPreferencesAddSession(prefs
);
265 // add [lock] notification
266 ok
= SCDynamicStoreAddWatchedKey(prefsPrivate
->session
,
267 prefsPrivate
->sessionKeyLock
,
270 sc_status
= SCError();
271 SC_log(LOG_INFO
, "SCDynamicStoreAddWatchedKey() failed");
274 // add SCDynamicStore session (for the actual lock)
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");
287 // Attempt to acquire the lock
288 value
= CFDateCreate(NULL
, CFAbsoluteTimeGetCurrent());
289 ok
= SCDynamicStoreAddTemporaryValue(prefsPrivate
->sessionNoO_EXLOCK
,
290 prefsPrivate
->sessionKeyLock
,
299 sc_status
= kSCStatusPrefsBusy
;
303 // wait for the lock to be released
304 ok
= SCDynamicStoreNotifyWait(prefsPrivate
->session
);
306 sc_status
= SCError();
307 SC_log(LOG_INFO
, "SCDynamicStoreNotifyWait() failed");
311 // clear out any notifications
312 changes
= SCDynamicStoreCopyNotifiedKeys(prefsPrivate
->session
);
313 if (changes
!= NULL
) {
316 SC_log(LOG_INFO
, "SCDynamicStoreCopyNotifiedKeys() failed");
321 // remove [lock] notification
322 (void) SCDynamicStoreRemoveWatchedKey(prefsPrivate
->session
,
323 prefsPrivate
->sessionKeyLock
,
326 // clear out any notifications
327 changes
= SCDynamicStoreCopyNotifiedKeys(prefsPrivate
->session
);
328 if (changes
!= NULL
) {
332 __SCPreferencesRemoveSession(prefs
);
334 if (!locked
&& (prefsPrivate
->sessionNoO_EXLOCK
!= NULL
)) {
335 CFRelease(prefsPrivate
->sessionNoO_EXLOCK
);
336 prefsPrivate
->sessionNoO_EXLOCK
= NULL
;
339 if (sc_status
!= kSCStatusOK
) {
340 _SCErrorSet(sc_status
);
348 SCPreferencesLock(SCPreferencesRef prefs
, Boolean wait
)
351 struct timeval lockStart
;
352 struct timeval lockElapsed
;
353 SCPreferencesPrivateRef prefsPrivate
= (SCPreferencesPrivateRef
)prefs
;
354 int sc_status
= kSCStatusFailed
;
356 struct stat statBuf2
;
359 /* sorry, you must provide a session */
360 _SCErrorSet(kSCStatusNoPrefsSession
);
364 if (prefsPrivate
->locked
) {
365 /* sorry, you already have the lock */
366 _SCErrorSet(kSCStatusLocked
);
370 if (prefsPrivate
->authorizationData
!= NULL
) {
371 return __SCPreferencesLock_helper(prefs
, wait
);
374 if (!prefsPrivate
->isRoot
) {
375 _SCErrorSet(kSCStatusAccessError
);
379 pthread_mutex_lock(&prefsPrivate
->lock
);
381 __SCPreferencesAddSessionKeys(prefs
);
383 if (prefsPrivate
->lockPath
== NULL
) {
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
);
393 (void)gettimeofday(&lockStart
, NULL
);
397 if (prefsPrivate
->sessionKeyLock
!= NULL
) {
398 if (lockWithSCDynamicStore(prefs
, wait
)) {
405 prefsPrivate
->lockFD
= open(prefsPrivate
->lockPath
,
406 wait
? O_WRONLY
|O_CREAT
|O_EXLOCK
407 : O_WRONLY
|O_CREAT
|O_EXLOCK
|O_NONBLOCK
,
409 if (prefsPrivate
->lockFD
== -1) {
412 if ((prefsPrivate
->prefsID
== NULL
) ||
413 !CFStringHasPrefix(prefsPrivate
->prefsID
, CFSTR("/"))) {
416 // create parent (/Library/Preferences/SystemConfiguration)
417 ret
= createParentDirectory(prefsPrivate
->lockPath
);
419 SC_log(LOG_INFO
, "created directory for \"%s\"",
420 prefsPrivate
->newPath
? prefsPrivate
->newPath
: prefsPrivate
->path
);
422 } else if (errno
== EROFS
) {
428 // if read-only filesystem
431 // if already locked (and we are not blocking)
432 sc_status
= kSCStatusPrefsBusy
;
435 if (!has_O_EXLOCK(prefsPrivate
)) {
436 // O_EXLOCK *not* available, use SCDynamicStore
437 prefsPrivate
->sessionKeyLock
= _SCPNotificationKey(NULL
,
438 prefsPrivate
->prefsID
,
439 kSCPreferencesKeyLock
);
449 SC_log(LOG_NOTICE
, "open() failed: %s", strerror(errno
));
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;
465 snprintf(buf
, sizeof(buf
), "%d\n", getpid());
466 (void) write(prefsPrivate
->lockFD
, buf
, strlen(buf
));
470 (void)gettimeofday(&prefsPrivate
->lockTime
, NULL
);
471 timersub(&prefsPrivate
->lockTime
, &lockStart
, &lockElapsed
);
473 if (prefsPrivate
->accessed
) {
474 CFDataRef currentSignature
;
478 * the preferences have been accessed since the
479 * session was created so we need to compare
480 * the signature of the stored preferences.
482 if (stat(prefsPrivate
->path
, &statBuf
) == -1) {
483 if (errno
== ENOENT
) {
484 memset(&statBuf
, 0, sizeof(statBuf
));
486 SC_log(LOG_INFO
, "stat() failed: %s",
492 currentSignature
= __SCPSignatureFromStatbuf(&statBuf
);
493 match
= CFEqual(prefsPrivate
->signature
, currentSignature
);
494 CFRelease(currentSignature
);
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.
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.
511 if (lockElapsed
.tv_sec
> 0) {
512 // if we waited more than 1 second to acquire the lock
513 reportDelay(prefs
, &lockElapsed
, FALSE
);
516 SC_log(LOG_DEBUG
, "SCPreferences() lock: %s",
517 prefsPrivate
->newPath
? prefsPrivate
->newPath
: prefsPrivate
->path
);
519 __SCPreferencesUpdateLockedState(prefs
, TRUE
);
520 pthread_mutex_unlock(&prefsPrivate
->lock
);
525 sc_status
= kSCStatusStale
;
526 unlink(prefsPrivate
->lockPath
);
528 if (lockElapsed
.tv_sec
> 0) {
529 // if we waited more than 1 second to acquire the lock
530 reportDelay(prefs
, &lockElapsed
, TRUE
);
535 if (prefsPrivate
->lockFD
!= -1) {
536 close(prefsPrivate
->lockFD
);
537 prefsPrivate
->lockFD
= -1;
540 pthread_mutex_unlock(&prefsPrivate
->lock
);
541 _SCErrorSet(sc_status
);