]> cygwin.com Git - cygwin-apps/setup.git/blob - ini.cc
99b2ac631639ff02b8e564055a18e438d9b72e26
[cygwin-apps/setup.git] / ini.cc
1 /*
2 * Copyright (c) 2000,2007 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
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. */
20
21 #include "ini.h"
22
23 #include "csu_util/rfc1738.h"
24 #include "csu_util/version_compare.h"
25
26 #include "setup_version.h"
27 #include "win32.h"
28 #include "LogFile.h"
29
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <stdarg.h>
33 #include <process.h>
34
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"
43
44 #include "io_stream.h"
45 #include "io_stream_memory.h"
46
47 #include "threebar.h"
48
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"
55
56 extern ThreeBarProgressPage Progress;
57
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)));
63
64 static BoolOption NoVerifyOption (false, 'X', "no-verify", "Don't verify setup.ini signatures");
65 static BoolOption NoVersionCheckOption (false, '\0', "no-version-check", "Suppress checking if a newer version of setup is available");
66
67 class GuiParseFeedback : public IniParseFeedback
68 {
69 public:
70 GuiParseFeedback () : lastpct (0)
71 {
72 Progress.SetText2 ("");
73 Progress.SetText3 ("");
74 Progress.SetText4 (IDS_PROGRESS_PARSING);
75
76 yyerror_count = 0;
77 yyerror_messages.clear ();
78 }
79 virtual void progress (unsigned long const pos, unsigned long const max)
80 {
81 if (!max)
82 /* length not known or eof */
83 return;
84 if (lastpct == 100)
85 /* rounding down should mean this only ever fires once */
86 lastpct = 0;
87 if (pos * 100 / max > lastpct)
88 {
89 lastpct = pos * 100 / max;
90 /* Log (LOG_BABBLE) << lastpct << "% (" << pos << " of " << max
91 << " bytes of ini file read)" << endLog; */
92 }
93 Progress.SetBar1 (pos, max);
94
95 static char buf[100];
96 sprintf (buf, "%d %% (%ldk/%ldk)", lastpct, pos/1000, max/1000);
97 Progress.SetText3 (buf);
98 }
99 virtual void iniName (const std::string& name)
100 {
101 Progress.SetText1 (IDS_PROGRESS_PARSING);
102 Progress.SetText2 (name.c_str ());
103 Progress.SetText3 ("");
104 filename = name;
105 }
106 virtual void babble (const std::string& message)const
107 {
108 Log (LOG_BABBLE) << message << endLog;
109 }
110 virtual void warning (const std::string& message)const
111 {
112 mbox (Progress.GetHWND(), message.c_str (), "Warning", 0);
113 }
114 virtual void note_error(int lineno, const std::string &error)
115 {
116 char tmp[16];
117 sprintf (tmp, "%d", lineno);
118
119 std::string e = filename + " line " + tmp + ": " + error;
120
121 if (!yyerror_messages.empty ())
122 yyerror_messages += "\n";
123
124 yyerror_messages += e;
125 yyerror_count++;
126 }
127 virtual bool has_errors () const
128 {
129 return (yyerror_count > 0);
130 }
131 virtual void show_errors () const
132 {
133 mbox (Progress.GetHWND(), yyerror_messages.c_str (), "Parse Errors", 0);
134 }
135 virtual ~ GuiParseFeedback ()
136 {
137 Progress.SetText4 (IDS_PROGRESS_PACKAGE);
138 }
139 private:
140 unsigned int lastpct;
141 std::string filename;
142 std::string yyerror_messages;
143 int yyerror_count;
144 };
145
146 static io_stream*
147 decompress_ini (io_stream *ini_file, std::string &current_ini_name)
148 {
149 // Replace the current compressed setup stream with its decompressed
150 // version. Which decompressor to use is determined by file magic.
151 io_stream *compressed_stream = compress::decompress (ini_file);
152 if (!compressed_stream)
153 {
154 /* This isn't a known compression format or an uncompressed file
155 stream. Pass it on in case it was uncompressed, it will
156 generate a parser error if it was some unknown format. */
157 delete compressed_stream;
158 }
159 else
160 {
161 /* Decompress the entire file in memory. This has the advantage
162 that input_stream->get_size () will work during parsing and
163 we'll have an accurate status bar. Also, we can't seek
164 compressed streams, so when we write out a local cached copy
165 of the .ini file below, we'd otherwise have to delete this
166 stream and uncompress it again from the start, which is
167 wasteful. The current uncompressed size of the setup.ini
168 file as of 2015 is about 5 MiB, so this is not a great deal
169 of memory. */
170 io_stream *uncompressed = new io_stream_memory ();
171 /* Note that the decompress io_stream now "owns" the underlying
172 compressed io_stream instance, so it need not be deleted
173 explicitly. */
174 if ((io_stream::copy (compressed_stream, uncompressed) != 0) ||
175 (compressed_stream->error () != 0))
176 {
177 /* There was a problem decompressing compressed_stream. */
178 Log (LOG_PLAIN) <<
179 "Warning: Error code " << compressed_stream->error () <<
180 " occurred while uncompressing " << current_ini_name <<
181 " - possibly truncated or corrupt file. " << endLog;
182 delete uncompressed;
183 ini_file = NULL;
184 }
185 else
186 {
187 ini_file = uncompressed;
188 ini_file->seek (0, IO_SEEK_SET);
189 }
190 }
191 return ini_file;
192 }
193
194 static io_stream*
195 check_ini_sig (io_stream* ini_file, io_stream* ini_sig_file,
196 bool& sig_fail, const char* site, const char* sig_name, HWND owner)
197 {
198 /* Unless the NoVerifyOption is set, check the signature for the
199 current setup and record the result. On a failed signature check
200 the streams are invalidated so even if we tried to read in the
201 setup anyway there's be nothing to parse. */
202 if (!NoVerifyOption && ini_file)
203 {
204 if (!ini_sig_file) {
205 // don't complain if the user installs from localdir and no
206 // signature file is present
207 // TODO: download the ini + signature file instead
208 if (casecompare (site, "localdir"))
209 {
210 note (owner, IDS_SETUPINI_MISSING, sig_name, site);
211 delete ini_file;
212 ini_file = NULL;
213 sig_fail = true;
214 }
215 }
216 else if (!verify_ini_file_sig (ini_file, ini_sig_file, owner))
217 {
218 note (owner, IDS_SIG_INVALID, sig_name, site);
219 delete ini_sig_file;
220 ini_sig_file = NULL;
221 delete ini_file;
222 ini_file = NULL;
223 sig_fail = true;
224 }
225 }
226 return ini_file;
227 }
228
229 static bool
230 do_local_ini (HWND owner)
231 {
232 bool ini_error = false;
233 io_stream *ini_file, *ini_sig_file;
234 // iterate over all setup files found in do_from_local_dir
235 for (IniList::const_iterator n = found_ini_list.begin ();
236 n != found_ini_list.end (); ++n)
237 {
238 GuiParseFeedback myFeedback;
239 IniDBBuilderPackage aBuilder (myFeedback);
240 bool sig_fail = false;
241 std::string current_ini_ext, current_ini_name, current_ini_sig_name;
242
243 current_ini_name = *n;
244 current_ini_sig_name = current_ini_name + ".sig";
245 current_ini_ext = current_ini_name.substr (current_ini_name.rfind (".") + 1);
246 ini_sig_file = io_stream::open ("file://" + current_ini_sig_name, "rb", 0);
247 ini_file = io_stream::open ("file://" + current_ini_name, "rb", 0);
248 ini_file = check_ini_sig (ini_file, ini_sig_file, sig_fail,
249 "localdir", current_ini_sig_name.c_str (), owner);
250 if (ini_file)
251 ini_file = decompress_ini (ini_file, current_ini_name);
252 if (!ini_file || sig_fail)
253 {
254 // no setup found or signature invalid
255 note (owner, IDS_SETUPINI_MISSING, SetupBaseName.c_str (),
256 "localdir");
257 ini_error = true;
258 }
259 else
260 {
261 // grok information from setup
262 myFeedback.babble ("Found ini file - " + current_ini_name);
263 myFeedback.iniName (current_ini_name);
264 int ldl = local_dir.length () + 1;
265 int cap = current_ini_name.rfind ("/" + SetupArch);
266 aBuilder.parse_mirror =
267 rfc1738_unescape (current_ini_name.substr (ldl, cap - ldl));
268 ini_init (ini_file, &aBuilder, myFeedback);
269
270 if (yyparse () || myFeedback.has_errors())
271 {
272 myFeedback.show_errors ();
273 ini_error = true;
274 }
275
276 if (aBuilder.timestamp > setup_timestamp)
277 {
278 setup_timestamp = aBuilder.timestamp;
279 ini_setup_version = aBuilder.version;
280 }
281 delete ini_file;
282 ini_file = NULL;
283 }
284 }
285 return ini_error;
286 }
287
288 static bool
289 do_remote_ini (HWND owner)
290 {
291 bool ini_error = false;
292 io_stream *ini_file = NULL, *ini_sig_file;
293
294 /* FIXME: Get rid of this io_stream pointer travesty. The need to
295 explicitly delete these things is ridiculous. */
296
297 // iterate over all sites
298 for (SiteList::const_iterator n = site_list.begin ();
299 n != site_list.end (); ++n)
300 {
301 GuiParseFeedback myFeedback;
302 IniDBBuilderPackage aBuilder (myFeedback);
303 bool sig_fail = false;
304 std::string current_ini_ext, current_ini_name, current_ini_sig_name;
305 // iterate over known extensions for setup
306 for (IniList::const_iterator ext = setup_ext_list.begin ();
307 ext != setup_ext_list.end ();
308 ext++)
309 {
310 current_ini_ext = *ext;
311 current_ini_name = n->url + SetupIniDir + SetupBaseName + "." + current_ini_ext;
312 current_ini_sig_name = current_ini_name + ".sig";
313 ini_sig_file = get_url_to_membuf (current_ini_sig_name, owner);
314 ini_file = get_url_to_membuf (current_ini_name, owner);
315 ini_file = check_ini_sig (ini_file, ini_sig_file, sig_fail,
316 n->url.c_str (), current_ini_sig_name.c_str (), owner);
317 // stop searching as soon as we find a setup file
318 if (ini_file)
319 break;
320 }
321 if (ini_file)
322 ini_file = decompress_ini (ini_file, current_ini_name);
323 if (!ini_file || sig_fail)
324 {
325 // no setup found or signature invalid
326 note (owner, IDS_SETUPINI_MISSING, SetupBaseName.c_str (), n->url.c_str ());
327 ini_error = true;
328 }
329 else
330 {
331 // grok information from setup
332 myFeedback.iniName (current_ini_name);
333 aBuilder.parse_mirror = n->url;
334 ini_init (ini_file, &aBuilder, myFeedback);
335
336 if (yyparse () || myFeedback.has_errors())
337 {
338 myFeedback.show_errors ();
339 ini_error = true;
340 }
341 else
342 {
343 /* save known-good setup.ini locally */
344 const std::string fp = "file://" + local_dir + "/" +
345 rfc1738_escape_part (n->url) +
346 "/" + SetupIniDir + SetupBaseName + ".ini";
347 io_stream::mkpath_p (PATH_TO_FILE, fp, 0);
348 if (io_stream *out = io_stream::open (fp, "wb", 0))
349 {
350 ini_file->seek (0, IO_SEEK_SET);
351 if (io_stream::copy (ini_file, out) != 0)
352 io_stream::remove (fp);
353 delete out;
354 }
355 }
356 if (aBuilder.timestamp > setup_timestamp)
357 {
358 setup_timestamp = aBuilder.timestamp;
359 ini_setup_version = aBuilder.version;
360 }
361 delete ini_file;
362 ini_file = NULL;
363 }
364 }
365 return ini_error;
366 }
367
368 static bool
369 do_ini_thread (HINSTANCE h, HWND owner)
370 {
371 packagedb db;
372 db.init();
373
374 bool ini_error = true;
375
376 if (source == IDC_SOURCE_LOCALDIR)
377 ini_error = do_local_ini (owner);
378 else
379 ini_error = do_remote_ini (owner);
380
381 if (ini_error)
382 return false;
383
384 if (get_root_dir ().c_str ())
385 {
386 io_stream::mkpath_p (PATH_TO_DIR, "cygfile:///etc/setup", 0755);
387
388 unsigned int old_timestamp = 0;
389 io_stream *ots =
390 io_stream::open ("cygfile:///etc/setup/timestamp", "rt", 0);
391 if (ots)
392 {
393 char temp[20];
394 memset (temp, '\0', 20);
395 if (ots->read (temp, 19))
396 sscanf (temp, "%u", &old_timestamp);
397 delete ots;
398 if (old_timestamp && setup_timestamp
399 && (old_timestamp > setup_timestamp))
400 {
401 int yn = yesno (owner, IDS_OLD_SETUPINI);
402 if (yn == IDNO)
403 Logger ().exit (1);
404 }
405 }
406 if (setup_timestamp)
407 {
408 io_stream *nts =
409 io_stream::open ("cygfile:///etc/setup/timestamp", "wt", 0);
410 if (nts)
411 {
412 char temp[20];
413 sprintf (temp, "%u", setup_timestamp);
414 nts->write (temp, strlen (temp));
415 delete nts;
416 }
417 }
418 }
419
420 LogBabblePrintf (".ini setup_version is %s, our setup_version is %s", ini_setup_version.size () ?
421 ini_setup_version.c_str () : "(null)",
422 setup_version);
423 if (ini_setup_version.size ())
424 {
425 if ((version_compare (setup_version, ini_setup_version) < 0)
426 && !NoVersionCheckOption)
427 note (owner, IDS_OLD_SETUP_VERSION, setup_version,
428 ini_setup_version.c_str ());
429 }
430
431 return true;
432 }
433
434 static DWORD WINAPI
435 do_ini_thread_reflector (void* p)
436 {
437 HANDLE *context;
438 context = (HANDLE*)p;
439
440 SetThreadUILanguage(langid);
441
442 try
443 {
444 bool succeeded = do_ini_thread ((HINSTANCE)context[0], (HWND)context[1]);
445
446 // Tell the progress page that we're done downloading
447 Progress.PostMessageNow (WM_APP_SETUP_INI_DOWNLOAD_COMPLETE, 0, succeeded);
448 }
449 TOPLEVEL_CATCH ((HWND) context[1], "ini");
450
451 ExitThread (0);
452 }
453
454 static HANDLE context[2];
455
456 void
457 do_ini (HINSTANCE h, HWND owner)
458 {
459 context[0] = h;
460 context[1] = owner;
461
462 DWORD threadID;
463 CreateThread (NULL, 0, do_ini_thread_reflector, context, 0, &threadID);
464 }
This page took 0.052476 seconds and 5 git commands to generate.