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/. */
5
6const { XPCOMUtils } = ChromeUtils.import(
7  "resource://gre/modules/XPCOMUtils.jsm"
8);
9const { FileUtils } = ChromeUtils.import(
10  "resource://gre/modules/FileUtils.jsm"
11);
12const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
13const { AppConstants } = ChromeUtils.import(
14  "resource://gre/modules/AppConstants.jsm"
15);
16
17const DIR_UPDATES = "updates";
18const FILE_UPDATE_STATUS = "update.status";
19const FILE_ACTIVE_UPDATE_XML = "active-update.xml";
20const FILE_LAST_UPDATE_LOG = "last-update.log";
21const FILE_UPDATES_XML = "updates.xml";
22const FILE_UPDATE_LOG = "update.log";
23const FILE_UPDATE_MESSAGES = "update_messages.log";
24const FILE_BACKUP_MESSAGES = "update_messages_old.log";
25
26const KEY_UPDROOT = "UpdRootD";
27const KEY_OLD_UPDROOT = "OldUpdRootD";
28const KEY_PROFILE_DIR = "ProfD";
29
30// The pref prefix below should have the hash of the install path appended to
31// ensure that this is a per-installation pref (i.e. to ensure that migration
32// happens for every install rather than once per profile)
33const PREF_PREFIX_UPDATE_DIR_MIGRATED = "app.update.migrated.updateDir2.";
34const PREF_APP_UPDATE_ALTUPDATEDIRPATH = "app.update.altUpdateDirPath";
35const PREF_APP_UPDATE_LOG = "app.update.log";
36const PREF_APP_UPDATE_FILE_LOGGING = "app.update.log.file";
37
38XPCOMUtils.defineLazyGetter(this, "gLogEnabled", function aus_gLogEnabled() {
39  return Services.prefs.getBoolPref(PREF_APP_UPDATE_LOG, false);
40});
41
42function getUpdateBaseDirNoCreate() {
43  if (Cu.isInAutomation) {
44    // This allows tests to use an alternate updates directory so they can test
45    // startup behavior.
46    const MAGIC_TEST_ROOT_PREFIX = "<test-root>";
47    const PREF_TEST_ROOT = "mochitest.testRoot";
48    let alternatePath = Services.prefs.getCharPref(
49      PREF_APP_UPDATE_ALTUPDATEDIRPATH,
50      null
51    );
52    if (alternatePath && alternatePath.startsWith(MAGIC_TEST_ROOT_PREFIX)) {
53      let testRoot = Services.prefs.getCharPref(PREF_TEST_ROOT);
54      let relativePath = alternatePath.substring(MAGIC_TEST_ROOT_PREFIX.length);
55      if (AppConstants.platform == "win") {
56        relativePath = relativePath.replace(/\//g, "\\");
57      }
58      alternatePath = testRoot + relativePath;
59      let updateDir = Cc["@mozilla.org/file/local;1"].createInstance(
60        Ci.nsIFile
61      );
62      updateDir.initWithPath(alternatePath);
63      LOG(
64        "getUpdateBaseDirNoCreate returning test directory, path: " +
65          updateDir.path
66      );
67      return updateDir;
68    }
69  }
70
71  return FileUtils.getDir(KEY_UPDROOT, [], false);
72}
73
74function UpdateServiceStub() {
75  let updateDir = getUpdateBaseDirNoCreate();
76  let prefUpdateDirMigrated =
77    PREF_PREFIX_UPDATE_DIR_MIGRATED + updateDir.leafName;
78
79  let statusFile = updateDir;
80  statusFile.append(DIR_UPDATES);
81  statusFile.append("0");
82  statusFile.append(FILE_UPDATE_STATUS);
83  updateDir = null; // We don't need updateDir anymore, plus now its nsIFile
84  // contains the status file's path
85
86  // We may need to migrate update data
87  if (
88    AppConstants.platform == "win" &&
89    !Services.prefs.getBoolPref(prefUpdateDirMigrated, false)
90  ) {
91    migrateUpdateDirectory();
92    Services.prefs.setBoolPref(prefUpdateDirMigrated, true);
93  }
94
95  // Prevent file logging from persisting for more than a session by disabling
96  // it on startup.
97  if (Services.prefs.getBoolPref(PREF_APP_UPDATE_FILE_LOGGING, false)) {
98    deactivateUpdateLogFile();
99  }
100
101  // If the update.status file exists then initiate post update processing.
102  if (statusFile.exists()) {
103    let aus = Cc["@mozilla.org/updates/update-service;1"]
104      .getService(Ci.nsIApplicationUpdateService)
105      .QueryInterface(Ci.nsIObserver);
106    aus.observe(null, "post-update-processing", "");
107  }
108}
109UpdateServiceStub.prototype = {
110  observe() {},
111  classID: Components.ID("{e43b0010-04ba-4da6-b523-1f92580bc150}"),
112  QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
113};
114
115var EXPORTED_SYMBOLS = ["UpdateServiceStub"];
116
117function deactivateUpdateLogFile() {
118  LOG("Application update file logging being automatically turned off");
119  Services.prefs.setBoolPref(PREF_APP_UPDATE_FILE_LOGGING, false);
120  let logFile = Services.dirsvc.get(KEY_PROFILE_DIR, Ci.nsIFile);
121  logFile.append(FILE_UPDATE_MESSAGES);
122
123  try {
124    logFile.moveTo(null, FILE_BACKUP_MESSAGES);
125  } catch (e) {
126    LOG(
127      "Failed to backup update messages log (" +
128        e +
129        "). Attempting to " +
130        "remove it."
131    );
132    try {
133      logFile.remove(false);
134    } catch (e) {
135      LOG("Also failed to remove the update messages log: " + e);
136    }
137  }
138}
139
140/**
141 * This function should be called when there are files in the old update
142 * directory that may need to be migrated to the new update directory.
143 */
144function migrateUpdateDirectory() {
145  let sourceRootDir = FileUtils.getDir(KEY_OLD_UPDROOT, [], false);
146  let destRootDir = FileUtils.getDir(KEY_UPDROOT, [], false);
147
148  if (!sourceRootDir.exists()) {
149    LOG(
150      "UpdateServiceStub:_migrateUpdateDirectory - Abort: No migration " +
151        "necessary. Nothing to migrate."
152    );
153    return;
154  }
155
156  if (destRootDir.exists()) {
157    // Migration must have already been done by another user
158    LOG(
159      "UpdateServiceStub:_migrateUpdateDirectory - migrated and unmigrated " +
160        "update directories found. Deleting the unmigrated directory: " +
161        sourceRootDir.path
162    );
163    try {
164      sourceRootDir.remove(true);
165    } catch (e) {
166      LOG(
167        "UpdateServiceStub:_migrateUpdateDirectory - Deletion of " +
168          "unmigrated directory failed. Exception: " +
169          e
170      );
171    }
172    return;
173  }
174
175  let sourceUpdateDir = sourceRootDir.clone();
176  sourceUpdateDir.append(DIR_UPDATES);
177  let destUpdateDir = destRootDir.clone();
178  destUpdateDir.append(DIR_UPDATES);
179
180  let sourcePatchDir = sourceUpdateDir.clone();
181  sourcePatchDir.append("0");
182  let destPatchDir = destUpdateDir.clone();
183  destPatchDir.append("0");
184
185  let sourceStatusFile = sourcePatchDir.clone();
186  sourceStatusFile.append(FILE_UPDATE_STATUS);
187  let destStatusFile = destPatchDir.clone();
188  destStatusFile.append(FILE_UPDATE_STATUS);
189
190  let sourceActiveXML = sourceRootDir.clone();
191  sourceActiveXML.append(FILE_ACTIVE_UPDATE_XML);
192  try {
193    sourceActiveXML.moveTo(destRootDir, sourceActiveXML.leafName);
194  } catch (e) {
195    LOG(
196      "UpdateServiceStub:_migrateUpdateDirectory - Unable to move active " +
197        "update XML file. Exception: " +
198        e
199    );
200  }
201
202  let sourceUpdateXML = sourceRootDir.clone();
203  sourceUpdateXML.append(FILE_UPDATES_XML);
204  try {
205    sourceUpdateXML.moveTo(destRootDir, sourceUpdateXML.leafName);
206  } catch (e) {
207    LOG(
208      "UpdateServiceStub:_migrateUpdateDirectory - Unable to move " +
209        "update XML file. Exception: " +
210        e
211    );
212  }
213
214  let sourceUpdateLog = sourcePatchDir.clone();
215  sourceUpdateLog.append(FILE_UPDATE_LOG);
216  try {
217    sourceUpdateLog.moveTo(destPatchDir, sourceUpdateLog.leafName);
218  } catch (e) {
219    LOG(
220      "UpdateServiceStub:_migrateUpdateDirectory - Unable to move " +
221        "update log file. Exception: " +
222        e
223    );
224  }
225
226  let sourceLastUpdateLog = sourceUpdateDir.clone();
227  sourceLastUpdateLog.append(FILE_LAST_UPDATE_LOG);
228  try {
229    sourceLastUpdateLog.moveTo(destUpdateDir, sourceLastUpdateLog.leafName);
230  } catch (e) {
231    LOG(
232      "UpdateServiceStub:_migrateUpdateDirectory - Unable to move " +
233        "last-update log file. Exception: " +
234        e
235    );
236  }
237
238  try {
239    sourceStatusFile.moveTo(destStatusFile.parent, destStatusFile.leafName);
240  } catch (e) {
241    LOG(
242      "UpdateServiceStub:_migrateUpdateDirectory - Unable to move update " +
243        "status file. Exception: " +
244        e
245    );
246  }
247
248  // Remove all remaining files in the old update directory. We don't need
249  // them anymore
250  try {
251    sourceRootDir.remove(true);
252  } catch (e) {
253    LOG(
254      "UpdateServiceStub:_migrateUpdateDirectory - Deletion of old update " +
255        "directory failed. Exception: " +
256        e
257    );
258  }
259}
260
261/**
262 * Logs a string to the error console.
263 * @param   string
264 *          The string to write to the error console.
265 */
266function LOG(string) {
267  if (gLogEnabled) {
268    dump("*** AUS:SVC " + string + "\n");
269    Services.console.logStringMessage("AUS:SVC " + string);
270  }
271}
272