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 mLockFile
= rhs
.mLockFile
;
72 rhs
.mLockFile
= nullptr;
73 mHaveLock
= rhs
.mHaveLock
;
74 rhs
.mHaveLock
= false;
75 mReplacedLockTime
= rhs
.mReplacedLockTime
;
78 mLockFileHandle
= rhs
.mLockFileHandle
;
79 rhs
.mLockFileHandle
= INVALID_HANDLE_VALUE
;
80 #elif defined(XP_UNIX)
81 mLockFileDesc
= rhs
.mLockFileDesc
;
82 rhs
.mLockFileDesc
= -1;
83 mPidLockFileName
= rhs
.mPidLockFileName
;
84 rhs
.mPidLockFileName
= nullptr;
85 if (mPidLockFileName
) {
86 // rhs had a symlink lock, therefore it was on the list.
88 PR_APPEND_LINK(this, &mPidLockList
);
95 nsProfileLock::~nsProfileLock() {
97 // Note that we don't clean up by default here so on next startup we know when
98 // the profile was last used based on the modification time of the lock file.
103 static int setupPidLockCleanup
;
105 PRCList
nsProfileLock::mPidLockList
=
106 PR_INIT_STATIC_CLIST(&nsProfileLock::mPidLockList
);
108 void nsProfileLock::RemovePidLockFiles(bool aFatalSignal
) {
109 while (!PR_CLIST_IS_EMPTY(&mPidLockList
)) {
110 nsProfileLock
* lock
= static_cast<nsProfileLock
*>(mPidLockList
.next
);
111 lock
->Unlock(aFatalSignal
);
115 static struct sigaction SIGHUP_oldact
;
116 static struct sigaction SIGINT_oldact
;
117 static struct sigaction SIGQUIT_oldact
;
118 static struct sigaction SIGILL_oldact
;
119 static struct sigaction SIGABRT_oldact
;
120 static struct sigaction SIGSEGV_oldact
;
121 static struct sigaction SIGTERM_oldact
;
123 void nsProfileLock::FatalSignalHandler(int signo
126 siginfo_t
* info
, void* context
129 // Remove any locks still held.
130 RemovePidLockFiles(true);
132 // Chain to the old handler, which may exit.
133 struct sigaction
* oldact
= nullptr;
137 oldact
= &SIGHUP_oldact
;
140 oldact
= &SIGINT_oldact
;
143 oldact
= &SIGQUIT_oldact
;
146 oldact
= &SIGILL_oldact
;
149 oldact
= &SIGABRT_oldact
;
152 oldact
= &SIGSEGV_oldact
;
155 oldact
= &SIGTERM_oldact
;
158 MOZ_ASSERT_UNREACHABLE("bad signo");
163 if (oldact
->sa_handler
== SIG_DFL
) {
164 // Make sure the default sig handler is executed
165 // We need it to get Mozilla to dump core.
166 sigaction(signo
, oldact
, nullptr);
168 // Now that we've restored the default handler, unmask the
169 // signal and invoke it.
171 sigset_t unblock_sigs
;
172 sigemptyset(&unblock_sigs
);
173 sigaddset(&unblock_sigs
, signo
);
175 sigprocmask(SIG_UNBLOCK
, &unblock_sigs
, nullptr);
180 else if (oldact
->sa_sigaction
&&
181 (oldact
->sa_flags
& SA_SIGINFO
) == SA_SIGINFO
) {
182 oldact
->sa_sigaction(signo
, info
, context
);
185 else if (oldact
->sa_handler
&& oldact
->sa_handler
!= SIG_IGN
) {
186 oldact
->sa_handler(signo
);
190 // Backstop exit call, just in case.
194 nsresult
nsProfileLock::LockWithFcntl(nsIFile
* aLockFile
,
195 nsIProfileUnlocker
** aUnlocker
) {
198 nsAutoCString lockFilePath
;
199 rv
= aLockFile
->GetNativePath(lockFilePath
);
201 NS_ERROR("Could not get native path");
205 aLockFile
->GetLastModifiedTime(&mReplacedLockTime
);
207 mLockFileDesc
= open(lockFilePath
.get(), O_WRONLY
| O_CREAT
| O_TRUNC
, 0666);
208 if (mLockFileDesc
!= -1) {
211 lock
.l_len
= 0; // len = 0 means entire file
212 lock
.l_type
= F_WRLCK
;
213 lock
.l_whence
= SEEK_SET
;
215 // If fcntl(F_GETLK) fails then the server does not support/allow fcntl(),
216 // return failure rather than access denied in this case so we fallback
217 // to using a symlink lock, bug 303633.
218 struct flock testlock
= lock
;
219 if (fcntl(mLockFileDesc
, F_GETLK
, &testlock
) == -1) {
220 close(mLockFileDesc
);
222 rv
= NS_ERROR_FAILURE
;
223 } else if (fcntl(mLockFileDesc
, F_SETLK
, &lock
) == -1) {
224 # ifdef MOZ_WIDGET_ANDROID
225 MOZ_ASSERT(aUnlocker
);
226 RefPtr
<mozilla::ProfileUnlockerAndroid
> unlocker(
227 new mozilla::ProfileUnlockerAndroid(testlock
.l_pid
));
228 nsCOMPtr
<nsIProfileUnlocker
> unlockerInterface(do_QueryObject(unlocker
));
229 unlockerInterface
.forget(aUnlocker
);
232 close(mLockFileDesc
);
235 // With OS X, on NFS, errno == ENOTSUP
236 // XXX Check for that and return specific rv for it?
238 printf("fcntl(F_SETLK) failed. errno = %d\n", errno
);
240 if (errno
== EAGAIN
|| errno
== EACCES
)
241 rv
= NS_ERROR_FILE_ACCESS_DENIED
;
243 rv
= NS_ERROR_FAILURE
;
246 NS_ERROR("Failed to open lock file.");
247 rv
= NS_ERROR_FAILURE
;
252 static bool IsSymlinkStaleLock(struct in_addr
* aAddr
, const char* aFileName
,
253 bool aHaveFcntlLock
) {
254 // the link exists; see if it's from this machine, and if
255 // so if the process is still active
257 int len
= readlink(aFileName
, buf
, sizeof buf
- 1);
260 char* colon
= strchr(buf
, ':');
263 unsigned long addr
= inet_addr(buf
);
264 if (addr
!= (unsigned long)-1) {
265 if (colon
[0] == '+' && aHaveFcntlLock
) {
266 // This lock was placed by a Firefox build which would have
267 // taken the fnctl lock, and we've already taken the fcntl lock,
268 // so the process that created this obsolete lock must be gone
272 char* after
= nullptr;
273 pid_t pid
= strtol(colon
, &after
, 0);
274 if (pid
!= 0 && *after
== '\0') {
275 if (addr
!= aAddr
->s_addr
) {
276 // Remote lock: give up even if stuck.
280 // kill(pid,0) is a neat trick to check if a
282 if (kill(pid
, 0) == 0 || errno
!= ESRCH
) {
283 // Local process appears to be alive, ass-u-me it
284 // is another Mozilla instance, or a compatible
285 // derivative, that's currently using the profile.
286 // XXX need an "are you Mozilla?" protocol
296 nsresult
nsProfileLock::LockWithSymlink(nsIFile
* aLockFile
,
297 bool aHaveFcntlLock
) {
299 nsAutoCString lockFilePath
;
300 rv
= aLockFile
->GetNativePath(lockFilePath
);
302 NS_ERROR("Could not get native path");
306 // don't replace an existing lock time if fcntl already got one
307 if (!mReplacedLockTime
)
308 aLockFile
->GetLastModifiedTimeOfLink(&mReplacedLockTime
);
310 struct in_addr inaddr
;
311 inaddr
.s_addr
= htonl(INADDR_LOOPBACK
);
313 // We still have not loaded the profile, so we may not have proxy information.
314 // Avoiding a DNS lookup in this stage makes sure any proxy is not bypassed.
315 // By default, the lookup is enabled, but when it is not, we use 127.0.0.1
316 // for the IP address portion of the lock signature.
317 // However, this may cause the browser to refuse to start in the rare case
318 // that all of the following conditions are met:
319 // 1. The browser profile is on a network file system.
320 // 2. The file system does not support fcntl() locking.
321 // 3. The browser is run from two different computers at the same time.
322 # ifndef MOZ_PROXY_BYPASS_PROTECTION
324 PRStatus status
= PR_GetSystemInfo(PR_SI_HOSTNAME
, hostname
, sizeof hostname
);
325 if (status
== PR_SUCCESS
) {
326 char netdbbuf
[PR_NETDB_BUF_SIZE
];
328 status
= PR_GetHostByName(hostname
, netdbbuf
, sizeof netdbbuf
, &hostent
);
329 if (status
== PR_SUCCESS
) memcpy(&inaddr
, hostent
.h_addr
, sizeof inaddr
);
333 mozilla::SmprintfPointer signature
=
334 mozilla::Smprintf("%s:%s%lu", inet_ntoa(inaddr
),
335 aHaveFcntlLock
? "+" : "", (unsigned long)getpid());
336 const char* fileName
= lockFilePath
.get();
337 int symlink_rv
, symlink_errno
= 0, tries
= 0;
339 // use ns4.x-compatible symlinks if the FS supports them
340 while ((symlink_rv
= symlink(signature
.get(), fileName
)) < 0) {
341 symlink_errno
= errno
;
342 if (symlink_errno
!= EEXIST
) break;
344 if (!IsSymlinkStaleLock(&inaddr
, fileName
, aHaveFcntlLock
)) break;
346 // Lock seems to be bogus: try to claim it. Give up after a large
347 // number of attempts (100 comes from the 4.x codebase).
348 (void)unlink(fileName
);
349 if (++tries
> 100) break;
352 if (symlink_rv
== 0) {
353 // We exclusively created the symlink: record its name for eventual
354 // unlock-via-unlink.
356 mPidLockFileName
= strdup(fileName
);
357 if (mPidLockFileName
) {
358 PR_APPEND_LINK(this, &mPidLockList
);
359 if (!setupPidLockCleanup
++) {
360 // Clean up on normal termination.
361 // This instanciates a dummy class, and will trigger the class
362 // destructor when libxul is unloaded. This is equivalent to atexit(),
363 // but gracefully handles dlclose().
364 static RemovePidLockFilesExiting r
;
366 // Clean up on abnormal termination, using POSIX sigaction.
367 // Don't arm a handler if the signal is being ignored, e.g.,
368 // because mozilla is run via nohup.
369 if (!sDisableSignalHandling
) {
370 struct sigaction act
, oldact
;
372 act
.sa_sigaction
= FatalSignalHandler
;
373 act
.sa_flags
= SA_SIGINFO
| SA_ONSTACK
;
375 act
.sa_handler
= FatalSignalHandler
;
377 sigfillset(&act
.sa_mask
);
379 # define CATCH_SIGNAL(signame) \
381 if (sigaction(signame, nullptr, &oldact) == 0 && \
382 oldact.sa_handler != SIG_IGN) { \
383 sigaction(signame, &act, &signame##_oldact); \
387 CATCH_SIGNAL(SIGHUP
);
388 CATCH_SIGNAL(SIGINT
);
389 CATCH_SIGNAL(SIGQUIT
);
390 CATCH_SIGNAL(SIGILL
);
391 CATCH_SIGNAL(SIGABRT
);
392 CATCH_SIGNAL(SIGSEGV
);
393 CATCH_SIGNAL(SIGTERM
);
399 } else if (symlink_errno
== EEXIST
)
400 rv
= NS_ERROR_FILE_ACCESS_DENIED
;
403 printf("symlink() failed. errno = %d\n", errno
);
405 rv
= NS_ERROR_FAILURE
;
411 nsresult
nsProfileLock::GetReplacedLockTime(PRTime
* aResult
) {
412 *aResult
= mReplacedLockTime
;
416 #if defined(XP_MACOSX)
417 constexpr auto LOCKFILE_NAME
= u
".parentlock"_ns
;
418 constexpr auto OLD_LOCKFILE_NAME
= u
"parent.lock"_ns
;
419 #elif defined(XP_UNIX)
420 constexpr auto OLD_LOCKFILE_NAME
= u
"lock"_ns
;
421 constexpr auto LOCKFILE_NAME
= u
".parentlock"_ns
;
423 constexpr auto LOCKFILE_NAME
= u
"parent.lock"_ns
;
426 bool nsProfileLock::IsMaybeLockFile(nsIFile
* aFile
) {
428 if (NS_SUCCEEDED(aFile
->GetLeafName(tmp
))) {
429 if (tmp
.Equals(LOCKFILE_NAME
)) return true;
430 #if (defined(XP_MACOSX) || defined(XP_UNIX))
431 if (tmp
.Equals(OLD_LOCKFILE_NAME
)) return true;
437 nsresult
nsProfileLock::Lock(nsIFile
* aProfileDir
,
438 nsIProfileUnlocker
** aUnlocker
) {
440 if (aUnlocker
) *aUnlocker
= nullptr;
442 NS_ENSURE_STATE(!mHaveLock
);
445 rv
= aProfileDir
->IsDirectory(&isDir
);
446 if (NS_FAILED(rv
)) return rv
;
447 if (!isDir
) return NS_ERROR_FILE_NOT_DIRECTORY
;
449 nsCOMPtr
<nsIFile
> lockFile
;
450 rv
= aProfileDir
->Clone(getter_AddRefs(lockFile
));
451 if (NS_FAILED(rv
)) return rv
;
453 rv
= lockFile
->Append(LOCKFILE_NAME
);
454 if (NS_FAILED(rv
)) return rv
;
456 // Remember the name we're using so we can clean up
457 rv
= lockFile
->Clone(getter_AddRefs(mLockFile
));
458 if (NS_FAILED(rv
)) return rv
;
460 #if defined(XP_MACOSX)
461 // First, try locking using fcntl. It is more reliable on
462 // a local machine, but may not be supported by an NFS server.
464 rv
= LockWithFcntl(lockFile
);
465 if (NS_FAILED(rv
) && (rv
!= NS_ERROR_FILE_ACCESS_DENIED
)) {
466 // If that failed for any reason other than NS_ERROR_FILE_ACCESS_DENIED,
467 // assume we tried an NFS that does not support it. Now, try with symlink.
468 rv
= LockWithSymlink(lockFile
, false);
470 #elif defined(XP_UNIX)
471 // Get the old lockfile name
472 nsCOMPtr
<nsIFile
> oldLockFile
;
473 rv
= aProfileDir
->Clone(getter_AddRefs(oldLockFile
));
474 if (NS_FAILED(rv
)) return rv
;
475 rv
= oldLockFile
->Append(OLD_LOCKFILE_NAME
);
476 if (NS_FAILED(rv
)) return rv
;
478 // First, try locking using fcntl. It is more reliable on
479 // a local machine, but may not be supported by an NFS server.
480 rv
= LockWithFcntl(lockFile
, aUnlocker
);
481 if (NS_SUCCEEDED(rv
)) {
482 // Check to see whether there is a symlink lock held by an older
483 // Firefox build, and also place our own symlink lock --- but
484 // mark it "obsolete" so that other newer builds can break the lock
485 // if they obtain the fcntl lock
486 rv
= LockWithSymlink(oldLockFile
, true);
488 // If the symlink failed for some reason other than it already
489 // exists, then something went wrong e.g. the file system
490 // doesn't support symlinks, or we don't have permission to
491 // create a symlink there. In such cases we should just
492 // continue because it's unlikely there is an old build
493 // running with a symlink there and we've already successfully
494 // placed a fcntl lock.
495 if (rv
!= NS_ERROR_FILE_ACCESS_DENIED
) rv
= NS_OK
;
496 } else if (rv
!= NS_ERROR_FILE_ACCESS_DENIED
) {
497 // If that failed for any reason other than NS_ERROR_FILE_ACCESS_DENIED,
498 // assume we tried an NFS that does not support it. Now, try with symlink
499 // using the old symlink path
500 rv
= LockWithSymlink(oldLockFile
, false);
503 #elif defined(XP_WIN)
504 nsAutoString filePath
;
505 rv
= lockFile
->GetPath(filePath
);
506 if (NS_FAILED(rv
)) return rv
;
508 lockFile
->GetLastModifiedTime(&mReplacedLockTime
);
510 // always create the profile lock and never delete it so we can use its
511 // modification timestamp to detect startup crashes
512 mLockFileHandle
= CreateFileW(filePath
.get(), GENERIC_READ
| GENERIC_WRITE
,
513 0, // no sharing - of course
514 nullptr, CREATE_ALWAYS
, 0, nullptr);
515 if (mLockFileHandle
== INVALID_HANDLE_VALUE
) {
517 RefPtr
<mozilla::ProfileUnlockerWin
> unlocker(
518 new mozilla::ProfileUnlockerWin(filePath
));
519 if (NS_SUCCEEDED(unlocker
->Init())) {
520 nsCOMPtr
<nsIProfileUnlocker
> unlockerInterface(
521 do_QueryObject(unlocker
));
522 unlockerInterface
.forget(aUnlocker
);
525 return NS_ERROR_FILE_ACCESS_DENIED
;
529 if (NS_SUCCEEDED(rv
)) mHaveLock
= true;
534 nsresult
nsProfileLock::Unlock(bool aFatalSignal
) {
539 if (mLockFileHandle
!= INVALID_HANDLE_VALUE
) {
540 CloseHandle(mLockFileHandle
);
541 mLockFileHandle
= INVALID_HANDLE_VALUE
;
543 #elif defined(XP_UNIX)
544 if (mPidLockFileName
) {
545 PR_REMOVE_LINK(this);
546 (void)unlink(mPidLockFileName
);
548 // Only free mPidLockFileName if we're not in the fatal signal
549 // handler. The problem is that a call to free() might be the
550 // cause of this fatal signal. If so, calling free() might cause
551 // us to wait on the malloc implementation's lock. We're already
552 // holding this lock, so we'll deadlock. See bug 522332.
553 if (!aFatalSignal
) free(mPidLockFileName
);
554 mPidLockFileName
= nullptr;
556 if (mLockFileDesc
!= -1) {
557 close(mLockFileDesc
);
569 nsresult
nsProfileLock::Cleanup() {
571 return NS_ERROR_FILE_IS_LOCKED
;
575 nsresult rv
= mLockFile
->Remove(false);
576 NS_ENSURE_SUCCESS(rv
, rv
);