Allow to run on Windows lacking CreateSymbolicLinkW()
[cygwin-setup.git] / ini.cc
blob3ef131185cc37abe16dcd890cf7dc57a45fb1e32
1 /*
2 * Copyright (c) 2000,2007 Red Hat, Inc.
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.
9 * A copy of the GNU General Public License can be found at
10 * http://www.gnu.org/
12 * Written by DJ Delorie <dj@cygnus.com>
16 /* The purpose of this file is to get and parse the setup.ini file
17 from the mirror site. A few support routines for the bison and
18 flex parsers are provided also. We check to see if this setup.ini
19 is older than the one we used last time, and if so, warn the user. */
21 #include "ini.h"
23 #include "csu_util/rfc1738.h"
24 #include "csu_util/version_compare.h"
26 #include "setup_version.h"
27 #include "win32.h"
28 #include "LogFile.h"
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <stdarg.h>
33 #include <process.h>
35 #include "resource.h"
36 #include "state.h"
37 #include "geturl.h"
38 #include "dialog.h"
39 #include "mount.h"
40 #include "site.h"
41 #include "find.h"
42 #include "IniParseFeedback.h"
44 #include "io_stream.h"
45 #include "io_stream_memory.h"
47 #include "threebar.h"
49 #include "getopt++/BoolOption.h"
50 #include "IniDBBuilderPackage.h"
51 #include "compress.h"
52 #include "Exception.h"
53 #include "crypto.h"
54 #include "package_db.h"
56 extern ThreeBarProgressPage Progress;
58 unsigned int setup_timestamp = 0;
59 std::string ini_setup_version;
60 // TODO: use C++11x initializer lists instead and drop the literal array
61 IniList setup_ext_list (setup_exts,
62 setup_exts + (sizeof(setup_exts) / sizeof(*setup_exts)));
64 static BoolOption NoVerifyOption (false, 'X', "no-verify", IDS_HELPTEXT_NO_VERIFY);
65 static BoolOption NoVersionCheckOption (false, '\0', "no-version-check", IDS_HELPTEXT_NO_VERSION_CHECK);
67 class GuiParseFeedback : public IniParseFeedback
69 public:
70 GuiParseFeedback () : lastpct (0)
72 Progress.SetText1 (IDS_PROGRESS_PARSING);
73 Progress.SetText2 ("");
74 Progress.SetText3 ("");
75 Progress.SetText4 (IDS_PROGRESS_PROGRESS);
77 yyerror_count = 0;
78 yyerror_messages.clear ();
80 virtual void progress (unsigned long const pos, unsigned long const max)
82 if (!max)
83 /* length not known or eof */
84 return;
85 if (lastpct == 100)
86 /* rounding down should mean this only ever fires once */
87 lastpct = 0;
88 if (pos * 100 / max > lastpct)
90 lastpct = pos * 100 / max;
91 /* Log (LOG_BABBLE) << lastpct << "% (" << pos << " of " << max
92 << " bytes of ini file read)" << endLog; */
94 Progress.SetBar1 (pos, max);
96 static char buf[100];
97 sprintf (buf, "%d %% (%ldk/%ldk)", lastpct, pos/1000, max/1000);
98 Progress.SetText3 (buf);
100 virtual void iniName (const std::string& name)
102 Progress.SetText2 (name.c_str ());
103 Progress.SetText3 ("");
104 filename = name;
106 virtual void babble (const std::string& message)const
108 Log (LOG_BABBLE) << message << endLog;
110 virtual void warning (const std::string& message)const
112 mbox (Progress.GetHWND(), message.c_str (), "Warning", 0);
114 virtual void note_error(int lineno, const std::string &error)
116 char tmp[16];
117 sprintf (tmp, "%d", lineno);
119 std::string e = filename + " line " + tmp + ": " + error;
121 if (!yyerror_messages.empty ())
122 yyerror_messages += "\n";
124 yyerror_messages += e;
125 yyerror_count++;
127 virtual bool has_errors () const
129 return (yyerror_count > 0);
131 virtual void show_errors () const
133 mbox (Progress.GetHWND(), yyerror_messages.c_str (), "Parse Errors", 0);
135 virtual ~ GuiParseFeedback ()
137 Progress.SetText2 ("");
138 Progress.SetText3 ("");
139 Progress.SetText4 (IDS_PROGRESS_PACKAGE);
140 Progress.SetBar1 (0);
142 private:
143 unsigned int lastpct;
144 std::string filename;
145 std::string yyerror_messages;
146 int yyerror_count;
149 static io_stream*
150 decompress_ini (io_stream *ini_file, std::string &current_ini_name)
152 // Replace the current compressed setup stream with its decompressed
153 // version. Which decompressor to use is determined by file magic.
154 io_stream *compressed_stream = compress::decompress (ini_file);
155 if (!compressed_stream)
157 /* This isn't a known compression format or an uncompressed file
158 stream. Pass it on in case it was uncompressed, it will
159 generate a parser error if it was some unknown format. */
160 delete compressed_stream;
162 else
164 /* Decompress the entire file in memory. This has the advantage
165 that input_stream->get_size () will work during parsing and
166 we'll have an accurate status bar. Also, we can't seek
167 compressed streams, so when we write out a local cached copy
168 of the .ini file below, we'd otherwise have to delete this
169 stream and uncompress it again from the start, which is
170 wasteful. The current uncompressed size of the setup.ini
171 file as of 2015 is about 5 MiB, so this is not a great deal
172 of memory. */
173 io_stream *uncompressed = new io_stream_memory ();
174 /* Note that the decompress io_stream now "owns" the underlying
175 compressed io_stream instance, so it need not be deleted
176 explicitly. */
177 if ((io_stream::copy (compressed_stream, uncompressed) != 0) ||
178 (compressed_stream->error () != 0))
180 /* There was a problem decompressing compressed_stream. */
181 Log (LOG_PLAIN) <<
182 "Warning: Error code " << compressed_stream->error () <<
183 " occurred while uncompressing " << current_ini_name <<
184 " - possibly truncated or corrupt file. " << endLog;
185 delete uncompressed;
186 ini_file = NULL;
188 else
190 ini_file = uncompressed;
191 ini_file->seek (0, IO_SEEK_SET);
194 return ini_file;
197 static io_stream*
198 check_ini_sig (io_stream* ini_file, io_stream* ini_sig_file,
199 bool& sig_fail, const char* site, const char* sig_name, HWND owner)
201 /* Unless the NoVerifyOption is set, check the signature for the
202 current setup and record the result. On a failed signature check
203 the streams are invalidated so even if we tried to read in the
204 setup anyway there's be nothing to parse. */
205 if (!NoVerifyOption && ini_file)
207 if (!ini_sig_file) {
208 // don't complain if the user installs from localdir and no
209 // signature file is present
210 // TODO: download the ini + signature file instead
211 if (casecompare (site, "localdir"))
213 note (owner, IDS_SETUPINI_MISSING, sig_name, site);
214 delete ini_file;
215 ini_file = NULL;
216 sig_fail = true;
219 else if (!verify_ini_file_sig (ini_file, ini_sig_file, owner))
221 note (owner, IDS_SIG_INVALID, sig_name, site);
222 delete ini_sig_file;
223 ini_sig_file = NULL;
224 delete ini_file;
225 ini_file = NULL;
226 sig_fail = true;
229 return ini_file;
232 static bool
233 do_local_ini (HWND owner)
235 bool ini_error = false;
236 io_stream *ini_file, *ini_sig_file;
237 // iterate over all setup files found in do_from_local_dir
238 for (IniList::const_iterator n = found_ini_list.begin ();
239 n != found_ini_list.end (); ++n)
241 GuiParseFeedback myFeedback;
242 IniDBBuilderPackage aBuilder (myFeedback);
243 bool sig_fail = false;
244 std::string current_ini_ext, current_ini_name, current_ini_sig_name;
246 current_ini_name = *n;
247 current_ini_sig_name = current_ini_name + ".sig";
248 current_ini_ext = current_ini_name.substr (current_ini_name.rfind (".") + 1);
249 ini_sig_file = io_stream::open ("file://" + current_ini_sig_name, "rb", 0);
250 ini_file = io_stream::open ("file://" + current_ini_name, "rb", 0);
251 ini_file = check_ini_sig (ini_file, ini_sig_file, sig_fail,
252 "localdir", current_ini_sig_name.c_str (), owner);
253 if (ini_file)
254 ini_file = decompress_ini (ini_file, current_ini_name);
255 if (!ini_file || sig_fail)
257 // no setup found or signature invalid
258 note (owner, IDS_SETUPINI_MISSING, SetupBaseName.c_str (),
259 "localdir");
260 ini_error = true;
262 else
264 // grok information from setup
265 myFeedback.babble ("Found ini file - " + current_ini_name);
266 myFeedback.iniName (current_ini_name);
267 int ldl = local_dir.length () + 1;
268 int cap = current_ini_name.rfind ("/" + SetupArch);
269 aBuilder.parse_mirror =
270 rfc1738_unescape (current_ini_name.substr (ldl, cap - ldl));
271 ini_init (ini_file, &aBuilder, myFeedback);
273 if (yyparse () || myFeedback.has_errors())
275 myFeedback.show_errors ();
276 ini_error = true;
279 if (aBuilder.timestamp > setup_timestamp)
281 setup_timestamp = aBuilder.timestamp;
282 ini_setup_version = aBuilder.version;
284 delete ini_file;
285 ini_file = NULL;
288 return ini_error;
291 static bool
292 do_remote_ini (HWND owner)
294 bool ini_error = false;
295 io_stream *ini_file = NULL, *ini_sig_file;
297 /* FIXME: Get rid of this io_stream pointer travesty. The need to
298 explicitly delete these things is ridiculous. */
300 // iterate over all sites
301 for (SiteList::const_iterator n = site_list.begin ();
302 n != site_list.end (); ++n)
304 GuiParseFeedback myFeedback;
305 IniDBBuilderPackage aBuilder (myFeedback);
306 bool sig_fail = false;
307 std::string current_ini_ext, current_ini_name, current_ini_sig_name;
308 // iterate over known extensions for setup
309 for (IniList::const_iterator ext = setup_ext_list.begin ();
310 ext != setup_ext_list.end ();
311 ext++)
313 current_ini_ext = *ext;
314 current_ini_name = n->url + SetupIniDir + SetupBaseName + "." + current_ini_ext;
315 current_ini_sig_name = current_ini_name + ".sig";
316 ini_sig_file = get_url_to_membuf (current_ini_sig_name, owner);
317 ini_file = get_url_to_membuf (current_ini_name, owner);
318 ini_file = check_ini_sig (ini_file, ini_sig_file, sig_fail,
319 n->url.c_str (), current_ini_sig_name.c_str (), owner);
320 // stop searching as soon as we find a setup file
321 if (ini_file)
322 break;
324 if (ini_file)
325 ini_file = decompress_ini (ini_file, current_ini_name);
326 if (!ini_file || sig_fail)
328 // no setup found or signature invalid
329 note (owner, IDS_SETUPINI_MISSING, SetupBaseName.c_str (), n->url.c_str ());
330 ini_error = true;
332 else
334 // grok information from setup
335 myFeedback.iniName (current_ini_name);
336 aBuilder.parse_mirror = n->url;
337 ini_init (ini_file, &aBuilder, myFeedback);
339 if (yyparse () || myFeedback.has_errors())
341 myFeedback.show_errors ();
342 ini_error = true;
344 else
346 /* save known-good setup.ini locally */
347 const std::string fp = "file://" + local_dir + "/" +
348 rfc1738_escape_part (n->url) +
349 "/" + SetupIniDir + SetupBaseName + ".ini";
350 io_stream::mkpath_p (PATH_TO_FILE, fp, 0);
351 if (io_stream *out = io_stream::open (fp, "wb", 0))
353 ini_file->seek (0, IO_SEEK_SET);
354 if (io_stream::copy (ini_file, out) != 0)
355 io_stream::remove (fp);
356 delete out;
359 if (aBuilder.timestamp > setup_timestamp)
361 setup_timestamp = aBuilder.timestamp;
362 ini_setup_version = aBuilder.version;
364 delete ini_file;
365 ini_file = NULL;
368 return ini_error;
371 static bool
372 do_ini_thread (HINSTANCE h, HWND owner)
374 packagedb db;
375 db.init();
377 bool ini_error = true;
379 if (source == IDC_SOURCE_LOCALDIR)
380 ini_error = do_local_ini (owner);
381 else
382 ini_error = do_remote_ini (owner);
384 if (ini_error)
385 return false;
387 if (get_root_dir ().c_str ())
389 io_stream::mkpath_p (PATH_TO_DIR, "cygfile:///etc/setup", 0755);
391 unsigned int old_timestamp = 0;
392 io_stream *ots =
393 io_stream::open ("cygfile:///etc/setup/timestamp", "rt", 0);
394 if (ots)
396 char temp[20];
397 memset (temp, '\0', 20);
398 if (ots->read (temp, 19))
399 sscanf (temp, "%u", &old_timestamp);
400 delete ots;
401 if (old_timestamp && setup_timestamp
402 && (old_timestamp > setup_timestamp))
404 int yn = yesno (owner, IDS_OLD_SETUPINI);
405 if (yn == IDNO)
406 Logger ().exit (1);
409 if (setup_timestamp)
411 io_stream *nts =
412 io_stream::open ("cygfile:///etc/setup/timestamp", "wt", 0);
413 if (nts)
415 char temp[20];
416 sprintf (temp, "%u", setup_timestamp);
417 nts->write (temp, strlen (temp));
418 delete nts;
423 LogBabblePrintf (".ini setup_version is %s, our setup_version is %s", ini_setup_version.size () ?
424 ini_setup_version.c_str () : "(null)",
425 setup_version);
426 if (ini_setup_version.size ())
428 if ((version_compare (setup_version, ini_setup_version) < 0)
429 && !NoVersionCheckOption)
430 note (owner, IDS_OLD_SETUP_VERSION, setup_version,
431 ini_setup_version.c_str ());
434 return true;
437 static DWORD WINAPI
438 do_ini_thread_reflector (void* p)
440 HANDLE *context;
441 context = (HANDLE*)p;
443 SetThreadUILanguage(langid);
447 bool succeeded = do_ini_thread ((HINSTANCE)context[0], (HWND)context[1]);
449 // Tell the progress page that we're done downloading
450 Progress.PostMessageNow (WM_APP_SETUP_INI_DOWNLOAD_COMPLETE, 0, succeeded);
452 TOPLEVEL_CATCH ((HWND) context[1], "ini");
454 ExitThread (0);
457 static HANDLE context[2];
459 void
460 do_ini (HINSTANCE h, HWND owner)
462 context[0] = h;
463 context[1] = owner;
465 DWORD threadID;
466 CreateThread (NULL, 0, do_ini_thread_reflector, context, 0, &threadID);