OSDN Git Service

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