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>
48 __SCPreferencesLock_helper(SCPreferencesRef prefs
, Boolean wait
)
51 SCPreferencesPrivateRef prefsPrivate
= (SCPreferencesPrivateRef
)prefs
;
52 uint32_t status
= kSCStatusOK
;
53 CFDataRef reply
= NULL
;
55 if (prefsPrivate
->helper_port
== MACH_PORT_NULL
) {
56 ok
= __SCPreferencesCreate_helper(prefs
);
62 // have the helper "lock" the prefs
65 ok
= _SCHelperExec(prefsPrivate
->helper_port
,
66 wait
? SCHELPER_MSG_PREFS_LOCKWAIT
: SCHELPER_MSG_PREFS_LOCK
,
67 prefsPrivate
->signature
,
74 if (status
!= kSCStatusOK
) {
78 prefsPrivate
->locked
= TRUE
;
84 if (prefsPrivate
->helper_port
!= MACH_PORT_NULL
) {
85 _SCHelperClose(&prefsPrivate
->helper_port
);
88 status
= kSCStatusAccessError
;
99 createParentDirectory(const char *path
)
106 // get parent directory path
107 if (strlcpy(dir
, path
, sizeof(dir
)) >= sizeof(dir
)) {
112 slash
= strrchr(dir
, '/');
113 if ((slash
== NULL
) || (slash
== dir
)) {
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
129 ret
= mkdir(dir
, mode
);
131 static gid_t group
= -1;
134 if (group
== (gid_t
)-1) {
137 struct group
*grpP
= NULL
;
139 if ((getgrnam_r("wheel", &grp
, buf
, sizeof(buf
), &grpP
) == 0) &&
141 group
= grpP
->gr_gid
;
143 SC_log(LOG_NOTICE
, "getgrnam_r() failed: %s", strerror(errno
));
148 if (chown(dir
, -1, group
) == -1) {
149 SC_log(LOG_NOTICE
, "chown() failed: %s", strerror(errno
));
153 if (chmod(dir
, mode
) == -1) {
154 SC_log(LOG_NOTICE
, "chmod() failed: %s", strerror(errno
));
157 if ((slash
== NULL
) || (scan
== dir
)) {
160 } else if ((errno
== ENOENT
) && (scan
== dir
)) {
161 // the initial mkdir (of the full dir path) can fail
163 } else if (errno
== EROFS
) {
165 } else if (errno
!= EEXIST
) {
174 slash
= strchr(scan
+ 1, '/');
177 SC_log(LOG_NOTICE
, "mkdir() failed: %s", strerror(errno
));
183 reportDelay(SCPreferencesRef prefs
, struct timeval
*delay
, Boolean isStale
)
185 SCPreferencesPrivateRef prefsPrivate
= (SCPreferencesPrivateRef
)prefs
;
188 "SCPreferences(%@:%@) lock delayed for %d.%3.3d seconds%s",
190 prefsPrivate
->prefsID
,
192 delay
->tv_usec
/ 1000,
193 isStale
? " (stale)" : "");
199 has_O_EXLOCK(SCPreferencesPrivateRef prefsPrivate
)
201 #pragma pack(push, 4)
204 vol_capabilities_attr_t capabilities
;
207 struct attrlist attrs
;
210 struct statfs statbuf
;
212 fd
= open(prefsPrivate
->lockPath
, O_WRONLY
|O_CREAT
, 0644);
214 SC_log(LOG_NOTICE
, "open() failed: %s", strerror(errno
));
218 ret
= fstatfs(fd
, &statbuf
);
219 unlink(prefsPrivate
->lockPath
);
222 SC_log(LOG_NOTICE
, "fstatfs() failed: %s", strerror(errno
));
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
236 SC_log(LOG_NOTICE
, "getattrlist() failed: %s", strerror(errno
));
240 if ((attrbuf
.capabilities
.capabilities
[VOL_CAPABILITIES_INTERFACES
] & VOL_CAP_INT_FLOCK
) &&
241 (attrbuf
.capabilities
.valid
[VOL_CAPABILITIES_INTERFACES
] & VOL_CAP_INT_FLOCK
)) {
250 lockWithSCDynamicStore(SCPreferencesRef prefs
, Boolean wait
)
253 Boolean locked
= FALSE
;
255 SCPreferencesPrivateRef prefsPrivate
= (SCPreferencesPrivateRef
)prefs
;
256 int sc_status
= kSCStatusOK
;
258 // add SCDynamicStore session (for lock monitoring)
259 ok
= __SCPreferencesAddSession(prefs
);
264 // add [lock] notification
265 ok
= SCDynamicStoreAddWatchedKey(prefsPrivate
->session
,
266 prefsPrivate
->sessionKeyLock
,
269 sc_status
= SCError();
270 SC_log(LOG_INFO
, "SCDynamicStoreAddWatchedKey() failed");
273 // add SCDynamicStore session (for the actual lock)
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");
286 // Attempt to acquire the lock
287 value
= CFDateCreate(NULL
, CFAbsoluteTimeGetCurrent());
288 ok
= SCDynamicStoreAddTemporaryValue(prefsPrivate
->sessionNoO_EXLOCK
,
289 prefsPrivate
->sessionKeyLock
,
298 sc_status
= kSCStatusPrefsBusy
;
302 // wait for the lock to be released
303 ok
= SCDynamicStoreNotifyWait(prefsPrivate
->session
);
305 sc_status
= SCError();
306 SC_log(LOG_INFO
, "SCDynamicStoreNotifyWait() failed");
310 // clear out any notifications
311 changes
= SCDynamicStoreCopyNotifiedKeys(prefsPrivate
->session
);
312 if (changes
!= NULL
) {
315 SC_log(LOG_INFO
, "SCDynamicStoreCopyNotifiedKeys() failed");
320 // remove [lock] notification
321 (void) SCDynamicStoreRemoveWatchedKey(prefsPrivate
->session
,
322 prefsPrivate
->sessionKeyLock
,
325 // clear out any notifications
326 changes
= SCDynamicStoreCopyNotifiedKeys(prefsPrivate
->session
);
327 if (changes
!= NULL
) {
331 __SCPreferencesRemoveSession(prefs
);
333 if (!locked
&& (prefsPrivate
->sessionNoO_EXLOCK
!= NULL
)) {
334 CFRelease(prefsPrivate
->sessionNoO_EXLOCK
);
335 prefsPrivate
->sessionNoO_EXLOCK
= NULL
;
338 if (sc_status
!= kSCStatusOK
) {
339 _SCErrorSet(sc_status
);
347 SCPreferencesLock(SCPreferencesRef prefs
, Boolean wait
)
350 struct timeval lockStart
;
351 struct timeval lockElapsed
;
352 SCPreferencesPrivateRef prefsPrivate
= (SCPreferencesPrivateRef
)prefs
;
353 int sc_status
= kSCStatusFailed
;
355 struct stat statBuf2
;
358 /* sorry, you must provide a session */
359 _SCErrorSet(kSCStatusNoPrefsSession
);
363 if (prefsPrivate
->locked
) {
364 /* sorry, you already have the lock */
365 _SCErrorSet(kSCStatusLocked
);
369 if (prefsPrivate
->authorizationData
!= NULL
) {
370 return __SCPreferencesLock_helper(prefs
, wait
);
373 if (!prefsPrivate
->isRoot
) {
374 _SCErrorSet(kSCStatusAccessError
);
378 pthread_mutex_lock(&prefsPrivate
->lock
);
380 __SCPreferencesAddSessionKeys(prefs
);
382 if (prefsPrivate
->lockPath
== NULL
) {
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
);
392 (void)gettimeofday(&lockStart
, NULL
);
396 if (prefsPrivate
->sessionKeyLock
!= NULL
) {
397 if (lockWithSCDynamicStore(prefs
, wait
)) {
404 prefsPrivate
->lockFD
= open(prefsPrivate
->lockPath
,
405 wait
? O_WRONLY
|O_CREAT
|O_EXLOCK
406 : O_WRONLY
|O_CREAT
|O_EXLOCK
|O_NONBLOCK
,
408 if (prefsPrivate
->lockFD
== -1) {
411 if ((prefsPrivate
->prefsID
== NULL
) ||
412 !CFStringHasPrefix(prefsPrivate
->prefsID
, CFSTR("/"))) {
415 // create parent (/Library/Preferences/SystemConfiguration)
416 ret
= createParentDirectory(prefsPrivate
->lockPath
);
418 SC_log(LOG_INFO
, "created directory for \"%s\"",
419 prefsPrivate
->newPath
? prefsPrivate
->newPath
: prefsPrivate
->path
);
421 } else if (errno
== EROFS
) {
427 // if read-only filesystem
430 // if already locked (and we are not blocking)
431 sc_status
= kSCStatusPrefsBusy
;
434 if (!has_O_EXLOCK(prefsPrivate
)) {
435 // O_EXLOCK *not* available, use SCDynamicStore
436 prefsPrivate
->sessionKeyLock
= _SCPNotificationKey(NULL
,
437 prefsPrivate
->prefsID
,
438 kSCPreferencesKeyLock
);
448 SC_log(LOG_NOTICE
, "open() failed: %s", strerror(errno
));
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;
464 snprintf(buf
, sizeof(buf
), "%d\n", getpid());
465 (void) write(prefsPrivate
->lockFD
, buf
, strlen(buf
));
469 (void)gettimeofday(&prefsPrivate
->lockTime
, NULL
);
470 timersub(&prefsPrivate
->lockTime
, &lockStart
, &lockElapsed
);
472 if (prefsPrivate
->accessed
) {
473 CFDataRef currentSignature
;
477 * the preferences have been accessed since the
478 * session was created so we need to compare
479 * the signature of the stored preferences.
481 if (stat(prefsPrivate
->path
, &statBuf
) == -1) {
482 if (errno
== ENOENT
) {
483 bzero(&statBuf
, sizeof(statBuf
));
485 SC_log(LOG_INFO
, "stat() failed: %s",
491 currentSignature
= __SCPSignatureFromStatbuf(&statBuf
);
492 match
= CFEqual(prefsPrivate
->signature
, currentSignature
);
493 CFRelease(currentSignature
);
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.
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.
510 if (lockElapsed
.tv_sec
> 0) {
511 // if we waited more than 1 second to acquire the lock
512 reportDelay(prefs
, &lockElapsed
, FALSE
);
515 SC_log(LOG_DEBUG
, "SCPreferences() lock: %s",
516 prefsPrivate
->newPath
? prefsPrivate
->newPath
: prefsPrivate
->path
);
518 prefsPrivate
->locked
= TRUE
;
519 pthread_mutex_unlock(&prefsPrivate
->lock
);
524 sc_status
= kSCStatusStale
;
525 unlink(prefsPrivate
->lockPath
);
527 if (lockElapsed
.tv_sec
> 0) {
528 // if we waited more than 1 second to acquire the lock
529 reportDelay(prefs
, &lockElapsed
, TRUE
);
534 if (prefsPrivate
->lockFD
!= -1) {
535 close(prefsPrivate
->lockFD
);
536 prefsPrivate
->lockFD
= -1;
539 pthread_mutex_unlock(&prefsPrivate
->lock
);
540 _SCErrorSet(sc_status
);