Bug 1874684 - Part 17: Fix uninitialised variable warnings from clang-tidy. r=allstarschh
[gecko.git] / toolkit / mozapps / update / UpdateServiceStub.sys.mjs
blob415c6bbe80533aad9a3a987bec44e9f353eca71f
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";
9 const lazy = {};
11 ChromeUtils.defineESModuleGetters(lazy, {
12   UpdateLog: "resource://gre/modules/UpdateLog.sys.mjs",
13 });
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
30     // startup behavior.
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,
35       null
36     );
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, "\\");
42       }
43       alternatePath = testRoot + relativePath;
44       let updateDir = Cc["@mozilla.org/file/local;1"].createInstance(
45         Ci.nsIFile
46       );
47       updateDir.initWithPath(alternatePath);
48       LOG(
49         "getUpdateBaseDirNoCreate returning test directory, path: " +
50           updateDir.path
51       );
52       return updateDir;
53     }
54   }
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
74   if (
75     AppConstants.platform == "win" &&
76     !Services.prefs.getBoolPref(prefUpdateDirMigrated, false)
77   ) {
78     Services.prefs.setBoolPref(prefUpdateDirMigrated, true);
79     try {
80       migrateUpdateDirectory();
81     } catch (ex) {
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.
86       LOG(
87         `UpdateServiceStub:UpdateServiceStub Failed to migrate update ` +
88           `directory. Exception: ${ex}`
89       );
90     }
91   }
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", "");
99   }
102 UpdateServiceStub.prototype = {
103   observe() {},
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.
111  */
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.
121     return;
122   }
124   // List of files to migrate. Each is specified as a list of path components.
125   const toMigrate = [
126     ["updates.xml"],
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"],
138   ];
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);
149     }
151     if (destFile.exists()) {
152       LOG(
153         `UpdateServiceStub:migrateUpdateDirectory Aborting migration because ` +
154           `"${destFile.path}" already exists.`
155       );
156       return;
157     }
158   }
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";
171   try {
172     for (let file of sourceRootParent.directoryEntries) {
173       if (
174         file.leafName.startsWith(updatePingPrefix) &&
175         file.leafName.endsWith(updatePingSuffix)
176       ) {
177         migrateFile(file, destRootParent);
178       }
179     }
180   } catch (ex) {
181     // migrateFile should catch its own errors, but it is possible that
182     // sourceRootParent.directoryEntries could throw.
183     LOG(
184       `UpdateServiceStub:migrateUpdateDirectory Failed to migrate uninstall ` +
185         `ping. Exception: ${ex}`
186     );
187   }
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";
193   try {
194     for (let file of sourceRootDir.directoryEntries) {
195       if (
196         file.leafName.startsWith(backgroundLogPrefix) &&
197         file.leafName.endsWith(backgroundLogSuffix)
198       ) {
199         migrateFile(file, destRootDir);
200       }
201     }
202   } catch (ex) {
203     LOG(
204       `UpdateServiceStub:migrateUpdateDirectory Failed to migrate background ` +
205         `log file. Exception: ${ex}`
206     );
207   }
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()) {
219     try {
220       for (let file of pendingPingSourceDir.directoryEntries) {
221         if (pendingPingFilenameRegex.test(file.leafName)) {
222           migrateFile(file, pendingPingDestDir);
223         }
224       }
225     } catch (ex) {
226       // migrateFile should catch its own errors, but it is possible that
227       // pendingPingSourceDir.directoryEntries could throw.
228       LOG(
229         `UpdateServiceStub:migrateUpdateDirectory Failed to migrate ` +
230           `pending pings. Exception: ${ex}`
231       );
232     }
233   }
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);
245     }
246     sourceFile.append(filename);
248     migrateFile(sourceFile, destDir);
249   }
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}`);
255   try {
256     updateLockFile.remove(false);
257   } catch (ex) {}
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.
263   //
264   // Potentially removes "C:\ProgramData\Mozilla\updates\<hash>" and
265   // subdirectories.
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.
284  */
285 function migrateFile(sourceFile, destDir) {
286   if (!sourceFile.exists()) {
287     return;
288   }
290   if (sourceFile.isDirectory()) {
291     LOG(
292       `UpdateServiceStub:migrateFile Aborting attempt to migrate ` +
293         `"${sourceFile.path}" because it is a directory.`
294     );
295     return;
296   }
298   // Create destination directory.
299   try {
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);
303   } catch (ex) {
304     if (ex.result != Cr.NS_ERROR_FILE_ALREADY_EXISTS) {
305       LOG(
306         `UpdateServiceStub:migrateFile Unable to create destination ` +
307           `directory "${destDir.path}": ${ex}`
308       );
309     }
310   }
312   try {
313     sourceFile.moveTo(destDir, null);
314     return;
315   } catch (ex) {}
317   try {
318     sourceFile.copyTo(destDir, null);
319   } catch (ex) {
320     LOG(
321       `UpdateServiceStub:migrateFile Failed to migrate file from ` +
322         `"${sourceFile.path}" to "${destDir.path}". Exception: ${ex}`
323     );
324     return;
325   }
327   try {
328     sourceFile.remove(false);
329   } catch (ex) {
330     LOG(
331       `UpdateServiceStub:migrateFile Successfully migrated file from ` +
332         `"${sourceFile.path}" to "${destDir.path}", but was unable to remove ` +
333         `the original. Exception: ${ex}`
334     );
335   }
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.
347  */
348 function cleanupDir(dir, recurse) {
349   let directoryEmpty = true;
350   try {
351     for (let file of dir.directoryEntries) {
352       if (!recurse) {
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.
356         return false;
357       }
358       if (file.isDirectory()) {
359         if (!cleanupDir(file, recurse)) {
360           directoryEmpty = false;
361         }
362       } else {
363         directoryEmpty = false;
364       }
365     }
366   } catch (ex) {
367     // If any of our nsIFile calls fail, just err on the side of caution and
368     // don't delete anything.
369     return false;
370   }
372   if (directoryEmpty) {
373     try {
374       dir.remove(false);
375       return true;
376     } catch (ex) {}
377   }
378   return false;
382  * Logs a string to the error console.
383  * @param   string
384  *          The string to write to the error console.
385  */
386 function LOG(string) {
387   lazy.UpdateLog.logPrefixedString("AUS:STB", string);