Bug 1867925 - Mark some storage-access-api tests as intermittent after wpt-sync....
[gecko.git] / toolkit / xre / MultiInstanceLock.cpp
blobeb4db6367a53851b52db6afd46c5a46dd0ffbc5e
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "MultiInstanceLock.h"
9 #include "commonupdatedir.h" // for GetInstallHash
10 #include "mozilla/UniquePtr.h"
11 #include "nsPrintfCString.h"
12 #include "nsPromiseFlatString.h"
13 #include "nsXULAppAPI.h"
14 #include "updatedefines.h" // for NS_t* definitions
16 #ifdef XP_WIN
17 # include <shlwapi.h>
18 #else
19 # include <fcntl.h>
20 # include <sys/stat.h>
21 # include <sys/types.h>
22 #endif
24 #ifdef XP_WIN
25 # include "WinUtils.h"
26 #endif
28 #ifdef MOZ_WIDGET_COCOA
29 # include "nsILocalFileMac.h"
30 #endif
32 namespace mozilla {
34 bool GetMultiInstanceLockFileName(const char* nameToken,
35 const char16_t* installPath,
36 nsCString& filePath) {
37 #ifdef XP_WIN
38 // On Windows, the lock file is placed at the path
39 // [updateDirectory]\[nameToken]-[pathHash], so first we need to get the
40 // update directory path and then append the file name.
42 // Note: This will return something like
43 // C:\ProgramData\Mozilla-1de4eec8-1241-4177-a864-e594e8d1fb38\updates\<hash>
44 // But we actually are going to want to return the root update directory,
45 // the grandparent of this directory, which will look something like this:
46 // C:\ProgramData\Mozilla-1de4eec8-1241-4177-a864-e594e8d1fb38
47 mozilla::UniquePtr<wchar_t[]> updateDir;
48 HRESULT hr = GetCommonUpdateDirectory(
49 reinterpret_cast<const wchar_t*>(installPath), updateDir);
50 if (FAILED(hr)) {
51 return false;
54 // For the path manipulation that we are about to do, it is important that
55 // the update directory have no trailing slash.
56 size_t len = wcslen(updateDir.get());
57 if (len == 0) {
58 return false;
60 if (updateDir.get()[len - 1] == '/' || updateDir.get()[len - 1] == '\\') {
61 updateDir.get()[len - 1] = '\0';
64 wchar_t* hashPtr = PathFindFileNameW(updateDir.get());
65 // PathFindFileNameW returns a pointer to the beginning of the string on
66 // failure.
67 if (hashPtr == updateDir.get()) {
68 return false;
71 // We need to make a copy of the hash before we modify updateDir to get the
72 // root update dir.
73 size_t hashSize = wcslen(hashPtr) + 1;
74 mozilla::UniquePtr<wchar_t[]> hash = mozilla::MakeUnique<wchar_t[]>(hashSize);
75 errno_t error = wcscpy_s(hash.get(), hashSize, hashPtr);
76 if (error != 0) {
77 return false;
80 // Get the root update dir from the update dir.
81 BOOL success = PathRemoveFileSpecW(updateDir.get());
82 if (!success) {
83 return false;
85 success = PathRemoveFileSpecW(updateDir.get());
86 if (!success) {
87 return false;
90 filePath =
91 nsPrintfCString("%s\\%s-%s", NS_ConvertUTF16toUTF8(updateDir.get()).get(),
92 nameToken, NS_ConvertUTF16toUTF8(hash.get()).get());
94 #else
95 mozilla::UniquePtr<NS_tchar[]> pathHash;
96 if (!GetInstallHash(installPath, pathHash)) {
97 return false;
100 // On POSIX platforms the base path is /tmp/[vendor][nameToken]-[pathHash].
101 filePath = nsPrintfCString("/tmp/%s%s-%s", MOZ_APP_VENDOR, nameToken,
102 pathHash.get());
104 #endif
106 return true;
109 MultiInstLockHandle OpenMultiInstanceLock(const char* nameToken,
110 const char16_t* installPath) {
111 nsCString filePath;
112 if (!GetMultiInstanceLockFileName(nameToken, installPath, filePath)) {
113 return MULTI_INSTANCE_LOCK_HANDLE_ERROR;
116 // Open a file handle with full privileges and sharing, and then attempt to
117 // take a shared (nonexclusive, read-only) lock on it.
118 #ifdef XP_WIN
119 HANDLE h =
120 ::CreateFileW(PromiseFlatString(NS_ConvertUTF8toUTF16(filePath)).get(),
121 GENERIC_READ | GENERIC_WRITE,
122 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
123 nullptr, OPEN_ALWAYS, 0, nullptr);
124 if (h != INVALID_HANDLE_VALUE) {
125 // The LockFileEx functions always require an OVERLAPPED structure even
126 // though we did not open the lock file for overlapped I/O.
127 OVERLAPPED o = {0};
128 if (!::LockFileEx(h, LOCKFILE_FAIL_IMMEDIATELY, 0, 1, 0, &o)) {
129 CloseHandle(h);
130 h = INVALID_HANDLE_VALUE;
133 return h;
135 #else
136 int fd = ::open(PromiseFlatCString(filePath).get(),
137 O_CLOEXEC | O_CREAT | O_NOFOLLOW,
138 S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
139 if (fd != -1) {
140 // We would like to ensure that the lock file is deleted when we are done
141 // with it. The normal way to do that would be to call unlink on it right
142 // now, but that would immediately delete the name from the file system, and
143 // we need other instances to be able to open that name and get the same
144 // inode, so we can't unlink the file before we're done with it. This means
145 // we accept some unreliability in getting the file deleted, but it's a zero
146 // byte file in the tmp directory, so having it stay around isn't the worst.
147 struct flock l = {0};
148 l.l_start = 0;
149 l.l_len = 0;
150 l.l_type = F_RDLCK;
151 if (::fcntl(fd, F_SETLK, &l)) {
152 ::close(fd);
153 fd = -1;
156 return fd;
158 #endif
161 void ReleaseMultiInstanceLock(MultiInstLockHandle lock) {
162 if (lock != MULTI_INSTANCE_LOCK_HANDLE_ERROR) {
163 #ifdef XP_WIN
164 OVERLAPPED o = {0};
165 ::UnlockFileEx(lock, 0, 1, 0, &o);
166 ::CloseHandle(lock);
168 #else
169 // If we're the last instance, then unlink the lock file. There is a race
170 // condition here that may cause an instance to fail to open the same inode
171 // as another even though they use the same path, but there's no reasonable
172 // way to avoid that without skipping deleting the file at all, so we accept
173 // that risk.
174 bool otherInstance = true;
175 if (IsOtherInstanceRunning(lock, &otherInstance) && !otherInstance) {
176 // Recover the file's path so we can unlink it.
177 // There's no error checking in here because we're content to let the file
178 // hang around if any of this fails (which can happen if for example we're
179 // on a system where /proc/self/fd does not exist); this is a zero-byte
180 // file in the tmp directory after all.
181 UniquePtr<NS_tchar[]> linkPath = MakeUnique<NS_tchar[]>(MAXPATHLEN + 1);
182 NS_tsnprintf(linkPath.get(), MAXPATHLEN + 1, "/proc/self/fd/%d", lock);
183 UniquePtr<NS_tchar[]> lockFilePath =
184 MakeUnique<NS_tchar[]>(MAXPATHLEN + 1);
185 if (::readlink(linkPath.get(), lockFilePath.get(), MAXPATHLEN + 1) !=
186 -1) {
187 ::unlink(lockFilePath.get());
190 // Now close the lock file, which will release the lock.
191 ::close(lock);
192 #endif
196 bool IsOtherInstanceRunning(MultiInstLockHandle lock, bool* aResult) {
197 // Every running instance has opened a readonly lock, and read locks prevent
198 // write locks from being opened, so to see if we are the only instance, we
199 // attempt to take a write lock, and if it succeeds then that must mean there
200 // are no other read locks open and therefore no other instances.
201 if (lock == MULTI_INSTANCE_LOCK_HANDLE_ERROR) {
202 return false;
205 #ifdef XP_WIN
206 // We need to release the lock we're holding before we would be allowed to
207 // take an exclusive lock, and if that succeeds we need to release it too
208 // in order to get our shared lock back. This procedure is not atomic, so we
209 // accept the risk of the scheduler deciding to ruin our day between these
210 // operations; we'd get a false negative in a different instance's check.
211 OVERLAPPED o = {0};
212 // Release our current shared lock.
213 if (!::UnlockFileEx(lock, 0, 1, 0, &o)) {
214 return false;
216 // Attempt to take an exclusive lock.
217 bool rv = false;
218 if (::LockFileEx(lock, LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY, 0,
219 1, 0, &o)) {
220 // We got the exclusive lock, so now release it.
221 ::UnlockFileEx(lock, 0, 1, 0, &o);
222 *aResult = false;
223 rv = true;
224 } else if (::GetLastError() == ERROR_LOCK_VIOLATION) {
225 // We didn't get the exclusive lock because of outstanding shared locks.
226 *aResult = true;
227 rv = true;
229 // Attempt to reclaim the shared lock we released at the beginning.
230 if (!::LockFileEx(lock, LOCKFILE_FAIL_IMMEDIATELY, 0, 1, 0, &o)) {
231 rv = false;
233 return rv;
235 #else
236 // See if we would be allowed to set a write lock (no need to actually do so).
237 struct flock l = {0};
238 l.l_start = 0;
239 l.l_len = 0;
240 l.l_type = F_WRLCK;
241 if (::fcntl(lock, F_GETLK, &l)) {
242 return false;
244 *aResult = l.l_type != F_UNLCK;
245 return true;
247 #endif
250 already_AddRefed<nsIFile> GetNormalizedAppFile(nsIFile* aAppFile) {
251 // If we're given an app file, use it; otherwise, get it from the ambient
252 // directory service.
253 nsresult rv;
254 nsCOMPtr<nsIFile> appFile;
255 if (aAppFile) {
256 rv = aAppFile->Clone(getter_AddRefs(appFile));
257 NS_ENSURE_SUCCESS(rv, nullptr);
258 } else {
259 nsCOMPtr<nsIProperties> dirSvc =
260 do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID);
261 NS_ENSURE_TRUE(dirSvc, nullptr);
263 rv = dirSvc->Get(XRE_EXECUTABLE_FILE, NS_GET_IID(nsIFile),
264 getter_AddRefs(appFile));
265 NS_ENSURE_SUCCESS(rv, nullptr);
268 // It is possible that the path we have is on a case insensitive
269 // filesystem in which case the path may vary depending on how the
270 // application is called. We want to normalize the case somehow.
271 // On Linux XRE_EXECUTABLE_FILE already seems to be set to the correct path.
273 // See similar nsXREDirProvider::GetInstallHash. The main difference here is
274 // to allow lookup to fail on OSX, because some tests use a nonexistent
275 // appFile.
276 #ifdef XP_WIN
277 // Windows provides a way to get the correct case.
278 if (!mozilla::widget::WinUtils::ResolveJunctionPointsAndSymLinks(appFile)) {
279 NS_WARNING("Failed to resolve install directory.");
281 #elif defined(MOZ_WIDGET_COCOA)
282 // On OSX roundtripping through an FSRef fixes the case.
283 FSRef ref;
284 nsCOMPtr<nsILocalFileMac> macFile = do_QueryInterface(appFile);
285 if (macFile && NS_SUCCEEDED(macFile->GetFSRef(&ref)) &&
286 NS_SUCCEEDED(
287 NS_NewLocalFileWithFSRef(&ref, true, getter_AddRefs(macFile)))) {
288 appFile = static_cast<nsIFile*>(macFile);
289 } else {
290 NS_WARNING("Failed to resolve install directory.");
292 #endif
294 return appFile.forget();
297 }; // namespace mozilla