use which instead of whereis, at least gives a error code back
[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/Target.h"
26 #include "zypp/ZYppFactory.h"
27
28 #include "zypp/media/MediaAria2c.h"
29 #include "zypp/media/proxyinfo/ProxyInfos.h"
30 #include "zypp/media/ProxyInfo.h"
31 #include "zypp/media/MediaUserAuth.h"
32 #include "zypp/thread/Once.h"
33 #include <cstdlib>
34 #include <sys/types.h>
35 #include <sys/stat.h>
36 #include <sys/mount.h>
37 #include <errno.h>
38 #include <dirent.h>
39 #include <unistd.h>
40 #include <boost/format.hpp>
41
42 #define  DETECT_DIR_INDEX       0
43 #define  CONNECT_TIMEOUT        60
44 #define  TRANSFER_TIMEOUT       60 * 3
45 #define  TRANSFER_TIMEOUT_MAX   60 * 60
46
47
48 using namespace std;
49 using namespace zypp::base;
50
51 namespace zypp
52 {
53 namespace media
54 {
55
56 Pathname MediaAria2c::_cookieFile = "/var/lib/YaST2/cookies";
57 Pathname MediaAria2c::_aria2cPath = "/usr/local/bin/aria2c";
58 std::string MediaAria2c::_aria2cVersion = "WE DON'T KNOW ARIA2C VERSION";
59
60 //check if aria2c is present in the system
61 bool
62 MediaAria2c::existsAria2cmd()
63 {
64     const char* argv[] =
65     {
66       "which",
67       "aria2c",
68       NULL
69     };
70
71     ExternalProgram aria(argv, ExternalProgram::Stderr_To_Stdout);
72     return ( aria.close() == 0 );
73 }
74
75 static const char *const anonymousIdHeader()
76 {
77   // we need to add the release and identifier to the
78   // agent string.
79   // The target could be not initialized, and then this information
80   // is not available.
81   Target_Ptr target = zypp::getZYpp()->getTarget();
82
83   static const std::string _value(
84       str::form(
85           "X-Zypp-AnonymousId: %s",
86           target ? target->anonymousUniqueId().c_str() : "" )
87   );
88   return _value.c_str();
89 }
90
91 static const char *const distributionFlavorHeader()
92 {
93   // we need to add the release and identifier to the
94   // agent string.
95   // The target could be not initialized, and then this information
96   // is not available.
97   Target_Ptr target = zypp::getZYpp()->getTarget();
98
99   static const std::string _value(
100       str::trim( str::form(
101           "X-ZYpp-DistributionFlavor: %s",
102           target ? target->distributionFlavor().c_str() : "" ) )
103   );
104   return _value.c_str();
105 }
106
107 const char *const MediaAria2c::agentString()
108 {
109   // we need to add the release and identifier to the
110   // agent string.
111   // The target could be not initialized, and then this information
112   // is not available.
113   Target_Ptr target = zypp::getZYpp()->getTarget();
114
115   static const std::string _value(
116     str::form(
117        "ZYpp %s (%s) %s"
118        , VERSION
119        , MediaAria2c::_aria2cVersion.c_str()
120        , target ? target->targetDistribution().c_str() : ""
121     )
122   );
123   return _value.c_str();
124 }
125
126
127 MediaAria2c::MediaAria2c( const Url &      url_r,
128                       const Pathname & attach_point_hint_r )
129     : MediaHandler( url_r, attach_point_hint_r,
130                     "/", // urlpath at attachpoint
131                     true ) // does_download
132 {
133   MIL << "MediaAria2c::MediaAria2c(" << url_r << ", " << attach_point_hint_r << ")" << endl;
134
135   if( !attachPoint().empty())
136   {
137     PathInfo ainfo(attachPoint());
138     Pathname apath(attachPoint() + "XXXXXX");
139     char    *atemp = ::strdup( apath.asString().c_str());
140     char    *atest = NULL;
141     if( !ainfo.isDir() || !ainfo.userMayRWX() ||
142          atemp == NULL || (atest=::mkdtemp(atemp)) == NULL)
143     {
144       WAR << "attach point " << ainfo.path()
145           << " is not useable for " << url_r.getScheme() << endl;
146       setAttachPoint("", true);
147     }
148     else if( atest != NULL)
149       ::rmdir(atest);
150
151     if( atemp != NULL)
152       ::free(atemp);
153   }
154
155    //At this point, we initialize aria2c path
156    _aria2cPath = Pathname( whereisAria2c().asString() );
157
158    //Get aria2c version
159    _aria2cVersion = getAria2cVersion();
160 }
161
162 void MediaAria2c::attachTo (bool next)
163 {
164    // clear last arguments
165    _args.clear();
166
167   if ( next )
168     ZYPP_THROW(MediaNotSupportedException(_url));
169
170   if ( !_url.isValid() )
171     ZYPP_THROW(MediaBadUrlException(_url));
172
173   if( !isUseableAttachPoint(attachPoint()))
174   {
175     std::string mountpoint = createAttachPoint().asString();
176
177     if( mountpoint.empty())
178       ZYPP_THROW( MediaBadAttachPointException(url()));
179
180     setAttachPoint( mountpoint, true);
181   }
182
183   disconnectFrom();
184
185   // Build the aria command.
186   _args.push_back(_aria2cPath.asString());
187   _args.push_back(str::form("--user-agent=%s", agentString()));
188   _args.push_back("--summary-interval=1");
189   _args.push_back("--follow-metalink=mem");
190   _args.push_back( "--check-integrity=true");
191
192    // add the anonymous id.
193    _args.push_back(str::form("--header=%s", anonymousIdHeader() ));
194    _args.push_back(str::form("--header=%s", distributionFlavorHeader() ));
195   // TODO add debug option
196
197   // Transfer timeout
198   {
199     _xfer_timeout = TRANSFER_TIMEOUT;
200
201     std::string param(_url.getQueryParam("timeout"));
202     if( !param.empty())
203     {
204       long num = str::strtonum<long>( param);
205       if( num >= 0 && num <= TRANSFER_TIMEOUT_MAX)
206         _xfer_timeout = num;
207     }
208   }
209
210   _args.push_back( str::form("--connect-timeout=%d", CONNECT_TIMEOUT));
211
212   // TODO limit redirections
213   // TODO Implement certificate validation
214
215   // FTP defaults to anonymous
216
217
218   if ( _url.getUsername().empty() )
219   {
220     if ( _url.getScheme() == "ftp" )
221     {
222       string id = "yast2@";
223       id += VERSION;
224       DBG << "Anonymous FTP identification: '" << id << "'" << endl;
225       _userpwd = "anonymous:" + id;
226     }
227   }
228   else
229   {
230      if ( _url.getScheme() == "ftp" )
231      {
232          _args.push_back(str::form("--ftp-user=%s", _url.getUsername().c_str() ));
233      }
234      else if ( _url.getScheme() == "http" ||
235                _url.getScheme() == "https" )
236     {
237         _args.push_back(str::form("--http-user=%s", _url.getUsername().c_str() ));
238     }
239
240     if ( _url.getPassword().size() )
241     {
242       if ( _url.getScheme() == "ftp" )
243       {
244           _args.push_back(str::form("--ftp-passwd=%s", _url.getPassword().c_str() ));
245       }
246       else if ( _url.getScheme() == "http" ||
247                _url.getScheme() == "https" )
248       {
249           _args.push_back(str::form("--http-passwd=%s", _url.getPassword().c_str() ));
250       }
251     }
252   }
253
254   // note, aria2c does not support setting the auth type with
255   // (basic, digest yet)
256
257
258   /*---------------------------------------------------------------*
259    CURLOPT_PROXY: host[:port]
260
261    Url::option(proxy and proxyport)
262    If not provided, /etc/sysconfig/proxy is evaluated
263    *---------------------------------------------------------------*/
264
265   _proxy = _url.getQueryParam( "proxy" );
266
267   if ( ! _proxy.empty() )
268   {
269     string proxyport( _url.getQueryParam( "proxyport" ) );
270     if ( ! proxyport.empty() ) {
271       _proxy += ":" + proxyport;
272     }
273   }
274   else
275   {
276
277     ProxyInfo proxy_info (ProxyInfo::ImplPtr(new ProxyInfoSysconfig("proxy")));
278
279     if ( proxy_info.enabled())
280     {
281       bool useproxy = true;
282
283       std::list<std::string> nope = proxy_info.noProxy();
284       for (ProxyInfo::NoProxyIterator it = proxy_info.noProxyBegin();
285            it != proxy_info.noProxyEnd();
286            it++)
287       {
288         std::string host( str::toLower(_url.getHost()));
289         std::string temp( str::toLower(*it));
290
291         // no proxy if it points to a suffix
292         // preceeded by a '.', that maches
293         // the trailing portion of the host.
294         if( temp.size() > 1 && temp.at(0) == '.')
295         {
296           if(host.size() > temp.size() &&
297              host.compare(host.size() - temp.size(), temp.size(), temp) == 0)
298           {
299             DBG << "NO_PROXY: '" << *it  << "' matches host '"
300                                  << host << "'" << endl;
301             useproxy = false;
302             break;
303           }
304         }
305         else
306         // no proxy if we have an exact match
307         if( host == temp)
308         {
309           DBG << "NO_PROXY: '" << *it  << "' matches host '"
310                                << host << "'" << endl;
311           useproxy = false;
312           break;
313         }
314       }
315
316       if ( useproxy ) {
317         _proxy = proxy_info.proxy(_url.getScheme());
318       }
319     }
320   }
321
322   DBG << "Proxy: " << (_proxy.empty() ? "-none-" : _proxy) << endl;
323
324   if ( ! _proxy.empty() )
325   {
326       _args.push_back(str::form("--http-proxy=%s", _proxy.c_str() ));
327
328      /*---------------------------------------------------------------*
329      CURLOPT_PROXYUSERPWD: [user name]:[password]
330
331      Url::option(proxyuser and proxypassword) -> CURLOPT_PROXYUSERPWD
332      If not provided, $HOME/.curlrc is evaluated
333      *---------------------------------------------------------------*/
334
335     _proxyuserpwd = _url.getQueryParam( "proxyuser" );
336
337     if ( ! _proxyuserpwd.empty() ) {
338         _args.push_back(str::form("--http-proxy-user=%s", _proxyuserpwd.c_str() ));
339
340       string proxypassword( _url.getQueryParam( "proxypassword" ) );
341       if ( ! proxypassword.empty() ) {
342           _args.push_back(str::form("--http-proxy-passwd=%s", proxypassword.c_str() ));
343       }
344     }
345   }
346
347   //_currentCookieFile = _cookieFile.asString();
348   //_args.push_back(str::form("--load-cookies=%s", _currentCookieFile.c_str()));
349   //NOTE cookie jar?
350
351   // FIXME: need a derived class to propelly compare url's
352   MediaSourceRef media( new MediaSource(_url.getScheme(), _url.asString()));
353   setMediaSource(media);
354
355 }
356
357 bool
358 MediaAria2c::checkAttachPoint(const Pathname &apoint) const
359 {
360   return MediaHandler::checkAttachPoint( apoint, true, true);
361 }
362
363 void MediaAria2c::disconnectFrom()
364 {
365 }
366
367 void MediaAria2c::releaseFrom( const std::string & ejectDev )
368 {
369   disconnect();
370 }
371
372 static Url getFileUrl(const Url & url, const Pathname & filename)
373 {
374   Url newurl(url);
375   string path = url.getPathName();
376   if ( !path.empty() && path != "/" && *path.rbegin() == '/' &&
377        filename.absolute() )
378   {
379     // If url has a path with trailing slash, remove the leading slash from
380     // the absolute file name
381     path += filename.asString().substr( 1, filename.asString().size() - 1 );
382   }
383   else if ( filename.relative() )
384   {
385     // Add trailing slash to path, if not already there
386     if (path.empty()) path = "/";
387     else if (*path.rbegin() != '/' ) path += "/";
388     // Remove "./" from begin of relative file name
389     path += filename.asString().substr( 2, filename.asString().size() - 2 );
390   }
391   else
392   {
393     path += filename.asString();
394   }
395
396   newurl.setPathName(path);
397   return newurl;
398 }
399
400 void MediaAria2c::getFile( const Pathname & filename ) const
401 {
402     // Use absolute file name to prevent access of files outside of the
403     // hierarchy below the attach point.
404     getFileCopy(filename, localPath(filename).absolutename());
405 }
406
407 void MediaAria2c::getFileCopy( const Pathname & filename , const Pathname & target) const
408 {
409   callback::SendReport<DownloadProgressReport> report;
410
411   Url fileurl(getFileUrl(_url, filename));
412
413   bool retry = false;
414
415   ExternalProgram::Arguments args = _args;
416   args.push_back(str::form("--dir=%s", target.dirname().c_str()));
417
418   args.push_back(fileurl.asString());
419
420   do
421   {
422     try
423     {
424       report->start(_url, target.asString() );
425
426       ExternalProgram aria(args, ExternalProgram::Stderr_To_Stdout);
427       int nLine = 0;
428
429       //Process response
430       for(std::string ariaResponse( aria.receiveLine());
431           ariaResponse.length();
432           ariaResponse = aria.receiveLine())
433       {
434         //cout << ariaResponse;
435
436         if (!ariaResponse.substr(0,31).compare("Exception: Authorization failed") )
437         {
438             ZYPP_THROW(MediaUnauthorizedException(
439                   _url, "Login failed.", "Login failed", "auth hint"
440                 ));
441         }
442         if (!ariaResponse.substr(0,29).compare("Exception: Resource not found") )
443         {
444             ZYPP_THROW(MediaFileNotFoundException(_url, filename));
445         }
446
447         if (!ariaResponse.substr(0,9).compare("[#2 SIZE:"))
448         {
449           if (!nLine)
450           {
451             size_t left_bound = ariaResponse.find('(',0) + 1;
452             size_t count = ariaResponse.find('%',left_bound) - left_bound;
453             //cout << ariaResponse.substr(left_bound, count) << endl;
454             //progressData.toMax();
455             report->progress ( std::atoi(ariaResponse.substr(left_bound, count).c_str()), _url, -1, -1 );
456             nLine = 1;
457           }
458           else
459           {
460             nLine = 0;
461           }
462         }
463       }
464
465       aria.close();
466
467       report->finish( _url ,  zypp::media::DownloadProgressReport::NO_ERROR, "");
468       retry = false;
469     }
470
471     // retry with proper authentication data
472     catch (MediaUnauthorizedException & ex_r)
473     {
474       if(authenticate(ex_r.hint(), !retry))
475         retry = true;
476       else
477       {
478         report->finish(fileurl, zypp::media::DownloadProgressReport::ACCESS_DENIED, ex_r.asUserHistory());
479         ZYPP_RETHROW(ex_r);
480       }
481
482     }
483     // unexpected exception
484     catch (MediaException & excpt_r)
485     {
486       // FIXME: error number fix
487       report->finish(fileurl, zypp::media::DownloadProgressReport::ERROR, excpt_r.asUserHistory());
488       ZYPP_RETHROW(excpt_r);
489     }
490   }
491   while (retry);
492
493   report->finish(fileurl, zypp::media::DownloadProgressReport::NO_ERROR, "");
494 }
495
496 bool MediaAria2c::getDoesFileExist( const Pathname & filename ) const
497 {
498   bool retry = false;
499   AuthData auth_data;
500
501   do
502   {
503     try
504     {
505       return doGetDoesFileExist( filename );
506     }
507     // authentication problem, retry with proper authentication data
508     catch (MediaUnauthorizedException & ex_r)
509     {
510       if(authenticate(ex_r.hint(), !retry))
511         retry = true;
512       else
513         ZYPP_RETHROW(ex_r);
514     }
515     // unexpected exception
516     catch (MediaException & excpt_r)
517     {
518       ZYPP_RETHROW(excpt_r);
519     }
520   }
521   while (retry);
522
523   return false;
524 }
525
526 bool MediaAria2c::doGetDoesFileExist( const Pathname & filename ) const
527 {
528
529   DBG << filename.asString() << endl;
530   return true;
531 }
532
533 void MediaAria2c::getDir( const Pathname & dirname, bool recurse_r ) const
534 {
535   filesystem::DirContent content;
536   getDirInfo( content, dirname, /*dots*/false );
537
538   for ( filesystem::DirContent::const_iterator it = content.begin(); it != content.end(); ++it ) {
539       Pathname filename = dirname + it->name;
540       int res = 0;
541
542       switch ( it->type ) {
543       case filesystem::FT_NOT_AVAIL: // old directory.yast contains no typeinfo at all
544       case filesystem::FT_FILE:
545         getFile( filename );
546         break;
547       case filesystem::FT_DIR: // newer directory.yast contain at least directory info
548         if ( recurse_r ) {
549           getDir( filename, recurse_r );
550         } else {
551           res = assert_dir( localPath( filename ) );
552           if ( res ) {
553             WAR << "Ignore error (" << res <<  ") on creating local directory '" << localPath( filename ) << "'" << endl;
554           }
555         }
556         break;
557       default:
558         // don't provide devices, sockets, etc.
559         break;
560       }
561   }
562 }
563
564 bool MediaAria2c::authenticate(const std::string & availAuthTypes, bool firstTry) const
565 {
566     return false;
567 }
568
569
570 void MediaAria2c::getDirInfo( std::list<std::string> & retlist,
571                                const Pathname & dirname, bool dots ) const
572 {
573   getDirectoryYast( retlist, dirname, dots );
574 }
575
576 void MediaAria2c::getDirInfo( filesystem::DirContent & retlist,
577                             const Pathname & dirname, bool dots ) const
578 {
579   getDirectoryYast( retlist, dirname, dots );
580 }
581
582 std::string MediaAria2c::getAria2cVersion()
583 {
584     const char* argv[] =
585     {
586         _aria2cPath.c_str(),
587       "--version",
588       NULL
589     };
590
591     ExternalProgram aria(argv, ExternalProgram::Stderr_To_Stdout);
592
593     std::string vResponse = aria.receiveLine();
594     aria.close();
595     return str::trim(vResponse);
596 }
597
598 #define ARIA_DEFAULT_BINARY "/usr/bin/aria2c"
599
600 Pathname MediaAria2c::whereisAria2c()
601 {
602     Pathname aria2cPathr(ARIA_DEFAULT_BINARY);
603
604     const char* argv[] =
605     {
606       "which",
607       "aria2c",
608       NULL
609     };
610
611     ExternalProgram aria(argv, ExternalProgram::Stderr_To_Stdout);
612
613     std::string ariaResponse( aria.receiveLine());
614     int code = aria.close();
615
616     if( code == 0 )
617     {
618         aria2cPathr = str::trim(ariaResponse);
619         MIL << "We will use aria2c located here:  " << aria2cPathr << endl;
620     }
621     else
622     {
623         MIL << "We don't know were is ari2ac binary. We will use aria2c located here:  " << aria2cPathr << endl;
624     }
625
626     return aria2cPathr;
627 }
628
629 } // namespace media
630 } // namespace zypp
631 //