OSDN Git Service

Avoid running InternetAttemptConnect in pkgInternetAgent constructor.
[mingw/mingw-get.git] / src / pkginet.cpp
1 /*
2  * pkginet.cpp
3  *
4  * $Id$
5  *
6  * Written by Keith Marshall <keithmarshall@users.sourceforge.net>
7  * Copyright (C) 2009, 2010, MinGW Project
8  *
9  *
10  * Implementation of the package download machinery for mingw-get.
11  *
12  *
13  * This is free software.  Permission is granted to copy, modify and
14  * redistribute this software, under the provisions of the GNU General
15  * Public License, Version 3, (or, at your option, any later version),
16  * as published by the Free Software Foundation; see the file COPYING
17  * for licensing details.
18  *
19  * Note, in particular, that this software is provided "as is", in the
20  * hope that it may prove useful, but WITHOUT WARRANTY OF ANY KIND; not
21  * even an implied WARRANTY OF MERCHANTABILITY, nor of FITNESS FOR ANY
22  * PARTICULAR PURPOSE.  Under no circumstances will the author, or the
23  * MinGW Project, accept liability for any damages, however caused,
24  * arising from the use of this software.
25  *
26  */
27 #define WIN32_LEAN_AND_MEAN
28
29 #include <unistd.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <wininet.h>
33 #include <errno.h>
34
35 #include "dmh.h"
36 #include "mkpath.h"
37
38 #include "pkgbase.h"
39 #include "pkgkeys.h"
40 #include "pkgtask.h"
41
42 class pkgInternetAgent
43 {
44   /* A minimal, locally implemented class, instantiated ONCE as a
45    * global object, to ensure that wininet's global initialisation is
46    * completed at the proper time, without us doing it explicitly.
47    */
48   private:
49     HINTERNET SessionHandle;
50
51   public:
52     inline pkgInternetAgent():SessionHandle( NULL )
53     {
54       /* Constructor...
55        *
56        * This is called during DLL initialisation; thus it seems to be
57        * the ideal place to perform one time internet connection setup.
58        * However, Microsoft caution against doing much here, (especially
59        * creation of threads, either directly or indirectly); thus we
60        * defer the connection setup until we ultimately need it.
61        */
62     }
63     inline ~pkgInternetAgent()
64     {
65       /* Destructor...
66        */
67       if( SessionHandle != NULL )
68         Close( SessionHandle );
69     }
70
71     /* Remaining methods are simple inline wrappers for the
72      * wininet functions we plan to use...
73      */
74     inline HINTERNET OpenURL( const char *URL )
75     {
76       /* Open an internet data stream.  This requires an internet
77        * connection to have been established...
78        */
79       if( (SessionHandle == NULL)
80       && (InternetAttemptConnect( 0 ) == ERROR_SUCCESS) )
81         /*
82          * ...so, on first call, we perform the connection setup
83          * which we deferred from the class constructor.
84          */
85         SessionHandle = InternetOpen
86           ( "MinGW Installer", INTERNET_OPEN_TYPE_PRECONFIG,
87              NULL, NULL, 0
88           );
89       return InternetOpenUrl( SessionHandle, URL, NULL, 0, 0, 0 );
90     }
91     inline DWORD QueryStatus( HINTERNET id )
92     {
93       DWORD ok, idx = 0, len = sizeof( ok );
94       if( HttpQueryInfo( id, HTTP_QUERY_FLAG_NUMBER | HTTP_QUERY_STATUS_CODE, &ok, &len, &idx ) )
95         return ok;
96       return 0;
97     }
98     inline int Read( HINTERNET dl, char *buf, size_t max, DWORD *count )
99     {
100       return InternetReadFile( dl, buf, max, count );
101     }
102     inline int Close( HINTERNET id )
103     {
104       return InternetCloseHandle( id );
105     }
106 };
107
108 /* This is the one and only instantiation of an object of this class.
109  */
110 static pkgInternetAgent pkgDownloadAgent;
111
112 class pkgInternetStreamingAgent
113 {
114   /* Another locally implemented class; each individual file download
115    * gets its own instance of this, either as-is for basic data transfer,
116    * or as a specialised derivative of this base class.
117    */
118   protected:
119     const char *filename;
120     const char *dest_template;
121
122     char *dest_file;
123     HINTERNET dl_host;
124     int dl_status;
125
126   private:
127     virtual int TransferData( int );
128
129   public:
130     pkgInternetStreamingAgent( const char*, const char* );
131     virtual ~pkgInternetStreamingAgent();
132
133     virtual int Get( const char* );
134     inline const char *DestFile(){ return dest_file; }
135 };
136
137 pkgInternetStreamingAgent::pkgInternetStreamingAgent
138 ( const char *local_name, const char *dest_specification )
139 {
140   /* Constructor for the pkgInternetStreamingAgent class.
141    */
142   filename = local_name;
143   dest_template = dest_specification;
144   dest_file = (char *)(malloc( mkpath( NULL, dest_template, filename, NULL ) ));
145   if( dest_file != NULL )
146     mkpath( dest_file, dest_template, filename, NULL );
147 }
148
149 pkgInternetStreamingAgent::~pkgInternetStreamingAgent()
150 {
151   /* Destructor needs to free the heap memory allocated by the
152    * constructor, for storage of "dest_file" name.
153    */
154   free( (void *)(dest_file) );
155 }
156
157 int pkgInternetStreamingAgent::TransferData( int fd )
158 {
159   /* In the case of this base class implementation,
160    * we simply read the file's data from the Internet source,
161    * and write a verbatim copy to the destination file.
162    */
163   char buf[8192]; DWORD count, tally = 0;
164   do { dl_status = pkgDownloadAgent.Read( dl_host, buf, sizeof( buf ), &count );
165        dmh_printf( "\rdownloading: %s: %I32d b", filename, tally += count );
166        write( fd, buf, count );
167      } while( dl_status && (count > 0) );
168   dmh_printf( "\rdownloading: %s: %I32d b\n", filename, tally );
169   return dl_status;
170 }
171
172 static const char *get_host_info
173 ( pkgXmlNode *ref, const char *property, const char *fallback = NULL )
174 {
175   /* Helper function to retrieve host information from the XML catalogue.
176    *
177    * Call with property = "url", to retrieve the URL template to pass as
178    * "fmt" argument to mkpath(), or with property = "mirror", to retrieve
179    * the substitution text for the "modifier" argument.
180    */
181   const char *uri = NULL;
182   while( ref != NULL )
183   {
184     /* Starting from the "ref" package entry in the catalogue...
185      */
186     pkgXmlNode *host = ref->FindFirstAssociate( download_host_key );
187     while( host != NULL )
188     {
189       /* Examine its associate tags; if we find one of type
190        * "download-host", with the requisite property, then we
191        * immediately return that property value...
192        */
193       if( (uri = host->GetPropVal( property, NULL )) != NULL )
194         return uri;
195
196       /* Otherwise, we look for any other candidate tags
197        * associated with the same catalogue entry...
198        */
199       host = host->FindNextAssociate( download_host_key );
200     }
201     /* Failing an immediate match, extend the search to the
202      * ancestors of the initial reference entry...
203      */
204     ref = ref->GetParent();
205   }
206   /* ...and ultimately, if no match is found, we return the
207    * specified "fallback" property value.
208    */
209   return fallback;
210 }
211
212 static inline
213 int set_transit_path( const char *path, const char *file, char *buf = NULL )
214 {
215   /* Helper to define the transitional path name for downloaded files,
216    * used to save the file data while the download is in progress.
217    */
218   static const char *transit_dir = "/.in-transit";
219   return mkpath( buf, path, file, transit_dir );
220 }
221
222 int pkgInternetStreamingAgent::Get( const char *from_url )
223 {
224   /* Download a file from the specified internet URL.
225    *
226    * Before download commences, we accept that this may fail...
227    */
228   dl_status = 0;
229
230   /* Set up a "transit-file" to receive the downloaded content.
231    */
232   char transit_file[set_transit_path( dest_template, filename )];
233   int fd; set_transit_path( dest_template, filename, transit_file );
234
235   if( (fd = set_output_stream( transit_file, 0644 )) >= 0 )
236   {
237     /* The "transit-file" is ready to receive incoming data...
238      * Configure and invoke the download handler to copy the data
239      * from the appropriate host URL, to this "transit-file".
240      */
241     if( (dl_host = pkgDownloadAgent.OpenURL( from_url )) != NULL )
242     {
243       if( pkgDownloadAgent.QueryStatus( dl_host ) == HTTP_STATUS_OK )
244       {
245         /* With the download transaction fully specified, we may
246          * request processing of the file transfer...
247          */
248         dl_status = TransferData( fd );
249       }
250
251       /* We are done with the URL handle; close it.
252        */
253       pkgDownloadAgent.Close( dl_host );
254     }
255
256     /* Always close the "transit-file", whether the download
257      * was successful, or not...
258      */
259     close( fd );
260     if( dl_status )
261       /*
262        * When successful, we move the "transit-file" to its
263        * final downloaded location...
264        */
265       rename( transit_file, dest_file );
266     else
267       /* ...otherwise, we discard the incomplete "transit-file",
268        * leaving the caller to diagnose the failure.
269        */
270       unlink( transit_file );
271   }
272
273   /* Report success or failure to the caller...
274    */
275   return dl_status;
276 }
277
278 void pkgActionItem::DownloadArchiveFiles( pkgActionItem *current )
279 {
280   /* Update the local package cache, to ensure that all packages needed
281    * to complete the current set of scheduled actions are present; if any
282    * are missing, invoke an Internet download agent to fetch them.  This
283    * requires us to walk the action list...
284    */
285   while( current != NULL )
286   {
287     /* ...while we haven't run off the end...
288      */
289     if( (current->flags & ACTION_INSTALL) == ACTION_INSTALL )
290     {
291       /* For all packages specified in the current action list,
292        * for which an "install" action is scheduled, and for which
293        * no associated archive file is present in the local archive
294        * cache, place an Internet download agent on standby to fetch
295        * the required archive from a suitable internet mirror host.
296        */
297       const char *package_name = current->Selection()->ArchiveName();
298
299       /* An explicit package name of "none" is a special case, indicating
300        * a "virtual" meta-package; it requires nothing to be downloaded...
301        */
302       if( ! match_if_explicit( package_name, value_none ) )
303       {
304         /* ...but we expect any other package to provide real content,
305          * for which we may need to download the package archive...
306          */
307         pkgInternetStreamingAgent download( package_name, pkgArchivePath() );
308
309         /* Check if the required archive is already available locally...
310          */
311         if( (access( download.DestFile(), R_OK ) != 0) && (errno == ENOENT) )
312         {
313           /* ...if not, ask the download agent to fetch it...
314            */
315           const char *url_template = get_host_info( current->Selection(), uri_key );
316           if( url_template != NULL )
317           {
318             /* ...from the URL constructed from the template specified in
319              * the package repository catalogue (configuration database)...
320              */
321             const char *mirror = get_host_info( current->Selection(), mirror_key );
322             char package_url[mkpath( NULL, url_template, package_name, mirror )];
323             mkpath( package_url, url_template, package_name, mirror );
324             if( ! (download.Get( package_url ) > 0) )
325               dmh_notify( DMH_ERROR,
326                   "Get package: %s: download failed\n", package_url
327                 );
328           }
329           else
330             /* Cannot download; the repository catalogue didn't specify a
331              * template, from which to construct a download URL...
332              */
333             dmh_notify( DMH_ERROR,
334                 "Get package: %s: no URL specified for download\n", package_name
335               );
336         }
337       }
338     }
339     /* Repeat download action, for any additional packages specified
340      * in the current "actions" list.
341      */
342     current = current->next;
343   }
344 }
345
346 #define DATA_CACHE_PATH         "%R" "var/cache/mingw-get/data"
347 #define WORKING_DATA_PATH       "%R" "var/lib/mingw-get/data"
348
349 /* Internet servers host package catalogues in lzma compressed format;
350  * we will decompress them "on the fly", as we download them.  To achieve
351  * this, we will use a variant of the pkgInternetStreamingAgent, using a
352  * specialised TransferData method; additionally, this will incorporate
353  * a special derivative of a pkgLzmaArchiveStream, with its GetRawData
354  * method adapted to stream data from an internet URI, instead of
355  * reading from a local file.
356  *
357  * To derive the pkgInternetLzmaStreamingAgent, we need to include the
358  * specialised declarations of a pkgArchiveStream, in order to make the
359  * declaration of pkgLzmaArchiveStream available as our base class.
360  */
361 #define  PKGSTRM_H_SPECIAL  1
362 #include "pkgstrm.h"
363
364 class pkgInternetLzmaStreamingAgent :
365 public pkgInternetStreamingAgent, public pkgLzmaArchiveStream
366 {
367   /* Specialisation of the pkgInternetStreamingAgent base class,
368    * providing decompressed copies of LZMA encoded files downloaded
369    * from the Internet; (the LZMA decompression capability is derived
370    * from the pkgLzmaArchiveStream base class).
371    */
372   public:
373     /* We need a specialised constructor...
374      */
375     pkgInternetLzmaStreamingAgent( const char*, const char* );
376
377   private:
378     /* Specialisation requires overrides for each of this pair of
379      * methods, (the first from the pkgLzmaArchiveStream base class;
380      * the second from pkgInternetStreamingAgent).
381      */
382     virtual int GetRawData( int, uint8_t*, size_t );
383     virtual int TransferData( int );
384 };
385
386 /* This specialisation of the pkgInternetStreamingAgent class needs its
387  * own constructor, simply to invoke the constructors for the base classes,
388  * (since neither is instantiated by a default constructor).
389  */
390 pkgInternetLzmaStreamingAgent::pkgInternetLzmaStreamingAgent
391 ( const char *local_name, const char *dest_specification ):
392 pkgInternetStreamingAgent( local_name, dest_specification ),
393 /*
394  * Note that, when we come to initialise the lzma streaming component
395  * of this derived class, we will be streaming directly from the internet,
396  * rather than from a file stream, so we don't require a file descriptor
397  * for the input stream; however, the class semantics still expect one.
398  * To avoid accidental association with an existing file stream, we
399  * use a negative value, (which is never a valid file descriptor);
400  * however, we must not choose -1, since the class implementation
401  * will decline to process the stream; hence, we choose -2.
402  */
403 pkgLzmaArchiveStream( -2 ){}
404
405 int pkgInternetLzmaStreamingAgent::GetRawData( int fd, uint8_t *buf, size_t max )
406 {
407   /* Fetch raw (compressed) data from the Internet host, and load it into
408    * the decompression filter's input buffer, whence the TransferData routine
409    * may retrieve it, via the filter, as an uncompressed stream.
410    */
411   DWORD count;
412   dl_status = pkgDownloadAgent.Read( dl_host, (char *)(buf), max, &count );
413   return (int)(count);
414 }
415
416 int pkgInternetLzmaStreamingAgent::TransferData( int fd )
417 {
418   /* In this case, we read the file's data from the Internet source,
419    * stream it through the lzma decompression filter, and write a copy
420    * of the resultant decompressed data to the destination file.
421    */
422   char buf[8192]; DWORD count;
423   do { count = pkgLzmaArchiveStream::Read( buf, sizeof( buf ) );
424        write( fd, buf, count );
425      } while( dl_status && (count > 0) );
426   return dl_status;
427 }
428
429 static const char *serial_number( const char *catalogue )
430 {
431   /* Local helper function to retrieve issue numbers from any repository
432    * package catalogue; returns the result as a duplicate of the internal
433    * string, allocated on the heap (courtesy of the strdup() function).
434    */
435   const char *issue;
436   pkgXmlDocument src( catalogue );
437
438   if(   src.IsOk()
439   &&  ((issue = src.GetRoot()->GetPropVal( issue_key, NULL )) != NULL)  )
440     /*
441      * Found an issue number; return a copy...
442      */
443     return strdup( issue );
444
445   /* If we get to here, we couldn't get a valid issue number;
446    * whatever the reason, return NULL to indicate failure.
447    */
448   return NULL;
449 }
450
451 void pkgXmlDocument::SyncRepository( const char *name, pkgXmlNode *repository )
452 {
453   /* Fetch a named package catalogue from a specified Internet repository.
454    *
455    * Package catalogues are XML files; the master copy on the Internet host
456    * must be stored in lzma compressed format, and named to comply with the
457    * convention "%F.xml.lzma", in which "%F" represents the value of the
458    * "name" argument passed to this pkgXmlDocument class method.
459    */ 
460   const char *url_template;
461   if( (url_template = repository->GetPropVal( uri_key, NULL )) != NULL )
462   {
463     /* Initialise a streaming agent, to manage the catalogue download;
464      * (note that we must include the "%/M" placeholder in the template
465      * for the local name, to accommodate the name of the intermediate
466      * "in-transit" directory used by the streaming agent).
467      */
468     pkgInternetLzmaStreamingAgent download( name, DATA_CACHE_PATH "%/M/%F.xml" );
469     {
470       /* Construct the full URI for the master catalogue, and stream it to
471        * a locally cached, decompressed copy of the XML file.
472        */
473       const char *mirror = repository->GetPropVal( mirror_key, NULL );
474       char catalogue_url[mkpath( NULL, url_template, name, mirror )];
475       mkpath( catalogue_url, url_template, name, mirror );
476       if( download.Get( catalogue_url ) <= 0 )
477         dmh_notify( DMH_ERROR,
478             "Sync Repository: %s: download failed\n", catalogue_url
479           );
480     }
481
482     /* We will only replace our current working copy of this catalogue,
483      * (if one already exists), with the copy we just downloaded, if this
484      * downloaded copy bears an issue number indicating that it is more
485      * recent than the working copy.
486      */
487     const char *repository_version, *working_version;
488     if( (repository_version = serial_number( download.DestFile() )) != NULL )
489     {
490       /* Identify the location for the working copy, (if it exists).
491        */
492       const char *working_copy_path_name = WORKING_DATA_PATH "/%F.xml";
493       char working_copy[mkpath( NULL, working_copy_path_name, name, NULL )];
494       mkpath( working_copy, working_copy_path_name, name, NULL );
495
496       /* Compare issue serial numbers...
497        */
498       if( ((working_version = serial_number( working_copy )) == NULL)
499       ||  ((strcmp( repository_version, working_version )) > 0)        )
500       {
501         /* In these circumstances, we couldn't identify an issue number
502          * for the working copy of the catalogue; (maybe there is no such
503          * catalogue, or maybe it doesn't specify a valid issue number);
504          * in either case, we promote the downloaded copy in its place.
505          *
506          * FIXME: we assume that the working file and the downloaded copy
507          * are stored on the same physical file system device, so we may
508          * replace the former by simply deleting it, and renaming the
509          * latter with its original path name; we make no provision for
510          * replacing the working version by physical data copying.
511          */
512         unlink( working_copy );
513         rename( download.DestFile(), working_copy );
514       }
515
516       /* The issue numbers, returned by the serial_number() function, were
517        * allocated on the heap; free them to avoid leaking memory!
518        */
519       free( (void *)(repository_version) );
520       /*
521        * The working copy issue number may be represented by a NULL pointer;
522        * while it may be safe to call free on this, it just *seems* wrong, so
523        * we check it first, to be certain.
524        */
525       if( working_version != NULL )
526         free( (void *)(working_version) );
527     }
528
529     /* If the downloaded copy of the catalogue is still in the download cache,
530      * we have chosen to keep a previous working copy, so we have no further
531      * use for the downloaded copy; discard it, noting that we don't need to
532      * confirm its existence because this will fail silently, if it is no
533      * longer present.
534      */
535     unlink( download.DestFile() );
536   }
537 }
538
539 /* $RCSfile$: end of file */