1 /* -*- indent-tabs-mode: nil; js-indent-level: 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 import { FileUtils } from "resource://gre/modules/FileUtils.sys.mjs";
7 import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
11 ChromeUtils.defineESModuleGetters(lazy, {
12 UpdateLog: "resource://gre/modules/UpdateLog.sys.mjs",
15 const DIR_UPDATES = "updates";
16 const FILE_UPDATE_STATUS = "update.status";
18 const KEY_UPDROOT = "UpdRootD";
19 const KEY_OLD_UPDROOT = "OldUpdRootD";
21 // The pref prefix below should have the hash of the install path appended to
22 // ensure that this is a per-installation pref (i.e. to ensure that migration
23 // happens for every install rather than once per profile)
24 const PREF_PREFIX_UPDATE_DIR_MIGRATED = "app.update.migrated.updateDir3.";
25 const PREF_APP_UPDATE_ALTUPDATEDIRPATH = "app.update.altUpdateDirPath";
27 function getUpdateBaseDirNoCreate() {
28 if (Cu.isInAutomation) {
29 // This allows tests to use an alternate updates directory so they can test
31 const MAGIC_TEST_ROOT_PREFIX = "<test-root>";
32 const PREF_TEST_ROOT = "mochitest.testRoot";
33 let alternatePath = Services.prefs.getCharPref(
34 PREF_APP_UPDATE_ALTUPDATEDIRPATH,
37 if (alternatePath && alternatePath.startsWith(MAGIC_TEST_ROOT_PREFIX)) {
38 let testRoot = Services.prefs.getCharPref(PREF_TEST_ROOT);
39 let relativePath = alternatePath.substring(MAGIC_TEST_ROOT_PREFIX.length);
40 if (AppConstants.platform == "win") {
41 relativePath = relativePath.replace(/\//g, "\\");
43 alternatePath = testRoot + relativePath;
44 let updateDir = Cc["@mozilla.org/file/local;1"].createInstance(
47 updateDir.initWithPath(alternatePath);
49 "getUpdateBaseDirNoCreate returning test directory, path: " +
56 return FileUtils.getDir(KEY_UPDROOT, []);
59 export function UpdateServiceStub() {
60 LOG("UpdateServiceStub - Begin.");
62 let updateDir = getUpdateBaseDirNoCreate();
63 let prefUpdateDirMigrated =
64 PREF_PREFIX_UPDATE_DIR_MIGRATED + updateDir.leafName;
66 let statusFile = updateDir;
67 statusFile.append(DIR_UPDATES);
68 statusFile.append("0");
69 statusFile.append(FILE_UPDATE_STATUS);
70 updateDir = null; // We don't need updateDir anymore, plus now its nsIFile
71 // contains the status file's path
73 // We may need to migrate update data
75 AppConstants.platform == "win" &&
76 !Services.prefs.getBoolPref(prefUpdateDirMigrated, false)
78 Services.prefs.setBoolPref(prefUpdateDirMigrated, true);
80 migrateUpdateDirectory();
82 // For the most part, migrateUpdateDirectory() catches its own errors.
83 // But there are technically things that could happen that might not be
84 // caught, like nsIFile.parent or nsIFile.append could unexpectedly fail.
85 // So we will catch any errors here, just in case.
87 `UpdateServiceStub:UpdateServiceStub Failed to migrate update ` +
88 `directory. Exception: ${ex}`
93 // If the update.status file exists then initiate post update processing.
94 if (statusFile.exists()) {
95 let aus = Cc["@mozilla.org/updates/update-service;1"]
96 .getService(Ci.nsIApplicationUpdateService)
97 .QueryInterface(Ci.nsIObserver);
98 aus.observe(null, "post-update-processing", "");
102 UpdateServiceStub.prototype = {
104 classID: Components.ID("{e43b0010-04ba-4da6-b523-1f92580bc150}"),
105 QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
109 * This function should be called when there are files in the old update
110 * directory that may need to be migrated to the new update directory.
112 function migrateUpdateDirectory() {
113 LOG("UpdateServiceStub:migrateUpdateDirectory Performing migration");
115 let sourceRootDir = FileUtils.getDir(KEY_OLD_UPDROOT, []);
116 let destRootDir = FileUtils.getDir(KEY_UPDROOT, []);
117 let hash = destRootDir.leafName;
119 if (!sourceRootDir.exists()) {
120 // Nothing to migrate.
124 // List of files to migrate. Each is specified as a list of path components.
127 ["active-update.xml"],
128 ["update-config.json"],
129 ["updates", "last-update.log"],
130 ["updates", "backup-update.log"],
131 ["updates", "downloading", FILE_UPDATE_STATUS],
132 ["updates", "downloading", "update.mar"],
133 ["updates", "0", FILE_UPDATE_STATUS],
134 ["updates", "0", "update.mar"],
135 ["updates", "0", "update.version"],
136 ["updates", "0", "update.log"],
137 ["backgroundupdate", "datareporting", "glean", "db", "data.safe.bin"],
140 // Before we copy anything, double check that a different profile hasn't
141 // already performed migration. If we don't have the necessary permissions to
142 // remove the pre-migration files, we don't want to copy any old files and
143 // potentially make the current update state inconsistent.
144 for (let pathComponents of toMigrate) {
145 // Assemble the destination nsIFile.
146 let destFile = destRootDir.clone();
147 for (let pathComponent of pathComponents) {
148 destFile.append(pathComponent);
151 if (destFile.exists()) {
153 `UpdateServiceStub:migrateUpdateDirectory Aborting migration because ` +
154 `"${destFile.path}" already exists.`
160 // Before we migrate everything in toMigrate, there are a few things that
161 // need special handling.
162 let sourceRootParent = sourceRootDir.parent.parent;
163 let destRootParent = destRootDir.parent.parent;
165 let profileCountFile = sourceRootParent.clone();
166 profileCountFile.append(`profile_count_${hash}.json`);
167 migrateFile(profileCountFile, destRootParent);
169 const updatePingPrefix = `uninstall_ping_${hash}_`;
170 const updatePingSuffix = ".json";
172 for (let file of sourceRootParent.directoryEntries) {
174 file.leafName.startsWith(updatePingPrefix) &&
175 file.leafName.endsWith(updatePingSuffix)
177 migrateFile(file, destRootParent);
181 // migrateFile should catch its own errors, but it is possible that
182 // sourceRootParent.directoryEntries could throw.
184 `UpdateServiceStub:migrateUpdateDirectory Failed to migrate uninstall ` +
185 `ping. Exception: ${ex}`
189 // Migrate "backgroundupdate.moz_log" and child process logs like
190 // "backgroundupdate.child-1.moz_log".
191 const backgroundLogPrefix = `backgroundupdate`;
192 const backgroundLogSuffix = ".moz_log";
194 for (let file of sourceRootDir.directoryEntries) {
196 file.leafName.startsWith(backgroundLogPrefix) &&
197 file.leafName.endsWith(backgroundLogSuffix)
199 migrateFile(file, destRootDir);
204 `UpdateServiceStub:migrateUpdateDirectory Failed to migrate background ` +
205 `log file. Exception: ${ex}`
209 const pendingPingRelDir =
210 "backgroundupdate\\datareporting\\glean\\pending_pings";
211 let pendingPingSourceDir = sourceRootDir.clone();
212 pendingPingSourceDir.appendRelativePath(pendingPingRelDir);
213 let pendingPingDestDir = destRootDir.clone();
214 pendingPingDestDir.appendRelativePath(pendingPingRelDir);
215 // Pending ping filenames are UUIDs.
216 const pendingPingFilenameRegex =
217 /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
218 if (pendingPingSourceDir.exists()) {
220 for (let file of pendingPingSourceDir.directoryEntries) {
221 if (pendingPingFilenameRegex.test(file.leafName)) {
222 migrateFile(file, pendingPingDestDir);
226 // migrateFile should catch its own errors, but it is possible that
227 // pendingPingSourceDir.directoryEntries could throw.
229 `UpdateServiceStub:migrateUpdateDirectory Failed to migrate ` +
230 `pending pings. Exception: ${ex}`
235 // Migrate everything in toMigrate.
236 for (let pathComponents of toMigrate) {
237 let filename = pathComponents.pop();
239 // Assemble the source and destination nsIFile's.
240 let sourceFile = sourceRootDir.clone();
241 let destDir = destRootDir.clone();
242 for (let pathComponent of pathComponents) {
243 sourceFile.append(pathComponent);
244 destDir.append(pathComponent);
246 sourceFile.append(filename);
248 migrateFile(sourceFile, destDir);
251 // There is no reason to keep this file, and it often hangs around and could
252 // interfere with cleanup.
253 let updateLockFile = sourceRootParent.clone();
254 updateLockFile.append(`UpdateLock-${hash}`);
256 updateLockFile.remove(false);
259 // We want to recursively remove empty directories out of the sourceRootDir.
260 // And if that was the only remaining update directory in sourceRootParent,
261 // we want to remove that too. But we don't want to recurse into other update
262 // directories in sourceRootParent.
264 // Potentially removes "C:\ProgramData\Mozilla\updates\<hash>" and
266 cleanupDir(sourceRootDir, true);
267 // Potentially removes "C:\ProgramData\Mozilla\updates"
268 cleanupDir(sourceRootDir.parent, false);
269 // Potentially removes "C:\ProgramData\Mozilla"
270 cleanupDir(sourceRootParent, false);
274 * Attempts to move the source file to the destination directory. If the file
275 * cannot be moved, we attempt to copy it and remove the original. All errors
276 * are logged, but no exceptions are thrown. Both arguments must be of type
277 * nsIFile and are expected to be regular files.
279 * Non-existent files are silently ignored.
281 * The reason that we are migrating is to deal with problematic inherited
282 * permissions. But, luckily, neither nsIFile.moveTo nor nsIFile.copyTo preserve
283 * inherited permissions.
285 function migrateFile(sourceFile, destDir) {
286 if (!sourceFile.exists()) {
290 if (sourceFile.isDirectory()) {
292 `UpdateServiceStub:migrateFile Aborting attempt to migrate ` +
293 `"${sourceFile.path}" because it is a directory.`
298 // Create destination directory.
300 // Pass an arbitrary value for permissions. Windows doesn't use octal
301 // permissions, so that value doesn't really do anything.
302 destDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0);
304 if (ex.result != Cr.NS_ERROR_FILE_ALREADY_EXISTS) {
306 `UpdateServiceStub:migrateFile Unable to create destination ` +
307 `directory "${destDir.path}": ${ex}`
313 sourceFile.moveTo(destDir, null);
318 sourceFile.copyTo(destDir, null);
321 `UpdateServiceStub:migrateFile Failed to migrate file from ` +
322 `"${sourceFile.path}" to "${destDir.path}". Exception: ${ex}`
328 sourceFile.remove(false);
331 `UpdateServiceStub:migrateFile Successfully migrated file from ` +
332 `"${sourceFile.path}" to "${destDir.path}", but was unable to remove ` +
333 `the original. Exception: ${ex}`
339 * If recurse is true, recurses through the directory's contents. Any empty
340 * directories are removed. Directories with remaining files are left behind.
342 * If recurse if false, we delete the directory passed as long as it is empty.
344 * All errors are silenced and not thrown.
346 * Returns true if the directory passed in was removed. Otherwise false.
348 function cleanupDir(dir, recurse) {
349 let directoryEmpty = true;
351 for (let file of dir.directoryEntries) {
353 // If we aren't recursing, bail out after we find a single file. The
354 // directory isn't empty so we can't delete it, and we aren't going to
355 // clean out and remove any other directories.
358 if (file.isDirectory()) {
359 if (!cleanupDir(file, recurse)) {
360 directoryEmpty = false;
363 directoryEmpty = false;
367 // If any of our nsIFile calls fail, just err on the side of caution and
368 // don't delete anything.
372 if (directoryEmpty) {
382 * Logs a string to the error console.
384 * The string to write to the error console.
386 function LOG(string) {
387 lazy.UpdateLog.logPrefixedString("AUS:STB", string);