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"
12 # include "ProfileUnlockerWin.h"
15 #if defined(XP_MACOSX)
16 # include <Carbon/Carbon.h>
17 # include <CoreFoundation/CoreFoundation.h>
27 # include "prsystem.h"
29 # include "mozilla/Printf.h"
32 // **********************************************************************
33 // class nsProfileLock
35 // This code was moved from profile/src/nsProfileAccess.
36 // **********************************************************************
39 static bool sDisableSignalHandling
= false;
42 nsProfileLock::nsProfileLock()
47 mLockFileHandle(INVALID_HANDLE_VALUE
)
48 #elif defined(XP_UNIX)
50 mPidLockFileName(nullptr),
56 sDisableSignalHandling
= PR_GetEnv("MOZ_DISABLE_SIG_HANDLER") ? true : false;
60 nsProfileLock::nsProfileLock(nsProfileLock
& src
) { *this = src
; }
62 nsProfileLock
& nsProfileLock::operator=(nsProfileLock
& rhs
) {
65 mHaveLock
= rhs
.mHaveLock
;
66 rhs
.mHaveLock
= false;
69 mLockFileHandle
= rhs
.mLockFileHandle
;
70 rhs
.mLockFileHandle
= INVALID_HANDLE_VALUE
;
71 #elif defined(XP_UNIX)
72 mLockFileDesc
= rhs
.mLockFileDesc
;
73 rhs
.mLockFileDesc
= -1;
74 mPidLockFileName
= rhs
.mPidLockFileName
;
75 rhs
.mPidLockFileName
= nullptr;
76 if (mPidLockFileName
) {
77 // rhs had a symlink lock, therefore it was on the list.
79 PR_APPEND_LINK(this, &mPidLockList
);
86 nsProfileLock::~nsProfileLock() {
88 // Note that we don't clean up by default here so on next startup we know when
89 // the profile was last used based on the modification time of the lock file.
94 static int setupPidLockCleanup
;
96 PRCList
nsProfileLock::mPidLockList
=
97 PR_INIT_STATIC_CLIST(&nsProfileLock::mPidLockList
);
99 void nsProfileLock::RemovePidLockFiles(bool aFatalSignal
) {
100 while (!PR_CLIST_IS_EMPTY(&mPidLockList
)) {
101 nsProfileLock
* lock
= static_cast<nsProfileLock
*>(mPidLockList
.next
);
102 lock
->Unlock(aFatalSignal
);
106 static struct sigaction SIGHUP_oldact
;
107 static struct sigaction SIGINT_oldact
;
108 static struct sigaction SIGQUIT_oldact
;
109 static struct sigaction SIGILL_oldact
;
110 static struct sigaction SIGABRT_oldact
;
111 static struct sigaction SIGSEGV_oldact
;
112 static struct sigaction SIGTERM_oldact
;
114 void nsProfileLock::FatalSignalHandler(int signo
117 siginfo_t
* info
, void* context
120 // Remove any locks still held.
121 RemovePidLockFiles(true);
123 // Chain to the old handler, which may exit.
124 struct sigaction
* oldact
= nullptr;
128 oldact
= &SIGHUP_oldact
;
131 oldact
= &SIGINT_oldact
;
134 oldact
= &SIGQUIT_oldact
;
137 oldact
= &SIGILL_oldact
;
140 oldact
= &SIGABRT_oldact
;
143 oldact
= &SIGSEGV_oldact
;
146 oldact
= &SIGTERM_oldact
;
149 MOZ_ASSERT_UNREACHABLE("bad signo");
154 if (oldact
->sa_handler
== SIG_DFL
) {
155 // Make sure the default sig handler is executed
156 // We need it to get Mozilla to dump core.
157 sigaction(signo
, oldact
, nullptr);
159 // Now that we've restored the default handler, unmask the
160 // signal and invoke it.
162 sigset_t unblock_sigs
;
163 sigemptyset(&unblock_sigs
);
164 sigaddset(&unblock_sigs
, signo
);
166 sigprocmask(SIG_UNBLOCK
, &unblock_sigs
, nullptr);
171 else if (oldact
->sa_sigaction
&&
172 (oldact
->sa_flags
& SA_SIGINFO
) == SA_SIGINFO
) {
173 oldact
->sa_sigaction(signo
, info
, context
);
176 else if (oldact
->sa_handler
&& oldact
->sa_handler
!= SIG_IGN
) {
177 oldact
->sa_handler(signo
);
181 // Backstop exit call, just in case.
185 nsresult
nsProfileLock::LockWithFcntl(nsIFile
* aLockFile
) {
188 nsAutoCString lockFilePath
;
189 rv
= aLockFile
->GetNativePath(lockFilePath
);
191 NS_ERROR("Could not get native path");
195 aLockFile
->GetLastModifiedTime(&mReplacedLockTime
);
197 mLockFileDesc
= open(lockFilePath
.get(), O_WRONLY
| O_CREAT
| O_TRUNC
, 0666);
198 if (mLockFileDesc
!= -1) {
201 lock
.l_len
= 0; // len = 0 means entire file
202 lock
.l_type
= F_WRLCK
;
203 lock
.l_whence
= SEEK_SET
;
205 // If fcntl(F_GETLK) fails then the server does not support/allow fcntl(),
206 // return failure rather than access denied in this case so we fallback
207 // to using a symlink lock, bug 303633.
208 struct flock testlock
= lock
;
209 if (fcntl(mLockFileDesc
, F_GETLK
, &testlock
) == -1) {
210 close(mLockFileDesc
);
212 rv
= NS_ERROR_FAILURE
;
213 } else if (fcntl(mLockFileDesc
, F_SETLK
, &lock
) == -1) {
214 close(mLockFileDesc
);
217 // With OS X, on NFS, errno == ENOTSUP
218 // XXX Check for that and return specific rv for it?
220 printf("fcntl(F_SETLK) failed. errno = %d\n", errno
);
222 if (errno
== EAGAIN
|| errno
== EACCES
)
223 rv
= NS_ERROR_FILE_ACCESS_DENIED
;
225 rv
= NS_ERROR_FAILURE
;
228 NS_ERROR("Failed to open lock file.");
229 rv
= NS_ERROR_FAILURE
;
234 static bool IsSymlinkStaleLock(struct in_addr
* aAddr
, const char* aFileName
,
235 bool aHaveFcntlLock
) {
236 // the link exists; see if it's from this machine, and if
237 // so if the process is still active
239 int len
= readlink(aFileName
, buf
, sizeof buf
- 1);
242 char* colon
= strchr(buf
, ':');
245 unsigned long addr
= inet_addr(buf
);
246 if (addr
!= (unsigned long)-1) {
247 if (colon
[0] == '+' && aHaveFcntlLock
) {
248 // This lock was placed by a Firefox build which would have
249 // taken the fnctl lock, and we've already taken the fcntl lock,
250 // so the process that created this obsolete lock must be gone
254 char* after
= nullptr;
255 pid_t pid
= strtol(colon
, &after
, 0);
256 if (pid
!= 0 && *after
== '\0') {
257 if (addr
!= aAddr
->s_addr
) {
258 // Remote lock: give up even if stuck.
262 // kill(pid,0) is a neat trick to check if a
264 if (kill(pid
, 0) == 0 || errno
!= ESRCH
) {
265 // Local process appears to be alive, ass-u-me it
266 // is another Mozilla instance, or a compatible
267 // derivative, that's currently using the profile.
268 // XXX need an "are you Mozilla?" protocol
278 nsresult
nsProfileLock::LockWithSymlink(nsIFile
* aLockFile
,
279 bool aHaveFcntlLock
) {
281 nsAutoCString lockFilePath
;
282 rv
= aLockFile
->GetNativePath(lockFilePath
);
284 NS_ERROR("Could not get native path");
288 // don't replace an existing lock time if fcntl already got one
289 if (!mReplacedLockTime
)
290 aLockFile
->GetLastModifiedTimeOfLink(&mReplacedLockTime
);
292 struct in_addr inaddr
;
293 inaddr
.s_addr
= htonl(INADDR_LOOPBACK
);
296 PRStatus status
= PR_GetSystemInfo(PR_SI_HOSTNAME
, hostname
, sizeof hostname
);
297 if (status
== PR_SUCCESS
) {
298 char netdbbuf
[PR_NETDB_BUF_SIZE
];
300 status
= PR_GetHostByName(hostname
, netdbbuf
, sizeof netdbbuf
, &hostent
);
301 if (status
== PR_SUCCESS
) memcpy(&inaddr
, hostent
.h_addr
, sizeof inaddr
);
304 mozilla::SmprintfPointer signature
=
305 mozilla::Smprintf("%s:%s%lu", inet_ntoa(inaddr
),
306 aHaveFcntlLock
? "+" : "", (unsigned long)getpid());
307 const char* fileName
= lockFilePath
.get();
308 int symlink_rv
, symlink_errno
= 0, tries
= 0;
310 // use ns4.x-compatible symlinks if the FS supports them
311 while ((symlink_rv
= symlink(signature
.get(), fileName
)) < 0) {
312 symlink_errno
= errno
;
313 if (symlink_errno
!= EEXIST
) break;
315 if (!IsSymlinkStaleLock(&inaddr
, fileName
, aHaveFcntlLock
)) break;
317 // Lock seems to be bogus: try to claim it. Give up after a large
318 // number of attempts (100 comes from the 4.x codebase).
319 (void)unlink(fileName
);
320 if (++tries
> 100) break;
323 if (symlink_rv
== 0) {
324 // We exclusively created the symlink: record its name for eventual
325 // unlock-via-unlink.
327 mPidLockFileName
= strdup(fileName
);
328 if (mPidLockFileName
) {
329 PR_APPEND_LINK(this, &mPidLockList
);
330 if (!setupPidLockCleanup
++) {
331 // Clean up on normal termination.
332 // This instanciates a dummy class, and will trigger the class
333 // destructor when libxul is unloaded. This is equivalent to atexit(),
334 // but gracefully handles dlclose().
335 static RemovePidLockFilesExiting r
;
337 // Clean up on abnormal termination, using POSIX sigaction.
338 // Don't arm a handler if the signal is being ignored, e.g.,
339 // because mozilla is run via nohup.
340 if (!sDisableSignalHandling
) {
341 struct sigaction act
, oldact
;
343 act
.sa_sigaction
= FatalSignalHandler
;
344 act
.sa_flags
= SA_SIGINFO
| SA_ONSTACK
;
346 act
.sa_handler
= FatalSignalHandler
;
348 sigfillset(&act
.sa_mask
);
350 # define CATCH_SIGNAL(signame) \
352 if (sigaction(signame, nullptr, &oldact) == 0 && \
353 oldact.sa_handler != SIG_IGN) { \
354 sigaction(signame, &act, &signame##_oldact); \
358 CATCH_SIGNAL(SIGHUP
);
359 CATCH_SIGNAL(SIGINT
);
360 CATCH_SIGNAL(SIGQUIT
);
361 CATCH_SIGNAL(SIGILL
);
362 CATCH_SIGNAL(SIGABRT
);
363 CATCH_SIGNAL(SIGSEGV
);
364 CATCH_SIGNAL(SIGTERM
);
370 } else if (symlink_errno
== EEXIST
)
371 rv
= NS_ERROR_FILE_ACCESS_DENIED
;
374 printf("symlink() failed. errno = %d\n", errno
);
376 rv
= NS_ERROR_FAILURE
;
382 nsresult
nsProfileLock::GetReplacedLockTime(PRTime
* aResult
) {
383 *aResult
= mReplacedLockTime
;
387 nsresult
nsProfileLock::Lock(nsIFile
* aProfileDir
,
388 nsIProfileUnlocker
** aUnlocker
) {
389 #if defined(XP_MACOSX)
390 constexpr auto LOCKFILE_NAME
= u
".parentlock"_ns
;
391 constexpr auto OLD_LOCKFILE_NAME
= u
"parent.lock"_ns
;
392 #elif defined(XP_UNIX)
393 constexpr auto OLD_LOCKFILE_NAME
= u
"lock"_ns
;
394 constexpr auto LOCKFILE_NAME
= u
".parentlock"_ns
;
396 constexpr auto LOCKFILE_NAME
= u
"parent.lock"_ns
;
400 if (aUnlocker
) *aUnlocker
= nullptr;
402 NS_ENSURE_STATE(!mHaveLock
);
405 rv
= aProfileDir
->IsDirectory(&isDir
);
406 if (NS_FAILED(rv
)) return rv
;
407 if (!isDir
) return NS_ERROR_FILE_NOT_DIRECTORY
;
409 nsCOMPtr
<nsIFile
> lockFile
;
410 rv
= aProfileDir
->Clone(getter_AddRefs(lockFile
));
411 if (NS_FAILED(rv
)) return rv
;
413 rv
= lockFile
->Append(LOCKFILE_NAME
);
414 if (NS_FAILED(rv
)) return rv
;
416 // Remember the name we're using so we can clean up
417 rv
= lockFile
->Clone(getter_AddRefs(mLockFile
));
418 if (NS_FAILED(rv
)) return rv
;
420 #if defined(XP_MACOSX)
421 // First, try locking using fcntl. It is more reliable on
422 // a local machine, but may not be supported by an NFS server.
424 rv
= LockWithFcntl(lockFile
);
425 if (NS_FAILED(rv
) && (rv
!= NS_ERROR_FILE_ACCESS_DENIED
)) {
426 // If that failed for any reason other than NS_ERROR_FILE_ACCESS_DENIED,
427 // assume we tried an NFS that does not support it. Now, try with symlink.
428 rv
= LockWithSymlink(lockFile
, false);
431 if (NS_SUCCEEDED(rv
)) {
432 // Check for the old-style lock used by pre-mozilla 1.3 builds.
433 // Those builds used an earlier check to prevent the application
434 // from launching if another instance was already running. Because
435 // of that, we don't need to create an old-style lock as well.
436 struct LockProcessInfo
{
437 ProcessSerialNumber psn
;
438 unsigned long launchDate
;
441 PRFileDesc
* fd
= nullptr;
443 ProcessInfoRec processInfo
;
444 LockProcessInfo lockProcessInfo
;
446 rv
= lockFile
->SetLeafName(OLD_LOCKFILE_NAME
);
447 if (NS_FAILED(rv
)) return rv
;
448 rv
= lockFile
->OpenNSPRFileDesc(PR_RDONLY
, 0, &fd
);
449 if (NS_SUCCEEDED(rv
)) {
450 ioBytes
= PR_Read(fd
, &lockProcessInfo
, sizeof(LockProcessInfo
));
453 if (ioBytes
== sizeof(LockProcessInfo
)) {
455 processInfo
.processAppRef
= nullptr;
457 processInfo
.processAppSpec
= nullptr;
459 processInfo
.processName
= nullptr;
460 processInfo
.processInfoLength
= sizeof(ProcessInfoRec
);
461 if (::GetProcessInformation(&lockProcessInfo
.psn
, &processInfo
) ==
463 processInfo
.processLaunchDate
== lockProcessInfo
.launchDate
) {
464 return NS_ERROR_FILE_ACCESS_DENIED
;
467 NS_WARNING("Could not read lock file - ignoring lock");
470 rv
= NS_OK
; // Don't propagate error from OpenNSPRFileDesc.
472 #elif defined(XP_UNIX)
473 // Get the old lockfile name
474 nsCOMPtr
<nsIFile
> oldLockFile
;
475 rv
= aProfileDir
->Clone(getter_AddRefs(oldLockFile
));
476 if (NS_FAILED(rv
)) return rv
;
477 rv
= oldLockFile
->Append(OLD_LOCKFILE_NAME
);
478 if (NS_FAILED(rv
)) return rv
;
480 // First, try locking using fcntl. It is more reliable on
481 // a local machine, but may not be supported by an NFS server.
482 rv
= LockWithFcntl(lockFile
);
483 if (NS_SUCCEEDED(rv
)) {
484 // Check to see whether there is a symlink lock held by an older
485 // Firefox build, and also place our own symlink lock --- but
486 // mark it "obsolete" so that other newer builds can break the lock
487 // if they obtain the fcntl lock
488 rv
= LockWithSymlink(oldLockFile
, true);
490 // If the symlink failed for some reason other than it already
491 // exists, then something went wrong e.g. the file system
492 // doesn't support symlinks, or we don't have permission to
493 // create a symlink there. In such cases we should just
494 // continue because it's unlikely there is an old build
495 // running with a symlink there and we've already successfully
496 // placed a fcntl lock.
497 if (rv
!= NS_ERROR_FILE_ACCESS_DENIED
) rv
= NS_OK
;
498 } else if (rv
!= NS_ERROR_FILE_ACCESS_DENIED
) {
499 // If that failed for any reason other than NS_ERROR_FILE_ACCESS_DENIED,
500 // assume we tried an NFS that does not support it. Now, try with symlink
501 // using the old symlink path
502 rv
= LockWithSymlink(oldLockFile
, false);
505 #elif defined(XP_WIN)
506 nsAutoString filePath
;
507 rv
= lockFile
->GetPath(filePath
);
508 if (NS_FAILED(rv
)) return rv
;
510 lockFile
->GetLastModifiedTime(&mReplacedLockTime
);
512 // always create the profile lock and never delete it so we can use its
513 // modification timestamp to detect startup crashes
514 mLockFileHandle
= CreateFileW(filePath
.get(), GENERIC_READ
| GENERIC_WRITE
,
515 0, // no sharing - of course
516 nullptr, CREATE_ALWAYS
, 0, nullptr);
517 if (mLockFileHandle
== INVALID_HANDLE_VALUE
) {
519 RefPtr
<mozilla::ProfileUnlockerWin
> unlocker(
520 new mozilla::ProfileUnlockerWin(filePath
));
521 if (NS_SUCCEEDED(unlocker
->Init())) {
522 nsCOMPtr
<nsIProfileUnlocker
> unlockerInterface(
523 do_QueryObject(unlocker
));
524 unlockerInterface
.forget(aUnlocker
);
527 return NS_ERROR_FILE_ACCESS_DENIED
;
531 if (NS_SUCCEEDED(rv
)) mHaveLock
= true;
536 nsresult
nsProfileLock::Unlock(bool aFatalSignal
) {
541 if (mLockFileHandle
!= INVALID_HANDLE_VALUE
) {
542 CloseHandle(mLockFileHandle
);
543 mLockFileHandle
= INVALID_HANDLE_VALUE
;
545 #elif defined(XP_UNIX)
546 if (mPidLockFileName
) {
547 PR_REMOVE_LINK(this);
548 (void)unlink(mPidLockFileName
);
550 // Only free mPidLockFileName if we're not in the fatal signal
551 // handler. The problem is that a call to free() might be the
552 // cause of this fatal signal. If so, calling free() might cause
553 // us to wait on the malloc implementation's lock. We're already
554 // holding this lock, so we'll deadlock. See bug 522332.
555 if (!aFatalSignal
) free(mPidLockFileName
);
556 mPidLockFileName
= nullptr;
558 if (mLockFileDesc
!= -1) {
559 close(mLockFileDesc
);
571 nsresult
nsProfileLock::Cleanup() {
573 return NS_ERROR_FILE_IS_LOCKED
;
577 nsresult rv
= mLockFile
->Remove(false);
578 NS_ENSURE_SUCCESS(rv
, rv
);