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
) {