Bug 1933479 - Add tab close button on hover to vertical tabs when sidebar is collapse...
[gecko.git] / toolkit / profile / nsProfileLock.cpp
bloba455a9c2dae304524b3b1e24476ee0dfdd4d9ff0
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"
7 #include "nsCOMPtr.h"
8 #include "nsQueryObject.h"
9 #include "nsString.h"
10 #include "nsPrintfCString.h"
11 #include "nsDebug.h"
13 #if defined(XP_WIN)
14 # include "ProfileUnlockerWin.h"
15 #endif
17 #if defined(XP_MACOSX)
18 # include <Carbon/Carbon.h>
19 # include <CoreFoundation/CoreFoundation.h>
20 #endif
22 #if defined(MOZ_WIDGET_ANDROID)
23 # include "ProfileUnlockerAndroid.h"
24 #endif
26 #ifdef XP_UNIX
27 # include <unistd.h>
28 # include <fcntl.h>
29 # include <errno.h>
30 # include <signal.h>
31 # include <stdlib.h>
32 # include "prnetdb.h"
33 # include "prsystem.h"
34 # include "prenv.h"
35 # include "mozilla/Printf.h"
36 #endif
38 // **********************************************************************
39 // class nsProfileLock
41 // This code was moved from profile/src/nsProfileAccess.
42 // **********************************************************************
44 #if defined(XP_UNIX)
45 static bool sDisableSignalHandling = false;
46 #endif
48 nsProfileLock::nsProfileLock()
49 : mHaveLock(false),
50 mReplacedLockTime(0)
51 #if defined(XP_WIN)
53 mLockFileHandle(INVALID_HANDLE_VALUE)
54 #elif defined(XP_UNIX)
56 mPidLockFileName(nullptr),
57 mLockFileDesc(-1)
58 #endif
60 #if defined(XP_UNIX)
61 next = prev = this;
62 sDisableSignalHandling = PR_GetEnv("MOZ_DISABLE_SIG_HANDLER") ? true : false;
63 #endif
66 nsProfileLock::nsProfileLock(nsProfileLock& src) { *this = src; }
68 nsProfileLock& nsProfileLock::operator=(nsProfileLock& rhs) {
69 Unlock();
71 mLockFile = rhs.mLockFile;
72 rhs.mLockFile = nullptr;
73 mHaveLock = rhs.mHaveLock;
74 rhs.mHaveLock = false;
75 mReplacedLockTime = rhs.mReplacedLockTime;
77 #if defined(XP_WIN)
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.
87 PR_REMOVE_LINK(&rhs);
88 PR_APPEND_LINK(this, &mPidLockList);
90 #endif
92 return *this;
95 nsProfileLock::~nsProfileLock() {
96 Unlock();
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.
101 #if defined(XP_UNIX)
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
124 # ifdef SA_SIGINFO
126 siginfo_t* info, void* context
127 # endif
129 // Remove any locks still held.
130 RemovePidLockFiles(true);
132 // Chain to the old handler, which may exit.
133 struct sigaction* oldact = nullptr;
135 switch (signo) {
136 case SIGHUP:
137 oldact = &SIGHUP_oldact;
138 break;
139 case SIGINT:
140 oldact = &SIGINT_oldact;
141 break;
142 case SIGQUIT:
143 oldact = &SIGQUIT_oldact;
144 break;
145 case SIGILL:
146 oldact = &SIGILL_oldact;
147 break;
148 case SIGABRT:
149 oldact = &SIGABRT_oldact;
150 break;
151 case SIGSEGV:
152 oldact = &SIGSEGV_oldact;
153 break;
154 case SIGTERM:
155 oldact = &SIGTERM_oldact;
156 break;
157 default:
158 MOZ_ASSERT_UNREACHABLE("bad signo");
159 break;
162 if (oldact) {
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);
177 raise(signo);
179 # ifdef SA_SIGINFO
180 else if (oldact->sa_sigaction &&
181 (oldact->sa_flags & SA_SIGINFO) == SA_SIGINFO) {
182 oldact->sa_sigaction(signo, info, context);
184 # endif
185 else if (oldact->sa_handler && oldact->sa_handler != SIG_IGN) {
186 oldact->sa_handler(signo);
190 // Backstop exit call, just in case.
191 _exit(signo);
194 nsresult nsProfileLock::LockWithFcntl(nsIFile* aLockFile,
195 nsIProfileUnlocker** aUnlocker) {
196 nsresult rv = NS_OK;
198 nsAutoCString lockFilePath;
199 rv = aLockFile->GetNativePath(lockFilePath);
200 if (NS_FAILED(rv)) {
201 NS_ERROR("Could not get native path");
202 return rv;
205 aLockFile->GetLastModifiedTime(&mReplacedLockTime);
207 mLockFileDesc = open(lockFilePath.get(), O_WRONLY | O_CREAT | O_TRUNC, 0666);
208 if (mLockFileDesc != -1) {
209 struct flock lock;
210 lock.l_start = 0;
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);
221 mLockFileDesc = -1;
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);
230 # endif
232 close(mLockFileDesc);
233 mLockFileDesc = -1;
235 // With OS X, on NFS, errno == ENOTSUP
236 // XXX Check for that and return specific rv for it?
237 # ifdef DEBUG
238 printf("fcntl(F_SETLK) failed. errno = %d\n", errno);
239 # endif
240 if (errno == EAGAIN || errno == EACCES)
241 rv = NS_ERROR_FILE_ACCESS_DENIED;
242 else
243 rv = NS_ERROR_FAILURE;
245 } else {
246 NS_ERROR("Failed to open lock file.");
247 rv = NS_ERROR_FAILURE;
249 return rv;
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
256 char buf[1024];
257 int len = readlink(aFileName, buf, sizeof buf - 1);
258 if (len > 0) {
259 buf[len] = '\0';
260 char* colon = strchr(buf, ':');
261 if (colon) {
262 *colon++ = '\0';
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
269 return true;
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.
277 return false;
280 // kill(pid,0) is a neat trick to check if a
281 // process exists
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
287 return false;
293 return true;
296 nsresult nsProfileLock::LockWithSymlink(nsIFile* aLockFile,
297 bool aHaveFcntlLock) {
298 nsresult rv;
299 nsAutoCString lockFilePath;
300 rv = aLockFile->GetNativePath(lockFilePath);
301 if (NS_FAILED(rv)) {
302 NS_ERROR("Could not get native path");
303 return rv;
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
323 char hostname[256];
324 PRStatus status = PR_GetSystemInfo(PR_SI_HOSTNAME, hostname, sizeof hostname);
325 if (status == PR_SUCCESS) {
326 char netdbbuf[PR_NETDB_BUF_SIZE];
327 PRHostEnt hostent;
328 status = PR_GetHostByName(hostname, netdbbuf, sizeof netdbbuf, &hostent);
329 if (status == PR_SUCCESS) memcpy(&inaddr, hostent.h_addr, sizeof inaddr);
331 # endif
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.
355 rv = NS_OK;
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;
371 # ifdef SA_SIGINFO
372 act.sa_sigaction = FatalSignalHandler;
373 act.sa_flags = SA_SIGINFO | SA_ONSTACK;
374 # else
375 act.sa_handler = FatalSignalHandler;
376 # endif
377 sigfillset(&act.sa_mask);
379 # define CATCH_SIGNAL(signame) \
380 PR_BEGIN_MACRO \
381 if (sigaction(signame, nullptr, &oldact) == 0 && \
382 oldact.sa_handler != SIG_IGN) { \
383 sigaction(signame, &act, &signame##_oldact); \
385 PR_END_MACRO
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);
395 # undef CATCH_SIGNAL
399 } else if (symlink_errno == EEXIST)
400 rv = NS_ERROR_FILE_ACCESS_DENIED;
401 else {
402 # ifdef DEBUG
403 printf("symlink() failed. errno = %d\n", errno);
404 # endif
405 rv = NS_ERROR_FAILURE;
407 return rv;
409 #endif /* XP_UNIX */
411 nsresult nsProfileLock::GetReplacedLockTime(PRTime* aResult) {
412 *aResult = mReplacedLockTime;
413 return NS_OK;
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;
422 #else
423 constexpr auto LOCKFILE_NAME = u"parent.lock"_ns;
424 #endif
426 bool nsProfileLock::IsMaybeLockFile(nsIFile* aFile) {
427 nsAutoString tmp;
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;
432 #endif
434 return false;
437 nsresult nsProfileLock::Lock(nsIFile* aProfileDir,
438 nsIProfileUnlocker** aUnlocker) {
439 nsresult rv;
440 if (aUnlocker) *aUnlocker = nullptr;
442 NS_ENSURE_STATE(!mHaveLock);
444 bool isDir;
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) {
516 if (aUnlocker) {
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;
527 #endif
529 if (NS_SUCCEEDED(rv)) mHaveLock = true;
531 return rv;
534 nsresult nsProfileLock::Unlock(bool aFatalSignal) {
535 nsresult rv = NS_OK;
537 if (mHaveLock) {
538 #if defined(XP_WIN)
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);
558 mLockFileDesc = -1;
559 // Don't remove it
561 #endif
563 mHaveLock = false;
566 return rv;
569 nsresult nsProfileLock::Cleanup() {
570 if (mHaveLock) {
571 return NS_ERROR_FILE_IS_LOCKED;
574 if (mLockFile) {
575 nsresult rv = mLockFile->Remove(false);
576 NS_ENSURE_SUCCESS(rv, rv);
577 mLockFile = nullptr;
580 return NS_OK;