1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
15 * The Original Code is the Update Service.
17 * The Initial Developer of the Original Code is Google Inc.
18 * Portions created by the Initial Developer are Copyright (C) 2005
19 * the Initial Developer. All Rights Reserved.
22 * Darin Fisher <darin@meer.net> (original author)
24 * Alternatively, the contents of this file may be used under the terms of
25 * either the GNU General Public License Version 2 or later (the "GPL"), or
26 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27 * in which case the provisions of the GPL or the LGPL are applicable instead
28 * of those above. If you wish to allow use of your version of this file only
29 * under the terms of either the GPL or the LGPL, and not to allow others to
30 * use your version of this file under the terms of the MPL, indicate your
31 * decision by deleting the provisions above and replace them with the notice
32 * and other provisions required by the GPL or the LGPL. If you do not delete
33 * the provisions above, a recipient may use your version of this file under
34 * the terms of any one of the MPL, the GPL or the LGPL.
36 * ***** END LICENSE BLOCK ***** */
39 * This file contains an implementation of nsIRunnable, which may be invoked
40 * to perform post-update modifications to the windows registry and uninstall
41 * logs required to complete an update of the application. This code is very
42 * specific to the xpinstall wizard for windows.
45 const URI_BRAND_PROPERTIES = "chrome://branding/locale/brand.properties";
47 const KEY_APPDIR = "XCurProcD";
48 const KEY_TMPDIR = "TmpD";
49 const KEY_UPDROOT = "UpdRootD";
50 const KEY_UAPPDATA = "UAppData";
53 const PR_RDONLY = 0x01;
54 const PR_WRONLY = 0x02;
55 const PR_APPEND = 0x10;
57 const PERMS_FILE = 0644;
58 const PERMS_DIR = 0700;
60 const nsIWindowsRegKey = Components.interfaces.nsIWindowsRegKey;
63 var gAppUpdateLogPostUpdate = false;
65 //-----------------------------------------------------------------------------
68 * Console logging support
71 if (gAppUpdateLogPostUpdate) {
72 dump("*** PostUpdateWin: " + s + "\n");
73 gConsole.logStringMessage(s);
78 * This function queries the XPCOM directory service.
80 function getFile(key) {
82 Components.classes["@mozilla.org/file/directory_service;1"].
83 getService(Components.interfaces.nsIProperties);
84 return dirSvc.get(key, Components.interfaces.nsIFile);
88 * Creates a new file object given a native file path.
90 * The native file path.
91 * @return nsILocalFile object for the given native file path.
93 function newFile(path) {
94 var file = Components.classes["@mozilla.org/file/local;1"]
95 .createInstance(Components.interfaces.nsILocalFile);
96 file.initWithPath(path);
101 * This function returns a file input stream.
103 function openFileInputStream(file) {
105 Components.classes["@mozilla.org/network/file-input-stream;1"].
106 createInstance(Components.interfaces.nsIFileInputStream);
107 stream.init(file, PR_RDONLY, 0, 0);
112 * This function returns a file output stream.
114 function openFileOutputStream(file, flags) {
116 Components.classes["@mozilla.org/network/file-output-stream;1"].
117 createInstance(Components.interfaces.nsIFileOutputStream);
118 stream.init(file, flags, 0644, 0);
122 //-----------------------------------------------------------------------------
124 const PREFIX_FILE = "File: ";
126 function InstallLogWriter() {
128 InstallLogWriter.prototype = {
129 _outputStream: null, // nsIOutputStream to the install wizard log file
132 * Write a single line to the output stream.
134 _writeLine: function(s) {
136 this._outputStream.write(s, s.length);
140 * This function creates an empty uninstall update log file if it doesn't
141 * exist and returns a reference to the resulting nsIFile.
143 _getUninstallLogFile: function() {
144 var file = getFile(KEY_APPDIR);
145 file.append("uninstall");
149 file.append("uninstall.log");
151 file.create(Components.interfaces.nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
157 * Return the update.log file. Use last-update.log file in case the
158 * updates/0 directory has already been cleaned out (see bug 311302).
160 _getUpdateLogFile: function() {
161 function appendUpdateLogPath(root) {
162 var file = root.clone();
163 file.append("updates");
165 file.append("update.log");
170 file.append("updates");
171 file.append("last-update.log");
178 // See the local appdata first if app dir is under Program Files.
182 updRoot = getFile(KEY_UPDROOT);
186 file = appendUpdateLogPath(updRoot);
188 // When updating from Fx 2.0.0.1 to 2.0.0.3 (or later) on Vista,
189 // we will have to see also user app data (see bug 351949).
191 file = appendUpdateLogPath(getFile(KEY_UAPPDATA));
194 // See the app dir if not found or app dir is out of Program Files.
196 file = appendUpdateLogPath(getFile(KEY_APPDIR));
202 * Read update.log to extract information about files that were
203 * newly added for this update.
205 _readUpdateLog: function(logFile, entries) {
208 stream = openFileInputStream(logFile).
209 QueryInterface(Components.interfaces.nsILineInputStream);
212 while (stream.readLine(line)) {
213 var data = line.value.split(" ");
214 if (data[0] == "EXECUTE" && data[1] == "ADD") {
215 // The uninstaller requires the path separator to be "\" and
216 // relative paths to start with a "\".
217 var relPath = "\\" + data[2].replace(/\//g, "\\");
218 entries[relPath] = null;
228 * Read install_wizard log files to extract information about files that were
229 * previously added by the xpinstall installer and software update.
231 _readXPInstallLog: function(logFile, entries) {
234 stream = openFileInputStream(logFile).
235 QueryInterface(Components.interfaces.nsILineInputStream);
237 function fixPath(path, offset) {
238 return path.substr(offset).replace(appDirPath, "");
241 var appDir = getFile(KEY_APPDIR);
242 var appDirPath = appDir.path;
244 while (stream.readLine(line)) {
245 var entry = line.value;
246 // This works with both the entries from xpinstall (e.g. Installing: )
247 // and from update (e.g. installing: )
248 var searchStr = "nstalling: ";
249 var index = entry.indexOf(searchStr);
251 entries[fixPath(entry, index + searchStr.length)] = null;
255 searchStr = "Replacing: ";
256 index = entry.indexOf(searchStr);
258 entries[fixPath(entry, index + searchStr.length)] = null;
262 searchStr = "Windows Shortcut: ";
263 index = entry.indexOf(searchStr);
265 entries[fixPath(entry + ".lnk", index + searchStr.length)] = null;
275 _readUninstallLog: function(logFile, entries) {
278 stream = openFileInputStream(logFile).
279 QueryInterface(Components.interfaces.nsILineInputStream);
282 var searchStr = "File: ";
283 while (stream.readLine(line)) {
284 var index = line.value.indexOf(searchStr);
286 var str = line.value.substr(index + searchStr.length);
297 * This function initializes the log writer and is responsible for
298 * translating 'update.log' and the 'install_wizard' logs to the NSIS format.
301 var updateLog = this._getUpdateLogFile();
305 var newEntries = { };
306 this._readUpdateLog(updateLog, newEntries);
309 const nsIDirectoryEnumerator = Components.interfaces.nsIDirectoryEnumerator;
310 const nsILocalFile = Components.interfaces.nsILocalFile;
311 var prefixWizLog = "install_wizard";
312 var uninstallDir = getFile(KEY_APPDIR);
313 uninstallDir.append("uninstall");
314 var entries = uninstallDir.directoryEntries.QueryInterface(nsIDirectoryEnumerator);
316 var wizLog = entries.nextFile;
319 if (wizLog instanceof nsILocalFile && !wizLog.isDirectory() &&
320 wizLog.leafName.indexOf(prefixWizLog) == 0) {
321 this._readXPInstallLog(wizLog, newEntries);
322 wizLog.remove(false);
330 var uninstallLog = this._getUninstallLogFile();
332 this._readUninstallLog(uninstallLog, oldEntries);
334 // Prevent writing duplicate entries in the log file
335 for (var relPath in newEntries) {
336 if (oldEntries.indexOf(relPath) != -1)
337 delete newEntries[relPath];
340 if (newEntries.length == 0)
343 // since we are not running with elevated privs, we can't write out
344 // the log file (at least, not on Vista). So, write the output to
345 // temp, and then later, we'll pass the file (gCopiedLog) to
346 // the post update clean up process, which can copy it to
347 // the desired location (because it will have elevated privs)
348 gCopiedLog = getFile(KEY_TMPDIR);
349 gCopiedLog.append("uninstall");
350 gCopiedLog.createUnique(gCopiedLog.DIRECTORY_TYPE, PERMS_DIR);
352 uninstallLog.copyTo(gCopiedLog, "uninstall.log");
353 gCopiedLog.append("uninstall.log");
355 LOG("uninstallLog = " + uninstallLog.path);
356 LOG("copiedLog = " + gCopiedLog.path);
358 if (!gCopiedLog.exists())
359 gCopiedLog.create(Components.interfaces.nsILocalFile.NORMAL_FILE_TYPE,
363 openFileOutputStream(gCopiedLog, PR_WRONLY | PR_APPEND);
365 // The NSIS uninstaller deletes all directories where the installer has
366 // added a file if the directory is empty after the files have been removed
367 // so there is no need to log directories.
368 for (var relPath in newEntries)
369 this._writeLine(PREFIX_FILE + relPath);
373 if (!this._outputStream)
375 this._outputStream.close();
376 this._outputStream = null;
380 var installLogWriter;
383 //-----------------------------------------------------------------------------
386 * A thin wrapper around nsIWindowsRegKey
387 * note, only the "read" methods are exposed. If you want to write
388 * to the registry on Vista, you need to be a priveleged app.
389 * We've moved that code into the uninstaller.
392 // Internally, we may pass parameters to this constructor.
393 if (arguments.length == 3) {
394 this._key = arguments[0];
395 this._root = arguments[1];
396 this._path = arguments[2];
399 Components.classes["@mozilla.org/windows-registry-key;1"].
400 createInstance(nsIWindowsRegKey);
408 ACCESS_READ: nsIWindowsRegKey.ACCESS_READ,
410 ROOT_KEY_CURRENT_USER: nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
411 ROOT_KEY_LOCAL_MACHINE: nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE,
412 ROOT_KEY_CLASSES_ROOT: nsIWindowsRegKey.ROOT_KEY_CLASSES_ROOT,
420 open: function(rootKey, path, mode) {
421 this._key.open(rootKey, path, mode);
422 this._root = rootKey;
426 openChild: function(path, mode) {
427 var child = this._key.openChild(path, mode);
428 return new RegKey(child, this._root, this._path + "\\" + path);
431 readStringValue: function(name) {
432 return this._key.readStringValue(name);
435 hasValue: function(name) {
436 return this._key.hasValue(name);
439 hasChild: function(name) {
440 return this._key.hasChild(name);
444 return this._key.childCount;
447 getChildName: function(index) {
448 return this._key.getChildName(index);
451 toString: function() {
453 switch (this._root) {
454 case this.ROOT_KEY_CLASSES_ROOT:
455 root = "HKEY_KEY_CLASSES_ROOT";
457 case this.ROOT_KEY_LOCAL_MACHINE:
458 root = "HKEY_LOCAL_MACHINE";
460 case this.ROOT_KEY_CURRENT_USER:
461 root = "HKEY_CURRENT_USER";
464 LOG("unknown root key");
467 return root + "\\" + this._path;
472 * This method walks the registry looking for the registry keys of
473 * the previous version of the application.
475 function haveOldInstall(key, brandFullName, version) {
476 var ourInstallDir = getFile(KEY_APPDIR);
478 var childKey, productKey, mainKey;
480 for (var i = 0; i < key.childCount; ++i) {
481 var childName = key.getChildName(i);
482 childKey = key.openChild(childName, key.ACCESS_READ);
483 if (childKey.hasValue("CurrentVersion")) {
484 for (var j = 0; j < childKey.childCount; ++j) {
485 var productVer = childKey.getChildName(j);
486 productKey = childKey.openChild(productVer, key.ACCESS_READ);
487 if (productKey.hasChild("Main")) {
488 mainKey = productKey.openChild("Main", key.ACCESS_READ);
489 var installDir = mainKey.readStringValue("Install Directory");
491 LOG("old install? " + installDir + " vs " + ourInstallDir.path);
492 LOG("old install? " + childName + " vs " + brandFullName);
493 LOG("old install? " + productVer.split(" ")[0] + " vs " + version);
494 if (newFile(installDir).equals(ourInstallDir) &&
495 (childName != brandFullName ||
496 productVer.split(" ")[0] != version)) {
521 function checkRegistry()
523 LOG("checkRegistry");
527 // Firefox is the only toolkit app that needs to do this.
528 // return false for other applications.
529 var app = Components.classes["@mozilla.org/xre/app-info;1"].
530 getService(Components.interfaces.nsIXULAppInfo);
531 if (app.name == "Firefox") {
533 var key = new RegKey();
534 key.open(RegKey.prototype.ROOT_KEY_CLASSES_ROOT, "FirefoxHTML\\shell\\open\\command", key.ACCESS_READ);
535 var commandKey = key.readStringValue("");
536 LOG("commandKey = " + commandKey);
537 // if "-requestPending" is not found, we need to do the cleanup
538 result = (commandKey.indexOf("-requestPending") == -1);
540 LOG("failed to open command key for FirefoxHTML: " + e);
547 function checkOldInstall(rootKey, vendorShortName, brandFullName, version)
549 var key = new RegKey();
553 key.open(rootKey, "SOFTWARE\\" + vendorShortName, key.ACCESS_READ);
554 LOG("checkOldInstall: " + key + " " + brandFullName + " " + version);
555 result = haveOldInstall(key, brandFullName, version);
557 LOG("failed trying to find old install: " + e);
563 //-----------------------------------------------------------------------------
565 function nsPostUpdateWin() {
566 gConsole = Components.classes["@mozilla.org/consoleservice;1"]
567 .getService(Components.interfaces.nsIConsoleService);
568 var prefs = Components.classes["@mozilla.org/preferences-service;1"].
569 getService(Components.interfaces.nsIPrefBranch);
571 gAppUpdateLogPostUpdate = prefs.getBoolPref("app.update.log.all");
576 if (!gAppUpdateLogPostUpdate)
577 gAppUpdateLogPostUpdate = prefs.getBoolPref("app.update.log.PostUpdate");
583 nsPostUpdateWin.prototype = {
584 QueryInterface: function(iid) {
585 if (iid.equals(Components.interfaces.nsIRunnable) ||
586 iid.equals(Components.interfaces.nsISupports))
588 throw Components.results.NS_ERROR_NO_INTERFACE;
592 // When uninstall/uninstall.update exists the uninstaller has already
593 // updated the uninstall.log with the files added by software update.
594 var updateUninstallFile = getFile(KEY_APPDIR);
595 updateUninstallFile.append("uninstall");
596 updateUninstallFile.append("uninstall.update");
597 if (updateUninstallFile.exists()) {
598 LOG("nothing to do, uninstall.log has already been updated");
603 installLogWriter = new InstallLogWriter();
605 installLogWriter.begin();
607 installLogWriter.end();
608 installLogWriter = null;
615 Components.classes["@mozilla.org/xre/app-info;1"].
616 getService(Components.interfaces.nsIXULAppInfo).
617 QueryInterface(Components.interfaces.nsIXULRuntime);
620 Components.classes["@mozilla.org/intl/stringbundle;1"].
621 getService(Components.interfaces.nsIStringBundleService);
622 var brandBundle = sbs.createBundle(URI_BRAND_PROPERTIES);
624 var vendorShortName = brandBundle.GetStringFromName("vendorShortName");
625 var brandFullName = brandBundle.GetStringFromName("brandFullName");
629 !checkOldInstall(RegKey.prototype.ROOT_KEY_LOCAL_MACHINE,
630 vendorShortName, brandFullName, app.version) &&
631 !checkOldInstall(RegKey.prototype.ROOT_KEY_CURRENT_USER,
632 vendorShortName, brandFullName, app.version)) {
633 LOG("nothing to do, so don't launch the helper");
639 app.QueryInterface(Components.interfaces.nsIWinAppHelper);
641 // note, gCopiedLog could be null
643 LOG("calling postUpdate with: " + gCopiedLog.path);
645 LOG("calling postUpdate without a log");
647 winAppHelper.postUpdate(gCopiedLog);
649 LOG("failed to launch the helper to do the post update cleanup: " + e);
654 //-----------------------------------------------------------------------------
657 registerSelf: function(compMgr, fileSpec, location, type) {
658 compMgr = compMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar);
660 for (var key in this._objects) {
661 var obj = this._objects[key];
662 compMgr.registerFactoryLocation(obj.CID, obj.className, obj.contractID,
663 fileSpec, location, type);
667 getClassObject: function(compMgr, cid, iid) {
668 if (!iid.equals(Components.interfaces.nsIFactory))
669 throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
671 for (var key in this._objects) {
672 if (cid.equals(this._objects[key].CID))
673 return this._objects[key].factory;
676 throw Components.results.NS_ERROR_NO_INTERFACE;
679 _makeFactory: #1= function(ctor) {
680 function ci(outer, iid) {
682 throw Components.results.NS_ERROR_NO_AGGREGATION;
683 return (new ctor()).QueryInterface(iid);
685 return { createInstance: ci };
689 manager: { CID : Components.ID("{d15b970b-5472-40df-97e8-eb03a04baa82}"),
690 contractID : "@mozilla.org/updates/post-update;1",
691 className : "nsPostUpdateWin",
692 factory : #1#(nsPostUpdateWin)
696 canUnload: function(compMgr) {
701 function NSGetModule(compMgr, fileSpec) {