1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsProfileLock.h"
8 #include "nsQueryObject.h"
10 #include "nsPrintfCString.h"
14 # include "ProfileUnlockerWin.h"
17 #if defined(XP_MACOSX)
18 # include <Carbon/Carbon.h>
19 # include <CoreFoundation/CoreFoundation.h>
22 #if defined(MOZ_WIDGET_ANDROID)
23 # include "ProfileUnlockerAndroid.h"
33 # include "prsystem.h"
35 # include "mozilla/Printf.h"
38 // **********************************************************************
39 // class nsProfileLock
41 // This code was moved from profile/src/nsProfileAccess.
42 // **********************************************************************
45 static bool sDisableSignalHandling
= false;
48 nsProfileLock::nsProfileLock()
53 mLockFileHandle(INVALID_HANDLE_VALUE
)
54 #elif defined(XP_UNIX)
56 mPidLockFileName(nullptr),
62 sDisableSignalHandling
= PR_GetEnv("MOZ_DISABLE_SIG_HANDLER") ? true : false;
66 nsProfileLock::nsProfileLock(nsProfileLock
& src
) { *this = src
; }
68 nsProfileLock
& nsProfileLock::operator=(nsProfileLock
& rhs
) {
71 mHaveLock
= rhs
.mHaveLock
;
72 rhs
.mHaveLock
= false;
75 mLockFileHandle
= rhs
.mLockFileHandle
;
76 rhs
.mLockFileHandle
= INVALID_HANDLE_VALUE
;
77 #elif defined(XP_UNIX)
78 mLockFileDesc
= rhs
.mLockFileDesc
;
79 rhs
.mLockFileDesc
= -1;
80 mPidLockFileName
= rhs
.mPidLockFileName
;
81 rhs
.mPidLockFileName
= nullptr;
82 if (mPidLockFileName
) {
83 // rhs had a symlink lock, therefore it was on the list.
85 PR_APPEND_LINK(this, &mPidLockList
);
92 nsProfileLock::~nsProfileLock() {
94 // Note that we don't clean up by default here so on next startup we know when
95 // the profile was last used based on the modification time of the lock file.
100 static int setupPidLockCleanup
;
102 PRCList
nsProfileLock::mPidLockList
=
103 PR_INIT_STATIC_CLIST(&nsProfileLock::mPidLockList
);
105 void nsProfileLock::RemovePidLockFiles(bool aFatalSignal
) {
106 while (!PR_CLIST_IS_EMPTY(&mPidLockList
)) {
107 nsProfileLock
* lock
= static_cast<nsProfileLock
*>(mPidLockList
.next
);
108 lock
->Unlock(aFatalSignal
);
112 static struct sigaction SIGHUP_oldact
;
113 static struct sigaction SIGINT_oldact
;
114 static struct sigaction SIGQUIT_oldact
;
115 static struct sigaction SIGILL_oldact
;
116 static struct sigaction SIGABRT_oldact
;
117 static struct sigaction SIGSEGV_oldact
;
118 static struct sigaction SIGTERM_oldact
;
120 void nsProfileLock::FatalSignalHandler(int signo
123 siginfo_t
* info
, void* context
126 // Remove any locks still held.
127 RemovePidLockFiles(true);
129 // Chain to the old handler, which may exit.
130 struct sigaction
* oldact
= nullptr;
134 oldact
= &SIGHUP_oldact
;
137 oldact
= &SIGINT_oldact
;
140 oldact
= &SIGQUIT_oldact
;
143 oldact
= &SIGILL_oldact
;
146 oldact
= &SIGABRT_oldact
;
149 oldact
= &SIGSEGV_oldact
;
152 oldact
= &SIGTERM_oldact
;
155 MOZ_ASSERT_UNREACHABLE("bad signo");
160 if (oldact
->sa_handler
== SIG_DFL
) {
161 // Make sure the default sig handler is executed
162 // We need it to get Mozilla to dump core.
163 sigaction(signo
, oldact
, nullptr);
165 // Now that we've restored the default handler, unmask the
166 // signal and invoke it.
168 sigset_t unblock_sigs
;
169 sigemptyset(&unblock_sigs
);
170 sigaddset(&unblock_sigs
, signo
);
172 sigprocmask(SIG_UNBLOCK
, &unblock_sigs
, nullptr);
177 else if (oldact
->sa_sigaction
&&
178 (oldact
->sa_flags
& SA_SIGINFO
) == SA_SIGINFO
) {
179 oldact
->sa_sigaction(signo
, info
, context
);
182 else if (oldact
->sa_handler
&& oldact
->sa_handler
!= SIG_IGN
) {
183 oldact
->sa_handler(signo
);
187 // Backstop exit call, just in case.
191 nsresult
nsProfileLock::LockWithFcntl(nsIFile
* aLockFile
,
192 nsIProfileUnlocker
** aUnlocker
) {
195 nsAutoCString lockFilePath
;
196 rv
= aLockFile
->GetNativePath(lockFilePath
);
198 NS_ERROR("Could not get native path");
202 aLockFile
->GetLastModifiedTime(&mReplacedLockTime
);
204 mLockFileDesc
= open(lockFilePath
.get(), O_WRONLY
| O_CREAT
| O_TRUNC
, 0666);
205 if (mLockFileDesc
!= -1) {
208 lock
.l_len
= 0; // len = 0 means entire file
209 lock
.l_type
= F_WRLCK
;
210 lock
.l_whence
= SEEK_SET
;
212 // If fcntl(F_GETLK) fails then the server does not support/allow fcntl(),
213 // return failure rather than access denied in this case so we fallback
214 // to using a symlink lock, bug 303633.
215 struct flock testlock
= lock
;
216 if (fcntl(mLockFileDesc
, F_GETLK
, &testlock
) == -1) {
217 close(mLockFileDesc
);
219 rv
= NS_ERROR_FAILURE
;
220 } else if (fcntl(mLockFileDesc
, F_SETLK
, &lock
) == -1) {
221 # ifdef MOZ_WIDGET_ANDROID
222 MOZ_ASSERT(aUnlocker
);
223 RefPtr
<mozilla::ProfileUnlockerAndroid
> unlocker(
224 new mozilla::ProfileUnlockerAndroid(testlock
.l_pid
));
225 nsCOMPtr
<nsIProfileUnlocker
> unlockerInterface(do_QueryObject(unlocker
));
226 unlockerInterface
.forget(aUnlocker
);
229 close(mLockFileDesc
);
232 // With OS X, on NFS, errno == ENOTSUP
233 // XXX Check for that and return specific rv for it?
235 printf("fcntl(F_SETLK) failed. errno = %d\n", errno
);
237 if (errno
== EAGAIN
|| errno
== EACCES
)
238 rv
= NS_ERROR_FILE_ACCESS_DENIED
;
240 rv
= NS_ERROR_FAILURE
;
243 NS_ERROR("Failed to open lock file.");
244 rv
= NS_ERROR_FAILURE
;
249 static bool IsSymlinkStaleLock(struct in_addr
* aAddr
, const char* aFileName
,
250 bool aHaveFcntlLock
) {
251 // the link exists; see if it's from this machine, and if
252 // so if the process is still active
254 int len
= readlink(aFileName
, buf
, sizeof buf
- 1);
257 char* colon
= strchr(buf
, ':');
260 unsigned long addr
= inet_addr(buf
);
261 if (addr
!= (unsigned long)-1) {
262 if (colon
[0] == '+' && aHaveFcntlLock
) {
263 // This lock was placed by a Firefox build which would have
264 // taken the fnctl lock, and we've already taken the fcntl lock,
265 // so the process that created this obsolete lock must be gone
269 char* after
= nullptr;
270 pid_t pid
= strtol(colon
, &after
, 0);
271 if (pid
!= 0 && *after
== '\0') {
272 if (addr
!= aAddr
->s_addr
) {
273 // Remote lock: give up even if stuck.
277 // kill(pid,0) is a neat trick to check if a
279 if (kill(pid
, 0) == 0 || errno
!= ESRCH
) {
280 // Local process appears to be alive, ass-u-me it
281 // is another Mozilla instance, or a compatible
282 // derivative, that's currently using the profile.
283 // XXX need an "are you Mozilla?" protocol
293 nsresult
nsProfileLock::LockWithSymlink(nsIFile
* aLockFile
,
294 bool aHaveFcntlLock
) {
296 nsAutoCString lockFilePath
;
297 rv
= aLockFile
->GetNativePath(lockFilePath
);
299 NS_ERROR("Could not get native path");
303 // don't replace an existing lock time if fcntl already got one
304 if (!mReplacedLockTime
)
305 aLockFile
->GetLastModifiedTimeOfLink(&mReplacedLockTime
);
307 struct in_addr inaddr
;
308 inaddr
.s_addr
= htonl(INADDR_LOOPBACK
);
310 // We still have not loaded the profile, so we may not have proxy information.
311 // Avoiding a DNS lookup in this stage makes sure any proxy is not bypassed.
312 // By default, the lookup is enabled, but when it is not, we use 127.0.0.1
313 // for the IP address portion of the lock signature.
314 // However, this may cause the browser to refuse to start in the rare case
315 // that all of the following conditions are met:
316 // 1. The browser profile is on a network file system.
317 // 2. The file system does not support fcntl() locking.
318 // 3. The browser is run from two different computers at the same time.
319 # ifndef MOZ_PROXY_BYPASS_PROTECTION
321 PRStatus status
= PR_GetSystemInfo(PR_SI_HOSTNAME
, hostname
, sizeof hostname
);
322 if (status
== PR_SUCCESS
) {
323 char netdbbuf
[PR_NETDB_BUF_SIZE
];
325 status
= PR_GetHostByName(hostname
, netdbbuf
, sizeof netdbbuf
, &hostent
);
326 if (status
== PR_SUCCESS
) memcpy(&inaddr
, hostent
.h_addr
, sizeof inaddr
);
330 mozilla::SmprintfPointer signature
=
331 mozilla::Smprintf("%s:%s%lu", inet_ntoa(inaddr
),
332 aHaveFcntlLock
? "+" : "", (unsigned long)getpid());
333 const char* fileName
= lockFilePath
.get();
334 int symlink_rv
, symlink_errno
= 0, tries
= 0;
336 // use ns4.x-compatible symlinks if the FS supports them
337 while ((symlink_rv
= symlink(signature
.get(), fileName
)) < 0) {
338 symlink_errno
= errno
;
339 if (symlink_errno
!= EEXIST
) break;
341 if (!IsSymlinkStaleLock(&inaddr
, fileName
, aHaveFcntlLock
)) break;
343 // Lock seems to be bogus: try to claim it. Give up after a large
344 // number of attempts (100 comes from the 4.x codebase).
345 (void)unlink(fileName
);
346 if (++tries
> 100) break;
349 if (symlink_rv
== 0) {
350 // We exclusively created the symlink: record its name for eventual
351 // unlock-via-unlink.
353 mPidLockFileName
= strdup(fileName
);
354 if (mPidLockFileName
) {
355 PR_APPEND_LINK(this, &mPidLockList
);
356 if (!setupPidLockCleanup
++) {
357 // Clean up on normal termination.
358 // This instanciates a dummy class, and will trigger the class
359 // destructor when libxul is unloaded. This is equivalent to atexit(),
360 // but gracefully handles dlclose().
361 static RemovePidLockFilesExiting r
;
363 // Clean up on abnormal termination, using POSIX sigaction.
364 // Don't arm a handler if the signal is being ignored, e.g.,
365 // because mozilla is run via nohup.
366 if (!sDisableSignalHandling
) {
367 struct sigaction act
, oldact
;
369 act
.sa_sigaction
= FatalSignalHandler
;
370 act
.sa_flags
= SA_SIGINFO
| SA_ONSTACK
;
372 act
.sa_handler
= FatalSignalHandler
;
374 sigfillset(&act
.sa_mask
);
376 # define CATCH_SIGNAL(signame) \
378 if (sigaction(signame, nullptr, &oldact) == 0 && \
379 oldact.sa_handler != SIG_IGN) { \
380 sigaction(signame, &act, &signame##_oldact); \
384 CATCH_SIGNAL(SIGHUP
);
385 CATCH_SIGNAL(SIGINT
);
386 CATCH_SIGNAL(SIGQUIT
);
387 CATCH_SIGNAL(SIGILL
);
388 CATCH_SIGNAL(SIGABRT
);
389 CATCH_SIGNAL(SIGSEGV
);
390 CATCH_SIGNAL(SIGTERM
);
396 } else if (symlink_errno
== EEXIST
)
397 rv
= NS_ERROR_FILE_ACCESS_DENIED
;
400 printf("symlink() failed. errno = %d\n", errno
);
402 rv
= NS_ERROR_FAILURE
;
408 nsresult
nsProfileLock::GetReplacedLockTime(PRTime
* aResult
) {
409 *aResult
= mReplacedLockTime
;
413 #if defined(XP_MACOSX)
414 constexpr auto LOCKFILE_NAME
= u
".parentlock"_ns
;
415 constexpr auto OLD_LOCKFILE_NAME
= u
"parent.lock"_ns
;
416 #elif defined(XP_UNIX)
417 constexpr auto OLD_LOCKFILE_NAME
= u
"lock"_ns
;
418 constexpr auto LOCKFILE_NAME
= u
".parentlock"_ns
;
420 constexpr auto LOCKFILE_NAME
= u
"parent.lock"_ns
;
423 bool nsProfileLock::IsMaybeLockFile(nsIFile
* aFile
) {
425 if (NS_SUCCEEDED(aFile
->GetLeafName(tmp
))) {
426 if (tmp
.Equals(LOCKFILE_NAME
)) return true;
427 #if (defined(XP_MACOSX) || defined(XP_UNIX))
428 if (tmp
.Equals(OLD_LOCKFILE_NAME
)) return true;
434 nsresult
nsProfileLock::Lock(nsIFile
* aProfileDir
,
435 nsIProfileUnlocker
** aUnlocker
) {
437 if (aUnlocker
) *aUnlocker
= nullptr;
439 NS_ENSURE_STATE(!mHaveLock
);
442 rv
= aProfileDir
->IsDirectory(&isDir
);
443 if (NS_FAILED(rv
)) return rv
;
444 if (!isDir
) return NS_ERROR_FILE_NOT_DIRECTORY
;
446 nsCOMPtr
<nsIFile
> lockFile
;
447 rv
= aProfileDir
->Clone(getter_AddRefs(lockFile
));
448 if (NS_FAILED(rv
)) return rv
;
450 rv
= lockFile
->Append(LOCKFILE_NAME
);
451 if (NS_FAILED(rv
)) return rv
;
453 // Remember the name we're using so we can clean up
454 rv
= lockFile
->Clone(getter_AddRefs(mLockFile
));
455 if (NS_FAILED(rv
)) return rv
;
457 #if defined(XP_MACOSX)
458 // First, try locking using fcntl. It is more reliable on
459 // a local machine, but may not be supported by an NFS server.
461 rv
= LockWithFcntl(lockFile
);
462 if (NS_FAILED(rv
) && (rv
!= NS_ERROR_FILE_ACCESS_DENIED
)) {
463 // If that failed for any reason other than NS_ERROR_FILE_ACCESS_DENIED,
464 // assume we tried an NFS that does not support it. Now, try with symlink.
465 rv
= LockWithSymlink(lockFile
, false);
467 #elif defined(XP_UNIX)
468 // Get the old lockfile name
469 nsCOMPtr
<nsIFile
> oldLockFile
;
470 rv
= aProfileDir
->Clone(getter_AddRefs(oldLockFile
));
471 if (NS_FAILED(rv
)) return rv
;
472 rv
= oldLockFile
->Append(OLD_LOCKFILE_NAME
);
473 if (NS_FAILED(rv
)) return rv
;
475 // First, try locking using fcntl. It is more reliable on
476 // a local machine, but may not be supported by an NFS server.
477 rv
= LockWithFcntl(lockFile
, aUnlocker
);
478 if (NS_SUCCEEDED(rv
)) {
479 // Check to see whether there is a symlink lock held by an older
480 // Firefox build, and also place our own symlink lock --- but
481 // mark it "obsolete" so that other newer builds can break the lock
482 // if they obtain the fcntl lock
483 rv
= LockWithSymlink(oldLockFile
, true);
485 // If the symlink failed for some reason other than it already
486 // exists, then something went wrong e.g. the file system
487 // doesn't support symlinks, or we don't have permission to
488 // create a symlink there. In such cases we should just
489 // continue because it's unlikely there is an old build
490 // running with a symlink there and we've already successfully
491 // placed a fcntl lock.
492 if (rv
!= NS_ERROR_FILE_ACCESS_DENIED
) rv
= NS_OK
;
493 } else if (rv
!= NS_ERROR_FILE_ACCESS_DENIED
) {
494 // If that failed for any reason other than NS_ERROR_FILE_ACCESS_DENIED,
495 // assume we tried an NFS that does not support it. Now, try with symlink
496 // using the old symlink path
497 rv
= LockWithSymlink(oldLockFile
, false);
500 #elif defined(XP_WIN)
501 nsAutoString filePath
;
502 rv
= lockFile
->GetPath(filePath
);
503 if (NS_FAILED(rv
)) return rv
;
505 lockFile
->GetLastModifiedTime(&mReplacedLockTime
);
507 // always create the profile lock and never delete it so we can use its
508 // modification timestamp to detect startup crashes
509 mLockFileHandle
= CreateFileW(filePath
.get(), GENERIC_READ
| GENERIC_WRITE
,
510 0, // no sharing - of course
511 nullptr, CREATE_ALWAYS
, 0, nullptr);
512 if (mLockFileHandle
== INVALID_HANDLE_VALUE
) {
514 RefPtr
<mozilla::ProfileUnlockerWin
> unlocker(
515 new mozilla::ProfileUnlockerWin(filePath
));
516 if (NS_SUCCEEDED(unlocker
->Init())) {
517 nsCOMPtr
<nsIProfileUnlocker
> unlockerInterface(
518 do_QueryObject(unlocker
));
519 unlockerInterface
.forget(aUnlocker
);
522 return NS_ERROR_FILE_ACCESS_DENIED
;
526 if (NS_SUCCEEDED(rv
)) mHaveLock
= true;
531 nsresult
nsProfileLock::Unlock(bool aFatalSignal
) {
536 if (mLockFileHandle
!= INVALID_HANDLE_VALUE
) {
537 CloseHandle(mLockFileHandle
);
538 mLockFileHandle
= INVALID_HANDLE_VALUE
;
540 #elif defined(XP_UNIX)
541 if (mPidLockFileName
) {
542 PR_REMOVE_LINK(this);
543 (void)unlink(mPidLockFileName
);
545 // Only free mPidLockFileName if we're not in the fatal signal
546 // handler. The problem is that a call to free() might be the
547 // cause of this fatal signal. If so, calling free() might cause
548 // us to wait on the malloc implementation's lock. We're already
549 // holding this lock, so we'll deadlock. See bug 522332.
550 if (!aFatalSignal
) free(mPidLockFileName
);
551 mPidLockFileName
= nullptr;
553 if (mLockFileDesc
!= -1) {
554 close(mLockFileDesc
);
566 nsresult
nsProfileLock::Cleanup() {
568 return NS_ERROR_FILE_IS_LOCKED
;
572 nsresult rv
= mLockFile
->Remove(false);
573 NS_ENSURE_SUCCESS(rv
, rv
);