Skip to content

Commit

Permalink
Bug 1700795. Detect running from .dmg on macOS and report telemetry. …
Browse files Browse the repository at this point in the history
…r=mstange,mossop

Differential Revision: https://phabricator.services.mozilla.com/D121567
  • Loading branch information
jwatt committed Aug 5, 2021
1 parent 558464a commit 3bb5899
Show file tree
Hide file tree
Showing 7 changed files with 164 additions and 0 deletions.
22 changes: 22 additions & 0 deletions toolkit/components/telemetry/Scalars.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5668,6 +5668,28 @@ startup:
record_in_processes:
- main

first_run_is_from_dmg:
bug_numbers:
- 1700795
description: >
Recorded on first run of a Firefox install on macOS, with a boolean value
indicating whether Firefox is being run directly from .dmg without
installing, or not. Note if running from a .dmg we will get a new ping
everytime the .dmg is remounted due to App Translocation.
expires: "99"
keyed: false
kind: boolean
notification_emails:
- [email protected]
- [email protected]
operating_systems:
- mac
products:
- 'firefox'
record_in_processes:
- 'main'
release_channel_collection: opt-out

script.preloader:
mainthread_recompile:
bug_numbers:
Expand Down
5 changes: 5 additions & 0 deletions toolkit/profile/nsIToolkitProfileService.idl
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,11 @@ interface nsIToolkitProfileService : nsISupports
*/
readonly attribute unsigned long profileCount;

/**
* Returns true if this is the first time the current install has run.
*/
[noscript, notxpcom] readonly attribute boolean isFirstRun;

/**
* Flush the profiles list file. This will fail with
* NS_ERROR_DATABASE_CHANGED if the files on disk have changed since the
Expand Down
3 changes: 3 additions & 0 deletions toolkit/profile/nsToolkitProfileService.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2163,6 +2163,9 @@ nsToolkitProfileService::GetProfileCount(uint32_t* aResult) {
return NS_OK;
}

NS_IMETHODIMP_(bool)
nsToolkitProfileService::GetIsFirstRun() { return mIsFirstRun; }

NS_IMETHODIMP
nsToolkitProfileService::Flush() {
if (GetIsListOutdated()) {
Expand Down
25 changes: 25 additions & 0 deletions toolkit/xre/MacRunFromDmgUtils.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// This file defines the interface between Cocoa-specific Obj-C++ and generic
// C++, so it itself cannot have any Obj-C bits in it.

#ifndef MacRunFromDmgUtils_h_
#define MacRunFromDmgUtils_h_

namespace mozilla {
namespace MacRunFromDmgUtils {

/**
* Returns true if the app is running from the read-only filesystem of a
* mounted .dmg. Returns false if not, or if we fail to determine whether it
* is.
*/
bool IsAppRunningFromDmg();

} // namespace MacRunFromDmgUtils
} // namespace mozilla

#endif
97 changes: 97 additions & 0 deletions toolkit/xre/MacRunFromDmgUtils.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include <ApplicationServices/ApplicationServices.h>
#include <CoreFoundation/CoreFoundation.h>
#include <CoreServices/CoreServices.h>
#include <IOKit/IOKitLib.h>
#include <string.h>
#include <sys/mount.h>
#include <sys/param.h>

#include "MacRunFromDmgUtils.h"

#include "nsCocoaFeatures.h"
#include "nsCommandLine.h"
#include "nsCommandLineServiceMac.h"
#include "nsILocalFileMac.h"
#include "nsIMacDockSupport.h"
#include "nsObjCExceptions.h"
#include "nsString.h"

// For IOKit docs, see:
// https://developer.apple.com/documentation/iokit
// https://developer.apple.com/library/archive/documentation/DeviceDrivers/Conceptual/IOKitFundamentals/

namespace mozilla {
namespace MacRunFromDmgUtils {

bool IsAppRunningFromDmg() {
NS_OBJC_BEGIN_TRY_BLOCK_RETURN;

const char* path = [[[NSBundle mainBundle] bundlePath] fileSystemRepresentation];

struct statfs statfsBuf;
if (statfs(path, &statfsBuf) != 0) {
return false;
}

// Optimization to minimize impact on startup time:
if (!(statfsBuf.f_flags & MNT_RDONLY)) {
return false;
}

// Get the "BSD device name" ("diskXsY", as found in /dev) of the filesystem
// our app is on so we can use IOKit to get its IOMedia object:
const char devDirPath[] = "/dev/";
const int devDirPathLength = strlen(devDirPath);
if (strncmp(statfsBuf.f_mntfromname, devDirPath, devDirPathLength) != 0) {
// Does this ever happen?
return false;
}
const char* bsdDeviceName = statfsBuf.f_mntfromname + devDirPathLength;

// Get the IOMedia object:
// (Note: IOServiceGetMatchingServices takes ownership of serviceDict's ref.)
CFMutableDictionaryRef serviceDict = IOBSDNameMatching(kIOMasterPortDefault, 0, bsdDeviceName);
if (!serviceDict) {
return false;
}
io_service_t media = IOServiceGetMatchingService(kIOMasterPortDefault, serviceDict);
if (!media || !IOObjectConformsTo(media, "IOMedia")) {
return false;
}

// Search the parent chain for a service implementing the disk image class
// (taking care to start with `media` itself):
io_service_t imageDrive = IO_OBJECT_NULL;
io_iterator_t iter;
if (IORegistryEntryCreateIterator(media, kIOServicePlane,
kIORegistryIterateRecursively | kIORegistryIterateParents,
&iter) != KERN_SUCCESS) {
IOObjectRelease(media);
return false;
}
const char* imageClass =
nsCocoaFeatures::macOSVersionMajor() >= 12 ? "AppleDiskImageDevice" : "IOHDIXHDDrive";
for (imageDrive = media; imageDrive; imageDrive = IOIteratorNext(iter)) {
if (IOObjectConformsTo(imageDrive, imageClass)) {
break;
}
IOObjectRelease(imageDrive);
}
IOObjectRelease(iter);

if (imageDrive) {
IOObjectRelease(imageDrive);
return true;
}
return false;

NS_OBJC_END_TRY_BLOCK_RETURN(false);
}

} // namespace MacRunFromDmgUtils
} // namespace mozilla
4 changes: 4 additions & 0 deletions toolkit/xre/moz.build
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,14 @@ if CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows":
"LauncherRegistryInfo.cpp",
]
elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa":
EXPORTS.mozilla += [
"MacRunFromDmgUtils.h",
]
UNIFIED_SOURCES += [
"MacApplicationDelegate.mm",
"MacAutoreleasePool.mm",
"MacLaunchHelper.mm",
"MacRunFromDmgUtils.mm",
"nsCommandLineServiceMac.mm",
"nsNativeAppSupportCocoa.mm",
"updaterfileutils_osx.mm",
Expand Down
8 changes: 8 additions & 0 deletions toolkit/xre/nsAppRunner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
# include "MacLaunchHelper.h"
# include "MacApplicationDelegate.h"
# include "MacAutoreleasePool.h"
# include "MacRunFromDmgUtils.h"
// these are needed for sysctl
# include <sys/types.h>
# include <sys/sysctl.h>
Expand Down Expand Up @@ -5475,6 +5476,13 @@ int XREMain::XRE_main(int argc, char* argv[], const BootstrapConfig& aConfig) {
rv = mScopedXPCOM->Initialize(/* aInitJSContext = */ false);
NS_ENSURE_SUCCESS(rv, 1);

#ifdef XP_MACOSX
if (mProfileSvc->GetIsFirstRun()) {
Telemetry::ScalarSet(Telemetry::ScalarID::STARTUP_FIRST_RUN_IS_FROM_DMG,
MacRunFromDmgUtils::IsAppRunningFromDmg());
}
#endif

// run!
rv = XRE_mainRun();

Expand Down

0 comments on commit 3bb5899

Please sign in to comment.