avoid broken pipe when looking for aria
[platform/upstream/libzypp.git] / zypp / media / MediaAria2c.cc
1 /*---------------------------------------------------------------------\
2 |                          ____ _   __ __ ___                          |
3 |                         |__  / \ / / . \ . \                         |
4 |                           / / \ V /|  _/  _/                         |
5 |                          / /__ | | | | | |                           |
6 |                         /_____||_| |_| |_|                           |
7 |                                                                      |
8 \---------------------------------------------------------------------*/
9 /** \file zypp/media/MediaAria2c.cc
10  *
11 */
12
13 #include <iostream>
14 #include <list>
15
16 #include "zypp/base/Logger.h"
17 #include "zypp/ExternalProgram.h"
18 #include "zypp/ProgressData.h"
19 #include "zypp/base/String.h"
20 #include "zypp/base/Gettext.h"
21 #include "zypp/base/Sysconfig.h"
22 #include "zypp/base/Gettext.h"
23 #include "zypp/ZYppCallbacks.h"
24
25 #include "zypp/Edition.h"
26 #include "zypp/Target.h"
27 #include "zypp/ZYppFactory.h"
28
29 #include "zypp/media/MediaAria2c.h"
30 #include "zypp/media/proxyinfo/ProxyInfos.h"
31 #include "zypp/media/ProxyInfo.h"
32 #include "zypp/media/MediaUserAuth.h"
33 #include "zypp/media/MediaCurl.h"
34 #include "zypp/thread/Once.h"
35 #include <cstdlib>
36 #include <sys/types.h>
37 #include <sys/stat.h>
38 #include <sys/mount.h>
39 #include <errno.h>
40 #include <dirent.h>
41 #include <unistd.h>
42 #include <boost/format.hpp>
43
44 #define  DETECT_DIR_INDEX       0
45 #define  CONNECT_TIMEOUT        60
46 #define  TRANSFER_TIMEOUT       60 * 3
47 #define  TRANSFER_TIMEOUT_MAX   60 * 60
48
49
50 using namespace std;
51 using namespace zypp::base;
52
53 namespace zypp
54 {
55 namespace media
56 {
57
58 Pathname MediaAria2c::_cookieFile = "/var/lib/YaST2/cookies";
59 Pathname MediaAria2c::_aria2cPath = "/usr/local/bin/aria2c";
60 std::string MediaAria2c::_aria2cVersion = "WE DON'T KNOW ARIA2C VERSION";
61
62 //check if aria2c is present in the system
63 bool
64 MediaAria2c::existsAria2cmd()
65 {
66     const char* argv[] =
67     {
68       "which",
69       "aria2c",
70       NULL
71     };
72
73     ExternalProgram aria(argv, ExternalProgram::Stderr_To_Stdout);
74     
75     for(std::string ariaResponse( aria.receiveLine());
76         ariaResponse.length();
77         ariaResponse = aria.receiveLine())
78     {
79         // nothing
80     }
81     
82     return ( aria.close() == 0 );
83 }
84
85 /**
86  * comannd line for aria.
87  * The argument list gets passed as reference
88  * and it is filled.
89  */
90 void fillAriaCmdLine( const Pathname &ariapath,
91                       const string &ariaver,
92                       const TransferSettings &s,
93                       const Url &url,
94                       const Pathname &destination,
95                       ExternalProgram::Arguments &args )
96 {
97     args.push_back(ariapath.c_str());
98     args.push_back(str::form("--user-agent=%s", s.userAgentString().c_str()));
99     args.push_back("--summary-interval=1");
100     args.push_back("--follow-metalink=mem");
101     args.push_back("--check-integrity=true");
102
103     // only present in recent aria lets find out the aria version
104     vector<string> fields;    
105     // "aria2c version x.x"
106     str::split( ariaver, std::back_inserter(fields));
107     if ( fields.size() == 3 )
108     {
109         if ( Edition(fields[2]) >= Edition("1.1.2") )
110             args.push_back( "--use-head=false");
111     }
112     
113     if ( s.maxDownloadSpeed() > 0 )
114         args.push_back(str::form("--max-download-limit=%ld", s.maxDownloadSpeed()));
115     if ( s.minDownloadSpeed() > 0 )
116         args.push_back(str::form("--lowest-speed-limit=%ld", s.minDownloadSpeed()));
117
118     args.push_back(str::form("--max-tries=%ld", s.maxSilentTries()));
119
120     if ( Edition(fields[2]) < Edition("1.2.0") )
121         WAR << "aria2c is older than 1.2.0, some features may be disabled" << endl;
122     
123     // TODO make this one configurable
124     args.push_back(str::form("--max-concurrent-downloads=%ld", s.maxConcurrentConnections()));
125
126     // add the anonymous id.
127     for ( TransferSettings::Headers::const_iterator it = s.headersBegin();
128           it != s.headersEnd();
129           ++it )
130         args.push_back(str::form("--header=%s", it->c_str() ));
131         
132     args.push_back( str::form("--connect-timeout=%ld", s.timeout()));
133
134     if ( s.username().empty() )
135     {
136         if ( url.getScheme() == "ftp" )
137         {
138             // set anonymous ftp
139             args.push_back(str::form("--ftp-user=%s", "suseuser" ));
140             args.push_back(str::form("--ftp-passwd=%s", VERSION ));
141
142             string id = "yast2";
143             id += VERSION;
144             DBG << "Anonymous FTP identification: '" << id << "'" << endl;
145         }
146     }
147     else
148     {
149         if ( url.getScheme() == "ftp" )
150             args.push_back(str::form("--ftp-user=%s", s.username().c_str() ));
151         else if ( url.getScheme() == "http" ||
152                   url.getScheme() == "https" )
153             args.push_back(str::form("--http-user=%s", s.username().c_str() ));
154         
155         if ( s.password().size() )
156         {
157             if ( url.getScheme() == "ftp" )
158                 args.push_back(str::form("--ftp-passwd=%s", s.password().c_str() ));
159             else if ( url.getScheme() == "http" ||
160                       url.getScheme() == "https" )
161                 args.push_back(str::form("--http-passwd=%s", s.password().c_str() ));
162         }
163     }
164     
165     if ( s.proxyEnabled() )
166     {
167         args.push_back(str::form("--http-proxy=%s", s.proxy().c_str() ));
168         if ( ! s.proxyUsername().empty() )
169         {
170             args.push_back(str::form("--http-proxy-user=%s", s.proxyUsername().c_str() ));
171             if ( ! s.proxyPassword().empty() )
172                 args.push_back(str::form("--http-proxy-passwd=%s", s.proxyPassword().c_str() ));
173         }
174     }
175
176     if ( ! destination.empty() )
177         args.push_back(str::form("--dir=%s", destination.c_str()));
178
179     args.push_back(url.asString().c_str());
180 }
181
182 const char *const MediaAria2c::agentString()
183 {
184   // we need to add the release and identifier to the
185   // agent string.
186   // The target could be not initialized, and then this information
187   // is not available.
188   Target_Ptr target = zypp::getZYpp()->getTarget();
189
190   static const std::string _value(
191     str::form(
192        "ZYpp %s (%s) %s"
193        , VERSION
194        , MediaAria2c::_aria2cVersion.c_str()
195        , target ? target->targetDistribution().c_str() : ""
196     )
197   );
198   return _value.c_str();
199 }
200
201
202
203 MediaAria2c::MediaAria2c( const Url &      url_r,
204                       const Pathname & attach_point_hint_r )
205     : MediaCurl( url_r, attach_point_hint_r )
206 {
207   MIL << "MediaAria2c::MediaAria2c(" << url_r << ", " << attach_point_hint_r << ")" << endl;
208
209    //At this point, we initialize aria2c path
210    _aria2cPath = Pathname( whereisAria2c().asString() );
211
212    //Get aria2c version
213    _aria2cVersion = getAria2cVersion();
214 }
215
216 void MediaAria2c::attachTo (bool next)
217 {
218   MediaCurl::attachTo(next);
219   _settings.setUserAgentString(agentString());
220 }
221
222 bool
223 MediaAria2c::checkAttachPoint(const Pathname &apoint) const
224 {
225     return MediaCurl::checkAttachPoint( apoint );
226 }
227
228 void MediaAria2c::disconnectFrom()
229 {
230     MediaCurl::disconnectFrom();
231 }
232
233 void MediaAria2c::releaseFrom( const std::string & ejectDev )
234 {
235   MediaCurl::releaseFrom(ejectDev);
236 }
237
238 static Url getFileUrl(const Url & url, const Pathname & filename)
239 {
240   Url newurl(url);
241   string path = url.getPathName();
242   if ( !path.empty() && path != "/" && *path.rbegin() == '/' &&
243        filename.absolute() )
244   {
245     // If url has a path with trailing slash, remove the leading slash from
246     // the absolute file name
247     path += filename.asString().substr( 1, filename.asString().size() - 1 );
248   }
249   else if ( filename.relative() )
250   {
251     // Add trailing slash to path, if not already there
252     if (path.empty()) path = "/";
253     else if (*path.rbegin() != '/' ) path += "/";
254     // Remove "./" from begin of relative file name
255     path += filename.asString().substr( 2, filename.asString().size() - 2 );
256   }
257   else
258   {
259     path += filename.asString();
260   }
261
262   newurl.setPathName(path);
263   return newurl;
264 }
265
266 void MediaAria2c::getFile( const Pathname & filename ) const
267 {
268     // Use absolute file name to prevent access of files outside of the
269     // hierarchy below the attach point.
270     getFileCopy(filename, localPath(filename).absolutename());
271 }
272
273 void MediaAria2c::getFileCopy( const Pathname & filename , const Pathname & target) const
274 {
275   callback::SendReport<DownloadProgressReport> report;
276
277   Url fileurl(getFileUrl(_url, filename));
278
279   bool retry = false;
280
281   ExternalProgram::Arguments args;
282
283   fillAriaCmdLine(_aria2cPath, _aria2cVersion, _settings, fileurl, target.dirname(), args);
284   
285   do
286   {
287     try
288     {
289       report->start(_url, target.asString() );
290
291       ExternalProgram aria(args, ExternalProgram::Stderr_To_Stdout);
292       int nLine = 0;
293
294       //Process response
295       for(std::string ariaResponse( aria.receiveLine());
296           ariaResponse.length();
297           ariaResponse = aria.receiveLine())
298       {
299         //cout << ariaResponse;
300
301         if (!ariaResponse.substr(0,31).compare("Exception: Authorization failed") )
302         {
303             ZYPP_THROW(MediaUnauthorizedException(
304                   _url, "Login failed.", "Login failed", "auth hint"
305                 ));
306         }
307         if (!ariaResponse.substr(0,29).compare("Exception: Resource not found") )
308         {
309             ZYPP_THROW(MediaFileNotFoundException(_url, filename));
310         }
311
312         if (!ariaResponse.substr(0,9).compare("[#2 SIZE:"))
313         {
314           if (!nLine)
315           {
316             size_t left_bound = ariaResponse.find('(',0) + 1;
317             size_t count = ariaResponse.find('%',left_bound) - left_bound;
318             //cout << ariaResponse.substr(left_bound, count) << endl;
319             //progressData.toMax();
320             report->progress ( std::atoi(ariaResponse.substr(left_bound, count).c_str()), _url, -1, -1 );
321             nLine = 1;
322           }
323           else
324           {
325             nLine = 0;
326           }
327         }
328       }
329
330       aria.close();
331
332       report->finish( _url ,  zypp::media::DownloadProgressReport::NO_ERROR, "");
333       retry = false;
334     }
335
336     // retry with proper authentication data
337     catch (MediaUnauthorizedException & ex_r)
338     {
339       if(authenticate(ex_r.hint(), !retry))
340         retry = true;
341       else
342       {
343         report->finish(fileurl, zypp::media::DownloadProgressReport::ACCESS_DENIED, ex_r.asUserHistory());
344         ZYPP_RETHROW(ex_r);
345       }
346
347     }
348     // unexpected exception
349     catch (MediaException & excpt_r)
350     {
351       // FIXME: error number fix
352       report->finish(fileurl, zypp::media::DownloadProgressReport::ERROR, excpt_r.asUserHistory());
353       ZYPP_RETHROW(excpt_r);
354     }
355   }
356   while (retry);
357
358   report->finish(fileurl, zypp::media::DownloadProgressReport::NO_ERROR, "");
359 }
360
361 bool MediaAria2c::getDoesFileExist( const Pathname & filename ) const
362 {
363     return MediaCurl::getDoesFileExist(filename);
364 }
365
366 bool MediaAria2c::doGetDoesFileExist( const Pathname & filename ) const
367 {
368     return MediaCurl::doGetDoesFileExist(filename);
369 }
370     
371 void MediaAria2c::getDir( const Pathname & dirname, bool recurse_r ) const
372 {
373     MediaCurl::getDir(dirname, recurse_r);
374 }
375
376 bool MediaAria2c::authenticate(const std::string & availAuthTypes, bool firstTry) const
377 {
378     return false;
379 }
380
381
382 void MediaAria2c::getDirInfo( std::list<std::string> & retlist,
383                                const Pathname & dirname, bool dots ) const
384 {
385   getDirectoryYast( retlist, dirname, dots );
386 }
387
388 void MediaAria2c::getDirInfo( filesystem::DirContent & retlist,
389                             const Pathname & dirname, bool dots ) const
390 {
391   getDirectoryYast( retlist, dirname, dots );
392 }
393
394 std::string MediaAria2c::getAria2cVersion()
395 {
396     const char* argv[] =
397     {
398         _aria2cPath.c_str(),
399       "--version",
400       NULL
401     };
402
403     ExternalProgram aria(argv, ExternalProgram::Stderr_To_Stdout);
404
405     std::string vResponse = aria.receiveLine();
406     aria.close();
407     return str::trim(vResponse);
408 }
409
410 #define ARIA_DEFAULT_BINARY "/usr/bin/aria2c"
411
412 Pathname MediaAria2c::whereisAria2c()
413 {
414     Pathname aria2cPathr(ARIA_DEFAULT_BINARY);
415
416     const char* argv[] =
417     {
418       "which",
419       "aria2c",
420       NULL
421     };
422
423     ExternalProgram aria(argv, ExternalProgram::Stderr_To_Stdout);
424
425     std::string ariaResponse( aria.receiveLine());
426     int code = aria.close();
427
428     if( code == 0 )
429     {
430         aria2cPathr = str::trim(ariaResponse);
431         MIL << "We will use aria2c located here:  " << aria2cPathr << endl;
432     }
433     else
434     {
435         MIL << "We don't know were is ari2ac binary. We will use aria2c located here:  " << aria2cPathr << endl;
436     }
437
438     return aria2cPathr;
439 }
440
441 } // namespace media
442 } // namespace zypp
443 //