Bug 1686495 [wpt PR 27132] - Add tests for proposed WebDriver Shadow DOM support...
[gecko.git] / toolkit / profile / nsProfileLock.cpp
blob01818d32e6f7e37b9db1dd28ce9d25bba75c633f
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"
11 #if defined(XP_WIN)
12 # include "ProfileUnlockerWin.h"
13 #endif
15 #if defined(XP_MACOSX)
16 # include <Carbon/Carbon.h>
17 # include <CoreFoundation/CoreFoundation.h>
18 #endif
20 #ifdef XP_UNIX
21 # include <unistd.h>
22 # include <fcntl.h>
23 # include <errno.h>
24 # include <signal.h>
25 # include <stdlib.h>
26 # include "prnetdb.h"
27 # include "prsystem.h"
28 # include "prenv.h"
29 # include "mozilla/Printf.h"
30 #endif
32 // **********************************************************************
33 // class nsProfileLock
35 // This code was moved from profile/src/nsProfileAccess.
36 // **********************************************************************
38 #if defined(XP_UNIX)
39 static bool sDisableSignalHandling = false;
40 #endif
42 nsProfileLock::nsProfileLock()
43 : mHaveLock(false),
44 mReplacedLockTime(0)
45 #if defined(XP_WIN)
47 mLockFileHandle(INVALID_HANDLE_VALUE)
48 #elif defined(XP_UNIX)
50 mPidLockFileName(nullptr),
51 mLockFileDesc(-1)
52 #endif
54 #if defined(XP_UNIX)
55 next = prev = this;
56 sDisableSignalHandling = PR_GetEnv("MOZ_DISABLE_SIG_HANDLER") ? true : false;
57 #endif
60 nsProfileLock::nsProfileLock(nsProfileLock& src) { *this = src; }
62 nsProfileLock& nsProfileLock::operator=(nsProfileLock& rhs) {
63 Unlock();
65 mHaveLock = rhs.mHaveLock;
66 rhs.mHaveLock = false;
68 #if defined(XP_WIN)
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.
78 PR_REMOVE_LINK(&rhs);
79 PR_APPEND_LINK(this, &mPidLockList);
81 #endif
83 return *this;
86 nsProfileLock::~nsProfileLock() {
87 Unlock();
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.
92 #if defined(XP_UNIX)
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
115 # ifdef SA_SIGINFO
117 siginfo_t* info, void* context
118 # endif
120 // Remove any locks still held.
121 RemovePidLockFiles(true);
123 // Chain to the old handler, which may exit.
124 struct sigaction* oldact = nullptr;
126 switch (signo) {
127 case SIGHUP:
128 oldact = &SIGHUP_oldact;
129 break;
130 case SIGINT:
131 oldact = &SIGINT_oldact;
132 break;
133 case SIGQUIT:
134 oldact = &SIGQUIT_oldact;
135 break;
136 case SIGILL:
137 oldact = &SIGILL_oldact;
138 break;
139 case SIGABRT:
140 oldact = &SIGABRT_oldact;
141 break;
142 case SIGSEGV:
143 oldact = &SIGSEGV_oldact;
144 break;
145 case SIGTERM:
146 oldact = &SIGTERM_oldact;
147 break;
148 default:
149 MOZ_ASSERT_UNREACHABLE("bad signo");
150 break;
153 if (oldact) {
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);
168 raise(signo);
170 # ifdef SA_SIGINFO
171 else if (oldact->sa_sigaction &&
172 (oldact->sa_flags & SA_SIGINFO) == SA_SIGINFO) {
173 oldact->sa_sigaction(signo, info, context);
175 # endif
176 else if (oldact->sa_handler && oldact->sa_handler != SIG_IGN) {
177 oldact->sa_handler(signo);
181 // Backstop exit call, just in case.
182 _exit(signo);
185 nsresult nsProfileLock::LockWithFcntl(nsIFile* aLockFile) {
186 nsresult rv = NS_OK;
188 nsAutoCString lockFilePath;
189 rv = aLockFile->GetNativePath(lockFilePath);
190 if (NS_FAILED(rv)) {
191 NS_ERROR("Could not get native path");
192 return rv;
195 aLockFile->GetLastModifiedTime(&mReplacedLockTime);
197 mLockFileDesc = open(lockFilePath.get(), O_WRONLY | O_CREAT | O_TRUNC, 0666);
198 if (mLockFileDesc != -1) {
199 struct flock lock;
200 lock.l_start = 0;
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);
211 mLockFileDesc = -1;
212 rv = NS_ERROR_FAILURE;
213 } else if (fcntl(mLockFileDesc, F_SETLK, &lock) == -1) {
214 close(mLockFileDesc);
215 mLockFileDesc = -1;
217 // With OS X, on NFS, errno == ENOTSUP
218 // XXX Check for that and return specific rv for it?
219 # ifdef DEBUG
220 printf("fcntl(F_SETLK) failed. errno = %d\n", errno);
221 # endif
222 if (errno == EAGAIN || errno == EACCES)
223 rv = NS_ERROR_FILE_ACCESS_DENIED;
224 else
225 rv = NS_ERROR_FAILURE;
227 } else {
228 NS_ERROR("Failed to open lock file.");
229 rv = NS_ERROR_FAILURE;
231 return rv;
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
238 char buf[1024];
239 int len = readlink(aFileName, buf, sizeof buf - 1);
240 if (len > 0) {
241 buf[len] = '\0';
242 char* colon = strchr(buf, ':');
243 if (colon) {
244 *colon++ = '\0';
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
251 return true;
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.
259 return false;
262 // kill(pid,0) is a neat trick to check if a
263 // process exists
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
269 return false;
275 return true;
278 nsresult nsProfileLock::LockWithSymlink(nsIFile* aLockFile,
279 bool aHaveFcntlLock) {
280 nsresult rv;
281 nsAutoCString lockFilePath;
282 rv = aLockFile->GetNativePath(lockFilePath);
283 if (NS_FAILED(rv)) {
284 NS_ERROR("Could not get native path");
285 return rv;
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);
295 char hostname[256];
296 PRStatus status = PR_GetSystemInfo(PR_SI_HOSTNAME, hostname, sizeof hostname);
297 if (status == PR_SUCCESS) {
298 char netdbbuf[PR_NETDB_BUF_SIZE];
299 PRHostEnt hostent;
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.
326 rv = NS_OK;
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;
342 # ifdef SA_SIGINFO
343 act.sa_sigaction = FatalSignalHandler;
344 act.sa_flags = SA_SIGINFO | SA_ONSTACK;
345 # else
346 act.sa_handler = FatalSignalHandler;
347 # endif
348 sigfillset(&act.sa_mask);
350 # define CATCH_SIGNAL(signame) \
351 PR_BEGIN_MACRO \
352 if (sigaction(signame, nullptr, &oldact) == 0 && \
353 oldact.sa_handler != SIG_IGN) { \
354 sigaction(signame, &act, &signame##_oldact); \
356 PR_END_MACRO
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);
366 # undef CATCH_SIGNAL
370 } else if (symlink_errno == EEXIST)
371 rv = NS_ERROR_FILE_ACCESS_DENIED;
372 else {
373 # ifdef DEBUG
374 printf("symlink() failed. errno = %d\n", errno);
375 # endif
376 rv = NS_ERROR_FAILURE;
378 return rv;
380 #endif /* XP_UNIX */
382 nsresult nsProfileLock::GetReplacedLockTime(PRTime* aResult) {
383 *aResult = mReplacedLockTime;
384 return NS_OK;
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;
395 #else
396 constexpr auto LOCKFILE_NAME = u"parent.lock"_ns;
397 #endif
399 nsresult rv;
400 if (aUnlocker) *aUnlocker = nullptr;
402 NS_ENSURE_STATE(!mHaveLock);
404 bool isDir;
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;
442 int32_t ioBytes;
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));
451 PR_Close(fd);
453 if (ioBytes == sizeof(LockProcessInfo)) {
454 # ifdef __LP64__
455 processInfo.processAppRef = nullptr;
456 # else
457 processInfo.processAppSpec = nullptr;
458 # endif
459 processInfo.processName = nullptr;
460 processInfo.processInfoLength = sizeof(ProcessInfoRec);
461 if (::GetProcessInformation(&lockProcessInfo.psn, &processInfo) ==
462 noErr &&
463 processInfo.processLaunchDate == lockProcessInfo.launchDate) {
464 return NS_ERROR_FILE_ACCESS_DENIED;
466 } else {
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) {
518 if (aUnlocker) {
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;
529 #endif
531 if (NS_SUCCEEDED(rv)) mHaveLock = true;
533 return rv;
536 nsresult nsProfileLock::Unlock(bool aFatalSignal) {
537 nsresult rv = NS_OK;
539 if (mHaveLock) {
540 #if defined(XP_WIN)
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);
560 mLockFileDesc = -1;
561 // Don't remove it
563 #endif
565 mHaveLock = false;
568 return rv;
571 nsresult nsProfileLock::Cleanup() {
572 if (mHaveLock) {
573 return NS_ERROR_FILE_IS_LOCKED;
576 if (mLockFile) {
577 nsresult rv = mLockFile->Remove(false);
578 NS_ENSURE_SUCCESS(rv, rv);
579 mLockFile = nullptr;
582 return NS_OK;