]> cygwin.com Git - cygwin-apps/setup.git/blame - install.cc
2002-07-15 Robert Collins <rbtcollins@hotmail.com>
[cygwin-apps/setup.git] / install.cc
CommitLineData
23c9e63c
DD
1/*
2 * Copyright (c) 2000, Red Hat, Inc.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * A copy of the GNU General Public License can be found at
10 * http://www.gnu.org/
11 *
12 * Written by DJ Delorie <dj@cygnus.com>
13 *
14 */
15
8e9aa511 16/* The purpose of this file is to install all the packages selected in
23c9e63c
DD
17 the install list (in ini.h). Note that we use a separate thread to
18 maintain the progress dialog, so we avoid the complexity of
19 handling two tasks in one thread. We also create or update all the
b24c88b3 20 files in /etc/setup/\* and create the mount points. */
23c9e63c 21
b24c88b3 22#if 0
ad3c7385 23static const char *cvsid = "\n%%% $Id$\n";
b24c88b3 24#endif
8507f105 25
23c9e63c
DD
26#include "win32.h"
27#include "commctrl.h"
28
29#include <stdio.h>
904d24fe 30#include <stdlib.h>
4a83b7b0 31#include <unistd.h>
23c9e63c
DD
32#include <sys/types.h>
33#include <sys/stat.h>
34#include <errno.h>
ab57ceaa
RC
35#include <process.h>
36
87c42361 37#include "zlib/zlib.h"
23c9e63c
DD
38
39#include "resource.h"
23c9e63c 40#include "dialog.h"
23c9e63c 41#include "geturl.h"
23c9e63c 42#include "state.h"
23c9e63c
DD
43#include "diskfull.h"
44#include "msg.h"
45#include "mount.h"
89b1a15b 46#include "log.h"
a351e48c 47#include "mount.h"
c46a33a9 48#include "filemanip.h"
b24c88b3
RC
49#include "io_stream.h"
50#include "compress.h"
51#include "compress_gz.h"
52#include "archive.h"
53#include "archive_tar.h"
8e9aa511 54#include "script.h"
4a83b7b0 55
fa0c0d10
RC
56#include "package_db.h"
57#include "package_meta.h"
bb849dbd
RC
58#include "package_version.h"
59#include "package_source.h"
fa0c0d10 60
4a83b7b0 61#include "port.h"
23c9e63c 62
ab57ceaa 63#include "threebar.h"
58ee6135
RC
64
65#include "md5.h"
66
67#include "Exception.h"
a75ed5ce 68#include "getopt++/BoolOption.h"
58ee6135 69
ab57ceaa 70extern ThreeBarProgressPage Progress;
23c9e63c
DD
71
72static int total_bytes = 0;
73static int total_bytes_sofar = 0;
74static int package_bytes = 0;
75
a75ed5ce
RC
76static BoolOption NoReplaceOnReboot (false, 'r', "no-replaceonreboot",
77 "Disable replacing in-use files on next "
78 "reboot.");
79
23c9e63c
DD
80static void
81init_dialog ()
82{
ab57ceaa
RC
83 Progress.SetText2 ("");
84 Progress.SetText3 ("");
23c9e63c
DD
85}
86
87static void
88progress (int bytes)
89{
ab57ceaa 90 if (package_bytes > 0)
23c9e63c 91 {
ab57ceaa 92 Progress.SetBar1 (bytes, package_bytes);
23c9e63c
DD
93 }
94
ab57ceaa 95 if (total_bytes > 0)
23c9e63c 96 {
ab57ceaa 97 Progress.SetBar2 (total_bytes_sofar + bytes, total_bytes);
23c9e63c 98 }
23c9e63c
DD
99}
100
b24c88b3 101static const char *standard_dirs[] = {
1a18aed7 102 "/bin",
904d24fe 103 "/etc",
1a18aed7 104 "/lib",
904d24fe 105 "/tmp",
4a83b7b0 106 "/usr",
1a18aed7
DD
107 "/usr/bin",
108 "/usr/lib",
ef45c299 109 "/usr/src",
4a83b7b0 110 "/usr/local",
1a18aed7
DD
111 "/usr/local/bin",
112 "/usr/local/etc",
113 "/usr/local/lib",
904d24fe 114 "/usr/tmp",
1a18aed7 115 "/var/run",
904d24fe
DD
116 "/var/tmp",
117 0
118};
119
ad3c7385
RC
120static int num_installs, num_replacements, num_uninstalls;
121static void uninstall_one (packagemeta &);
122static int replace_one (packagemeta &);
3c054baf
RC
123static int install_one_source (packagemeta &, packagesource &, String const &,
124 String const &, package_type_t);
ad3c7385 125static bool rebootneeded;
3b9077d4 126
fa0c0d10 127/* FIXME: upgrades should be a method too */
3b9077d4 128static void
cbfc4215 129uninstall_one (packagemeta & pkgm)
3b9077d4 130{
ab57ceaa 131 Progress.SetText1 ("Uninstalling...");
3c054baf 132 Progress.SetText2 (pkgm.name.cstr_oneuse());
1ac649ed 133 log (LOG_PLAIN, String("Uninstalling ") + pkgm.name);
ab57ceaa
RC
134 pkgm.uninstall ();
135 num_uninstalls++;
3b9077d4
DD
136}
137
ad3c7385
RC
138/* uninstall and install a package, preserving configuration
139 * files and the like.
140 * This method should also know about replacing in-use file.
141 * ASSUMPTIONS: pkgm is installed.
142 * pkgm has a desired package.
143 */
144static int
145replace_one (packagemeta & pkg)
146{
147 int errors = 0;
148 Progress.SetText1 ("Replacing...");
3c054baf 149 Progress.SetText2 (pkg.name.cstr_oneuse());
1ac649ed 150 log (LOG_PLAIN, String( "Replacing ") + pkg.name);
ad3c7385
RC
151 pkg.uninstall ();
152
153 errors +=
3c196821 154 install_one_source (pkg, *pkg.desired.source(), "cygfile://","/", package_binary);
ad3c7385
RC
155 if (!errors)
156 pkg.installed = pkg.desired;
157 num_replacements++;
158 return errors;
159}
42bf5b92 160
bb849dbd
RC
161
162/* install one source at a given prefix. */
3b9077d4 163static int
bb849dbd 164install_one_source (packagemeta & pkgm, packagesource & source,
3c054baf 165 String const &prefixURL, String const &prefixPath, package_type_t type)
3b9077d4
DD
166{
167 int errors = 0;
ab57ceaa 168 Progress.SetText2 (source.Base ());
7c6ef2c3 169 if (!source.Cached () || !io_stream::exists (source.Cached ()))
c46a33a9 170 {
ab57ceaa 171 note (NULL, IDS_ERR_OPEN_READ, source.Cached (), "No such file");
bb849dbd 172 return 1;
c46a33a9 173 }
bb849dbd
RC
174 io_stream *lst = 0;
175 if (type == package_binary)
c46a33a9 176 {
bb849dbd
RC
177 io_stream *tmp =
178 io_stream::
3c054baf 179 open (String ("cygfile:///etc/setup/") + pkgm.name + ".lst.gz",
bb849dbd
RC
180 "wb");
181 lst = new compress_gz (tmp, "w9");
182 if (lst->error ())
183 {
184 delete lst;
185 lst = NULL;
186 }
c46a33a9 187 }
b24c88b3 188
bb849dbd 189 package_bytes = source.size;
3b9077d4 190
bb849dbd
RC
191 char msg[64];
192 strcpy (msg, "Installing");
ab57ceaa 193 Progress.SetText1 (msg);
1ac649ed 194 log (LOG_PLAIN, String (msg) + " " + source.Cached ());
58ee6135
RC
195
196 if (source.md5.isSet())
197 {
198 // check the MD5 sum of the cached file here
199 io_stream *thefile = io_stream::open (source.Cached (), "rb");
200 if (!thefile)
201 return 0;
202 md5_state_t pns;
203 md5_init (&pns);
204
205 unsigned char buffer[16384];
206 ssize_t count;
207 while ((count = thefile->read (buffer, 16384)) > 0)
208 md5_append (&pns, buffer, count);
209 delete thefile;
210 if (count < 0)
211 throw new Exception ("__LINE__ __FILE__", (String ("IO Error reading ") + source.Cached()).cstr_oneuse(), APPERR_IO_ERROR);
212
213 md5_byte_t tempdigest[16];
214 md5_finish(&pns, tempdigest);
215 md5 tempMD5;
216 tempMD5.set (tempdigest);
217
218 log (LOG_BABBLE, String ("For file") + source.Cached() + " ini digest is" + source.md5.print() + " file digest is " + tempMD5.print());
219
220 if (source.md5 != tempMD5)
221 throw new Exception ("__LINE__ __FILE__", (String ("Checksum failure for ") + source.Cached()).cstr_oneuse(), APPERR_CORRUPT_PACKAGE);
222 }
bb849dbd
RC
223 io_stream *tmp = io_stream::open (source.Cached (), "rb");
224 archive *thefile = 0;
225 if (tmp)
d4a4527d 226 {
bb849dbd
RC
227 io_stream *tmp2 = compress::decompress (tmp);
228 if (tmp2)
229 thefile = archive::extract (tmp2);
230 else
231 thefile = archive::extract (tmp);
3b9077d4 232 }
bb849dbd
RC
233 /* FIXME: potential leak of either *tmp or *tmp2 */
234 if (thefile)
d4a4527d 235 {
3c054baf
RC
236 String fn;
237 while ((fn = thefile->next_file_name ()).size())
bb849dbd
RC
238 {
239 if (lst)
3c054baf
RC
240 {
241 String tmp=fn + "\n";
2fa7c5a4 242 lst->write (tmp.cstr_oneuse(), tmp.size());
3c054baf
RC
243 }
244
245 String canonicalfn = prefixPath + fn;
bb849dbd 246
3c054baf
RC
247 Progress.SetText3 (canonicalfn.cstr_oneuse());
248 log (LOG_BABBLE, String("Installing file ") + prefixURL + prefixPath + fn);
6dc75764 249 if (archive::extract_file (thefile, prefixURL, prefixPath) != 0)
bb849dbd 250 {
a75ed5ce
RC
251 if (NoReplaceOnReboot)
252 {
253 ++errors;
254 log (LOG_PLAIN, String("Not replacing in-use file ") +
255 prefixURL + prefixPath + fn);
256 }
257 else
ad3c7385 258 //extract to temp location
6dc75764 259 if (archive::extract_file (thefile, prefixURL, prefixPath, ".new") != 0)
ad3c7385 260 {
1ac649ed
RC
261 log (LOG_PLAIN,
262 String("Unable to install file ") +
263 prefixURL + prefixPath + fn);
ad3c7385
RC
264 errors++;
265 }
266 else
267 //switch Win32::OS
268 {
269 switch (Win32::OS ())
270 {
271 case Win32::Win9x:{
272 /* Get the short file names */
273 char source[MAX_PATH];
274 unsigned int len =
1ac649ed
RC
275 GetShortPathName (cygpath (String ("/") + fn +
276 ".new").cstr_oneuse(),
ad3c7385
RC
277 source, MAX_PATH);
278 if (!len || len > MAX_PATH)
279 {
3c054baf 280 log (LOG_TIMESTAMP,
ad3c7385 281 "Unable to schedule reboot replacement of file %s with %s (Win32 Error %ld)",
1ac649ed
RC
282 cygpath (String ("/") + fn).cstr_oneuse(),
283 cygpath (String ("/") + fn +
284 ".new").cstr_oneuse(),
ad3c7385
RC
285 GetLastError ());
286 ++errors;
287 }
288 else
289 {
290 char dest[MAX_PATH];
291 len =
1ac649ed
RC
292 GetShortPathName (cygpath (String ("/") +
293 fn).cstr_oneuse(),
294 dest, MAX_PATH);
ad3c7385
RC
295 if (!len || len > MAX_PATH)
296 {
3c054baf 297 log (LOG_TIMESTAMP,
ad3c7385 298 "Unable to schedule reboot replacement of file %s with %s (Win32 Error %ld)",
1ac649ed
RC
299 cygpath (String ("/") + fn).cstr_oneuse(),
300 cygpath (String ("/") + fn +
301 ".new").cstr_oneuse(),
ad3c7385
RC
302 GetLastError ());
303 ++errors;
304
305 }
306 else
307 /* trigger a replacement on reboot */
308 if (!WritePrivateProfileString
309 ("rename", dest, source, "WININIT.INI"))
310 {
3c054baf 311 log (LOG_TIMESTAMP,
ad3c7385 312 "Unable to schedule reboot replacement of file %s with %s (Win32 Error %ld)",
1ac649ed
RC
313 cygpath (String ("/") + fn).cstr_oneuse(),
314 cygpath (String ("/") + fn +
315 ".new").cstr_oneuse(),
ad3c7385
RC
316 GetLastError ());
317 ++errors;
318 }
319
320 }
321 }
322 break;
323 case Win32::WinNT:
324 /* XXX FIXME: prefix may not be / for in use files -
325 * although it most likely is
326 * - we need a io method to get win32 paths
327 * or to wrap this system call
328 */
1ac649ed
RC
329 if (!MoveFileEx (cygpath (String ("/") + fn +
330 ".new").cstr_oneuse(),
331 cygpath (String ("/") + fn).cstr_oneuse(),
ad3c7385
RC
332 MOVEFILE_DELAY_UNTIL_REBOOT |
333 MOVEFILE_REPLACE_EXISTING))
334 {
3c054baf 335 log (LOG_TIMESTAMP,
ad3c7385 336 "Unable to schedule reboot replacement of file %s with %s (Win32 Error %ld)",
1ac649ed
RC
337 cygpath (String ("/") + fn).cstr_oneuse(),
338 cygpath (String ("/") + fn + ".new").cstr_oneuse(),
ad3c7385
RC
339 GetLastError ());
340 ++errors;
341 }
342 else
343 rebootneeded = true;
344 break;
345 }
346 }
bb849dbd
RC
347 }
348
349 progress (tmp->tell ());
350 num_installs++;
351 }
352 delete thefile;
353
354 total_bytes_sofar += package_bytes;
d4a4527d 355 }
3b9077d4 356
90d14922 357
bb849dbd
RC
358 progress (0);
359
3c054baf 360 int df = diskfull (get_root_dir ().cstr_oneuse());
ab57ceaa 361 Progress.SetBar3 (df);
bb849dbd
RC
362
363 if (lst)
364 delete lst;
365
366 return errors;
367}
368
369/* install a package, install both the binary and source aspects if needed */
370static int
371install_one (packagemeta & pkg)
372{
373 int errors = 0;
374
3c196821 375 if (pkg.installed != pkg.desired && pkg.desired.picked())
ab57ceaa 376 {
bb849dbd 377 errors +=
3c196821 378 install_one_source (pkg, *pkg.desired.source(), "cygfile://","/",
ab57ceaa
RC
379 package_binary);
380 if (!errors)
381 pkg.installed = pkg.desired;
382 }
3c196821 383 if (pkg.desired.sourcePackage().picked())
ab57ceaa 384 errors +=
3c196821 385 install_one_source (pkg, *pkg.desired.sourcePackage().source(), "cygfile://","/usr/src/",
ab57ceaa 386 package_source);
bb849dbd
RC
387
388 /* FIXME: make a upgrade method and reinstate this */
389#if 0
1ac649ed 390 String msg;
c46a33a9 391 if (!pkg->installed)
1ac649ed 392 msg = "Installing";
c46a33a9
CF
393 else
394 {
395 int n = strcmp (pi->version, pkg->installed->version);
396 if (n < 0)
1ac649ed 397 msg = "Reverting";
c46a33a9 398 else if (n == 0)
1ac649ed 399 msg = "Reinstalling";
c46a33a9 400 else
1ac649ed 401 msg = "Upgrading";
c46a33a9
CF
402 }
403
404 switch (pkg->action)
3b9077d4 405 {
c46a33a9 406 case ACTION_PREV:
1ac649ed 407 msg += " previous version...";
3b9077d4 408 break;
c46a33a9 409 case ACTION_CURR:
1ac649ed 410 msg += "...";
3b9077d4 411 break;
c46a33a9 412 case ACTION_TEST:
1ac649ed 413 msg += " test version...";
42bf5b92 414 break;
b24c88b3
RC
415 default:
416 /* FIXME: log this somehow */
417 break;
3b9077d4 418 }
1ac649ed
RC
419 SetWindowText (ins_action, msg.cstr_oneuse());
420 log (LOG_PLAIN, msg + " " + file);
bb849dbd 421#endif
c46a33a9 422
3b9077d4
DD
423 return errors;
424}
425
72fd1d1e
CF
426static void
427check_for_old_cygwin ()
428{
429 char buf[_MAX_PATH + sizeof ("\\cygwin1.dll")];
430 if (!GetSystemDirectory (buf, sizeof (buf)))
431 return;
432 strcat (buf, "\\cygwin1.dll");
433 if (_access (buf, 0) != 0)
434 return;
435
436 char msg[sizeof (buf) + 132];
b24c88b3
RC
437 sprintf (msg,
438 "An old version of cygwin1.dll was found here:\r\n%s\r\nDelete?",
439 buf);
440 switch (MessageBox
441 (NULL, msg, "What's that doing there?",
442 MB_YESNO | MB_ICONQUESTION | MB_TASKMODAL))
72fd1d1e
CF
443 {
444 case IDYES:
445 if (!DeleteFile (buf))
446 {
447 sprintf (msg, "Couldn't delete file %s.\r\n"
b24c88b3 448 "Is the DLL in use by another application?\r\n"
7c7034e8
RC
449 "You should delete the old version of cygwin1.dll\r\n"
450 "at your earliest convenience.", buf);
b24c88b3
RC
451 MessageBox (NULL, buf, "Couldn't delete file",
452 MB_OK | MB_ICONEXCLAMATION | MB_TASKMODAL);
72fd1d1e
CF
453 }
454 break;
455 default:
b24c88b3 456 break;
72fd1d1e
CF
457 }
458
459 return;
460}
461
ab57ceaa
RC
462static void
463do_install_thread (HINSTANCE h, HWND owner)
23c9e63c 464{
3b9077d4 465 int i;
2a1a01e0
DD
466 int errors = 0;
467
ad3c7385
RC
468 num_installs = 0, num_uninstalls = 0, num_replacements = 0;
469 rebootneeded = false;
3b9077d4 470
50225eae 471 next_dialog = IDD_DESKTOP;
23c9e63c 472
490717ef 473 io_stream::mkpath_p (PATH_TO_DIR, String ("file://") + get_root_dir ());
904d24fe 474
b24c88b3 475 for (i = 0; standard_dirs[i]; i++)
904d24fe 476 {
1ac649ed 477 String p = cygpath (standard_dirs[i]);
3c054baf 478 if (p.size())
490717ef 479 io_stream::mkpath_p (PATH_TO_DIR, String ("file://") + p);
904d24fe
DD
480 }
481
1a18aed7 482 /* Create /var/run/utmp */
b24c88b3
RC
483 io_stream *utmp = io_stream::open ("cygfile:///var/run/utmp", "wb");
484 delete utmp;
1a18aed7 485
23c9e63c
DD
486 init_dialog ();
487
488 total_bytes = 0;
489 total_bytes_sofar = 0;
490
3c054baf 491 int df = diskfull (get_root_dir ().cstr_oneuse());
ab57ceaa 492 Progress.SetBar3 (df);
d07591a3 493
0af2d779
CF
494 int istext = (root_text == IDC_ROOT_TEXT) ? 1 : 0;
495 int issystem = (root_scope == IDC_ROOT_SYSTEM) ? 1 : 0;
496
1ac649ed
RC
497 create_mount ("/", get_root_dir (), istext, issystem);
498 create_mount ("/usr/bin", cygpath ("/bin"), istext, issystem);
499 create_mount ("/usr/lib", cygpath ("/lib"), istext, issystem);
0af2d779
CF
500 set_cygdrive_flags (istext, issystem);
501
8e9aa511
RC
502 /* Let's hope people won't uninstall packages before installing [b]ash */
503 init_run_script ();
504
bb849dbd 505 packagedb db;
cfae3b8d
RC
506 for (vector <packagemeta *>::iterator i = db.packages.begin ();
507 i != db.packages.end (); ++i)
cbfc4215 508 {
cfae3b8d 509 packagemeta & pkg = **i;
ab57ceaa 510
3c196821 511 if (pkg.desired.changeRequested())
ab57ceaa 512 {
3c196821
RC
513 if (pkg.desired.picked())
514 total_bytes += pkg.desired.source()->size;
515 if (pkg.desired.sourcePackage ().picked())
516 total_bytes += pkg.desired.sourcePackage ().source()->size;
ab57ceaa 517 }
cbfc4215 518 }
bb849dbd 519
ad3c7385 520 /* start with uninstalls - remove files that new packages may replace */
cfae3b8d
RC
521 for (vector <packagemeta *>::iterator i = db.packages.begin ();
522 i != db.packages.end (); ++i)
23c9e63c 523 {
cfae3b8d 524 packagemeta & pkg = **i;
cbfc4215 525 if (pkg.installed && (!pkg.desired || pkg.desired != pkg.installed))
ad3c7385
RC
526 uninstall_one (pkg);
527 }
528
529 /* now in-place binary upgrades/reinstalls, as these may remove fils
530 * that have been moved into new packages
531 */
532
cfae3b8d
RC
533 for (vector <packagemeta *>::iterator i = db.packages.begin ();
534 i != db.packages.end (); ++i)
ad3c7385 535 {
cfae3b8d 536 packagemeta & pkg = **i;
3c196821 537 if (pkg.installed && pkg.desired.picked())
4a83b7b0 538 {
58ee6135
RC
539 try {
540 int e = 0;
541 e += replace_one (pkg);
542 if (e)
543 errors++;
544 }
545 catch (exception *e)
546 {
547 if (yesno (owner, IDS_INSTALL_ERROR, e->what()) != IDYES)
548 {
549 log (LOG_TIMESTAMP, String ("User cancelled setup after install error"));
9f4a0c62 550 LogSingleton::GetInstance().exit (1);
58ee6135
RC
551 return;
552 }
553 }
4a83b7b0 554 }
ad3c7385
RC
555 }
556
cfae3b8d
RC
557 for (vector <packagemeta *>::iterator i = db.packages.begin ();
558 i != db.packages.end (); ++i)
ad3c7385 559 {
cfae3b8d 560 packagemeta & pkg = **i;
23c9e63c 561
3c196821 562 if (pkg.desired && pkg.desired.changeRequested())
23c9e63c 563 {
58ee6135 564 try
40aef45e 565 {
58ee6135
RC
566 int e = 0;
567 e += install_one (pkg);
568 if (e)
569 errors++;
570 }
571 catch (exception *e)
572 {
573 if (yesno (owner, IDS_INSTALL_ERROR, e->what()) != IDYES)
574 {
575 log (LOG_TIMESTAMP, String ("User cancelled setup after install error"));
9f4a0c62 576 LogSingleton::GetInstance().exit (1);
58ee6135
RC
577 return;
578 }
40aef45e 579 }
4a83b7b0 580 }
b24c88b3 581 } // end of big package loop
4a83b7b0 582
ad3c7385
RC
583 if (rebootneeded)
584 note (owner, IDS_REBOOT_REQUIRED);
585
7c7034e8
RC
586 int temperr;
587 if ((temperr = db.flush ()))
23c9e63c 588 {
7c7034e8 589 const char *err = strerror (temperr);
23c9e63c
DD
590 if (!err)
591 err = "(unknown error)";
3c054baf
RC
592 fatal (owner, IDS_ERR_OPEN_WRITE, "Package Database",
593 err);
23c9e63c
DD
594 }
595
72fd1d1e
CF
596 if (!errors)
597 check_for_old_cygwin ();
4a83b7b0
DD
598 if (num_installs == 0 && num_uninstalls == 0)
599 {
600 exit_msg = IDS_NOTHING_INSTALLED;
601 return;
602 }
603 if (num_installs == 0)
604 {
605 exit_msg = IDS_UNINSTALL_COMPLETE;
606 return;
607 }
608
2a1a01e0
DD
609 if (errors)
610 exit_msg = IDS_INSTALL_INCOMPLETE;
611 else
612 exit_msg = IDS_INSTALL_COMPLETE;
23c9e63c 613}
ab57ceaa 614
45e01f23 615static DWORD WINAPI
ab57ceaa
RC
616do_install_reflector (void *p)
617{
618 HANDLE *context;
619 context = (HANDLE *) p;
620
621 do_install_thread ((HINSTANCE) context[0], (HWND) context[1]);
622
623 // Tell the progress page that we're done downloading
624 Progress.PostMessage (WM_APP_INSTALL_THREAD_COMPLETE);
625
45e01f23 626 ExitThread (0);
ab57ceaa
RC
627}
628
629static HANDLE context[2];
630
631void
632do_install (HINSTANCE h, HWND owner)
633{
634 context[0] = h;
635 context[1] = owner;
636
45e01f23
RC
637 DWORD threadID;
638 CreateThread (NULL, 0, do_install_reflector, context, 0, &threadID);
ab57ceaa 639}
This page took 0.113613 seconds and 5 git commands to generate.