2 * Copyright (c) 2000, 2001, 2004-2010 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 <TargetConditionals.h>
35 #include <SystemConfiguration/SystemConfiguration.h>
36 #include <SystemConfiguration/SCPrivate.h>
37 #include "SCPreferencesInternal.h"
38 #include "SCHelper_client.h"
46 #include <sys/errno.h>
47 #include <sys/mount.h>
48 #include <sys/param.h>
53 __SCPreferencesLock_helper(SCPreferencesRef prefs
, Boolean wait
)
56 SCPreferencesPrivateRef prefsPrivate
= (SCPreferencesPrivateRef
)prefs
;
57 uint32_t status
= kSCStatusOK
;
58 CFDataRef reply
= NULL
;
60 if (prefsPrivate
->helper_port
== MACH_PORT_NULL
) {
61 ok
= __SCPreferencesCreate_helper(prefs
);
67 // have the helper "lock" the prefs
70 ok
= _SCHelperExec(prefsPrivate
->helper_port
,
71 wait
? SCHELPER_MSG_PREFS_LOCKWAIT
: SCHELPER_MSG_PREFS_LOCK
,
72 prefsPrivate
->signature
,
79 if (status
!= kSCStatusOK
) {
83 prefsPrivate
->locked
= TRUE
;
89 if (prefsPrivate
->helper_port
!= MACH_PORT_NULL
) {
90 _SCHelperClose(&prefsPrivate
->helper_port
);
93 status
= kSCStatusAccessError
;
104 createParentDirectory(const char *path
)
111 // get parent directory path
112 if (strlcpy(dir
, path
, sizeof(dir
)) >= sizeof(dir
)) {
117 slash
= strrchr(dir
, '/');
118 if ((slash
== NULL
) || (slash
== dir
)) {
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
134 ret
= mkdir(dir
, mode
);
136 static gid_t group
= -1;
142 struct group
*grpP
= NULL
;
144 if ((getgrnam_r("wheel", &grp
, buf
, sizeof(buf
), &grpP
) == 0) &&
146 group
= grpP
->gr_gid
;
149 CFSTR("SCPreferencesLock getgrnam_r() failed: %s"),
155 if (chown(dir
, -1, group
) == -1) {
157 CFSTR("SCPreferencesLock chown() failed: %s"),
162 if (chmod(dir
, mode
) == -1) {
164 CFSTR("SCPreferencesLock chmod() failed: %s"),
168 if ((slash
== NULL
) || (scan
== dir
)) {
171 } else if ((errno
== ENOENT
) && (scan
== dir
)) {
172 // the initial mkdir (of the full dir path) can fail
174 } else if (errno
== EROFS
) {
176 } else if (errno
!= EEXIST
) {
185 slash
= strchr(scan
+ 1, '/');
189 CFSTR("SCPreferencesLock mkdir() failed: %s"),
196 reportDelay(SCPreferencesRef prefs
, struct timeval
*delay
, Boolean isStale
)
199 SCPreferencesPrivateRef prefsPrivate
= (SCPreferencesPrivateRef
)prefs
;
202 m
= asl_new(ASL_TYPE_MSG
);
203 asl_set(m
, "com.apple.message.domain", "com.apple.SystemConfiguration.SCPreferencesLock");
204 (void) _SC_cfstring_to_cstring(prefsPrivate
->name
, str
, sizeof(str
), kCFStringEncodingUTF8
);
205 asl_set(m
, "com.apple.message.signature", str
);
206 (void) _SC_cfstring_to_cstring(prefsPrivate
->prefsID
, str
, sizeof(str
), kCFStringEncodingUTF8
);
207 asl_set(m
, "com.apple.message.signature2", str
);
208 (void) snprintf(str
, sizeof(str
),
211 delay
->tv_usec
/ 1000);
212 asl_set(m
, "com.apple.message.value", str
);
213 SCLOG(NULL
, m
, ASL_LEVEL_DEBUG
,
214 CFSTR("SCPreferences(%@:%@) lock delayed for %d.%3.3d seconds%s"),
216 prefsPrivate
->prefsID
,
218 delay
->tv_usec
/ 1000,
219 isStale
? " (stale)" : "");
227 has_O_EXLOCK(SCPreferencesPrivateRef prefsPrivate
)
229 #pragma pack(push, 4)
232 vol_capabilities_attr_t capabilities
;
235 struct attrlist attrs
;
238 struct statfs statbuf
;
240 fd
= open(prefsPrivate
->lockPath
, O_WRONLY
|O_CREAT
, 0644);
243 CFSTR("SCPreferencesLock open() failed: %s"),
248 ret
= fstatfs(fd
, &statbuf
);
249 unlink(prefsPrivate
->lockPath
);
253 CFSTR("SCPreferencesLock fstatfs() failed: %s"),
258 bzero(&attrs
, sizeof(attrs
));
259 attrs
.bitmapcount
= ATTR_BIT_MAP_COUNT
;
260 attrs
.volattr
= ATTR_VOL_INFO
| ATTR_VOL_CAPABILITIES
;
261 bzero(&attrbuf
, sizeof(attrbuf
));
262 ret
= getattrlist(statbuf
.f_mntonname
, // path (of mount point)
263 &attrs
, // attribute list
264 &attrbuf
, // attribute buffer
269 CFSTR("SCPreferencesLock getattrlist() failed: %s"),
274 if ((attrbuf
.capabilities
.capabilities
[VOL_CAPABILITIES_INTERFACES
] & VOL_CAP_INT_FLOCK
) &&
275 (attrbuf
.capabilities
.valid
[VOL_CAPABILITIES_INTERFACES
] & VOL_CAP_INT_FLOCK
)) {
284 lockWithSCDynamicStore(SCPreferencesPrivateRef prefsPrivate
, Boolean wait
)
287 Boolean locked
= FALSE
;
289 int sc_status
= kSCStatusOK
;
291 // add [lock] notification
292 ok
= SCDynamicStoreAddWatchedKey(prefsPrivate
->session
,
293 prefsPrivate
->sessionKeyLock
,
296 sc_status
= SCError();
297 SCLog(_sc_verbose
, LOG_ERR
, CFSTR("SCPreferencesLock SCDynamicStoreAddWatchedKey() failed"));
303 // Attempt to acquire the lock
304 value
= CFDateCreate(NULL
, CFAbsoluteTimeGetCurrent());
305 ok
= SCDynamicStoreAddTemporaryValue(prefsPrivate
->session
,
306 prefsPrivate
->sessionKeyLock
,
315 sc_status
= kSCStatusPrefsBusy
;
319 // wait for the lock to be released
320 ok
= SCDynamicStoreNotifyWait(prefsPrivate
->session
);
322 sc_status
= SCError();
323 SCLog(_sc_verbose
, LOG_ERR
, CFSTR("SCPreferencesLock SCDynamicStoreNotifyWait() failed"));
327 // clear out any notifications
328 changes
= SCDynamicStoreCopyNotifiedKeys(prefsPrivate
->session
);
329 if (changes
!= NULL
) {
332 SCLog(_sc_verbose
, LOG_ERR
, CFSTR("SCPreferencesLock SCDynamicStoreCopyNotifiedKeys() failed"));
337 // remove [lock] notification
338 (void) SCDynamicStoreRemoveWatchedKey(prefsPrivate
->session
,
339 prefsPrivate
->sessionKeyLock
,
342 // clear out any notifications
343 changes
= SCDynamicStoreCopyNotifiedKeys(prefsPrivate
->session
);
344 if (changes
!= NULL
) {
348 if (sc_status
!= kSCStatusOK
) {
349 _SCErrorSet(sc_status
);
356 SCPreferencesLock(SCPreferencesRef prefs
, Boolean wait
)
359 struct timeval lockStart
;
360 struct timeval lockElapsed
;
361 SCPreferencesPrivateRef prefsPrivate
= (SCPreferencesPrivateRef
)prefs
;
362 int sc_status
= kSCStatusFailed
;
364 struct stat statBuf2
;
367 /* sorry, you must provide a session */
368 _SCErrorSet(kSCStatusNoPrefsSession
);
372 if (prefsPrivate
->locked
) {
373 /* sorry, you already have the lock */
374 _SCErrorSet(kSCStatusLocked
);
378 if (prefsPrivate
->authorizationData
!= NULL
) {
379 return __SCPreferencesLock_helper(prefs
, wait
);
382 if (!prefsPrivate
->isRoot
) {
383 _SCErrorSet(kSCStatusAccessError
);
388 pthread_mutex_lock(&prefsPrivate
->lock
);
390 if (prefsPrivate
->session
== NULL
) {
391 __SCPreferencesAddSession(prefs
);
394 if (prefsPrivate
->lockPath
== NULL
) {
398 path
= prefsPrivate
->newPath
? prefsPrivate
->newPath
: prefsPrivate
->path
;
399 pathLen
= strlen(path
) + sizeof("-lock");
400 prefsPrivate
->lockPath
= CFAllocatorAllocate(NULL
, pathLen
, 0);
401 snprintf(prefsPrivate
->lockPath
, pathLen
, "%s-lock", path
);
404 (void)gettimeofday(&lockStart
, NULL
);
408 if (prefsPrivate
->sessionKeyLock
!= NULL
) {
409 if (lockWithSCDynamicStore(prefsPrivate
, wait
)) {
416 prefsPrivate
->lockFD
= open(prefsPrivate
->lockPath
,
417 wait
? O_WRONLY
|O_CREAT
|O_EXLOCK
418 : O_WRONLY
|O_CREAT
|O_EXLOCK
|O_NONBLOCK
,
420 if (prefsPrivate
->lockFD
== -1) {
423 if ((prefsPrivate
->prefsID
== NULL
) ||
424 !CFStringHasPrefix(prefsPrivate
->prefsID
, CFSTR("/"))) {
427 // create parent (/Library/Preferences/SystemConfiguration)
428 ret
= createParentDirectory(prefsPrivate
->lockPath
);
430 SCLog(TRUE
, LOG_NOTICE
,
431 CFSTR("created directory for \"%s\""),
432 prefsPrivate
->newPath
? prefsPrivate
->newPath
: prefsPrivate
->path
);
434 } else if (errno
== EROFS
) {
440 // if read-only filesystem
443 // if already locked (and we are not blocking)
444 sc_status
= kSCStatusPrefsBusy
;
447 if (!has_O_EXLOCK(prefsPrivate
)) {
448 // O_EXLOCK *not* available, use SCDynamicStore
449 prefsPrivate
->sessionKeyLock
= _SCPNotificationKey(NULL
,
450 prefsPrivate
->prefsID
,
451 kSCPreferencesKeyLock
);
462 CFSTR("SCPreferencesLock open() failed: %s"),
467 if ((stat(prefsPrivate
->lockPath
, &statBuf
) == -1) ||
468 (fstat(prefsPrivate
->lockFD
, &statBuf2
) == -1) ||
469 (statBuf
.st_dev
!= statBuf2
.st_dev
) ||
470 (statBuf
.st_ino
!= statBuf2
.st_ino
)) {
471 // if the lock file was unlinked or re-created
472 close(prefsPrivate
->lockFD
);
473 prefsPrivate
->lockFD
= -1;
479 snprintf(buf
, sizeof(buf
), "%d\n", getpid());
480 write(prefsPrivate
->lockFD
, buf
, strlen(buf
));
484 (void)gettimeofday(&prefsPrivate
->lockTime
, NULL
);
485 timersub(&prefsPrivate
->lockTime
, &lockStart
, &lockElapsed
);
487 if (prefsPrivate
->accessed
) {
488 CFDataRef currentSignature
;
492 * the preferences have been accessed since the
493 * session was created so we need to compare
494 * the signature of the stored preferences.
496 if (stat(prefsPrivate
->path
, &statBuf
) == -1) {
497 if (errno
== ENOENT
) {
498 bzero(&statBuf
, sizeof(statBuf
));
500 SCLog(TRUE
, LOG_DEBUG
,
501 CFSTR("SCPreferencesLock stat() failed: %s"),
507 currentSignature
= __SCPSignatureFromStatbuf(&statBuf
);
508 match
= CFEqual(prefsPrivate
->signature
, currentSignature
);
509 CFRelease(currentSignature
);
512 * the preferences have been updated since the
513 * session was accessed so we've got no choice
514 * but to deny the lock request.
520 // * the file contents have changed but since we
521 // * haven't accessed any of the preference data we
522 // * don't need to return an error. Simply proceed.
526 if (lockElapsed
.tv_sec
> 0) {
527 // if we waited more than 1 second to acquire the lock
528 reportDelay(prefs
, &lockElapsed
, FALSE
);
531 prefsPrivate
->locked
= TRUE
;
532 pthread_mutex_unlock(&prefsPrivate
->lock
);
537 sc_status
= kSCStatusStale
;
538 unlink(prefsPrivate
->lockPath
);
540 if (lockElapsed
.tv_sec
> 0) {
541 // if we waited more than 1 second to acquire the lock
542 reportDelay(prefs
, &lockElapsed
, TRUE
);
547 if (prefsPrivate
->lockFD
!= -1) {
548 close(prefsPrivate
->lockFD
);
549 prefsPrivate
->lockFD
= -1;
552 pthread_mutex_unlock(&prefsPrivate
->lock
);
553 _SCErrorSet(sc_status
);