diff options
Diffstat (limited to 'methods')
-rw-r--r-- | methods/connect.cc | 6 | ||||
-rw-r--r-- | methods/copy.cc | 20 | ||||
-rw-r--r-- | methods/file.cc | 2 | ||||
-rw-r--r-- | methods/ftp.cc | 53 | ||||
-rw-r--r-- | methods/ftp.h | 2 | ||||
-rw-r--r-- | methods/gpgv.cc | 1 | ||||
-rw-r--r-- | methods/gzip.cc | 24 | ||||
-rw-r--r-- | methods/http.cc | 983 | ||||
-rw-r--r-- | methods/http.h | 139 | ||||
-rw-r--r-- | methods/https.cc | 151 | ||||
-rw-r--r-- | methods/https.h | 33 | ||||
-rw-r--r-- | methods/makefile | 6 | ||||
-rw-r--r-- | methods/mirror.cc | 14 | ||||
-rw-r--r-- | methods/mirror.h | 2 | ||||
-rw-r--r-- | methods/rfc2553emu.h | 2 | ||||
-rw-r--r-- | methods/rred.cc | 1185 | ||||
-rw-r--r-- | methods/rsh.cc | 38 | ||||
-rw-r--r-- | methods/server.cc | 668 | ||||
-rw-r--r-- | methods/server.h | 144 |
19 files changed, 1929 insertions, 1544 deletions
diff --git a/methods/connect.cc b/methods/connect.cc index fc7a72ee9..d9c9a1dd4 100644 --- a/methods/connect.cc +++ b/methods/connect.cc @@ -142,9 +142,9 @@ bool Connect(std::string Host,int Port,const char *Service,int DefPort,int &Fd, // Convert the port name/number char ServStr[300]; if (Port != 0) - snprintf(ServStr,sizeof(ServStr),"%u",Port); + snprintf(ServStr,sizeof(ServStr),"%i", Port); else - snprintf(ServStr,sizeof(ServStr),"%s",Service); + snprintf(ServStr,sizeof(ServStr),"%s", Service); /* We used a cached address record.. Yes this is against the spec but the way we have setup our rotating dns suggests that this is more @@ -190,7 +190,7 @@ bool Connect(std::string Host,int Port,const char *Service,int DefPort,int &Fd, { if (DefPort != 0) { - snprintf(ServStr,sizeof(ServStr),"%u",DefPort); + snprintf(ServStr, sizeof(ServStr), "%i", DefPort); DefPort = 0; continue; } diff --git a/methods/copy.cc b/methods/copy.cc index e81d0022b..f2a8f9ed8 100644 --- a/methods/copy.cc +++ b/methods/copy.cc @@ -18,7 +18,7 @@ #include <apt-pkg/hashes.h> #include <sys/stat.h> -#include <utime.h> +#include <sys/time.h> #include <unistd.h> #include <apti18n.h> /*}}}*/ @@ -72,17 +72,15 @@ bool CopyMethod::Fetch(FetchItem *Itm) From.Close(); To.Close(); - + // Transfer the modification times - struct utimbuf TimeBuf; - TimeBuf.actime = Buf.st_atime; - TimeBuf.modtime = Buf.st_mtime; - if (utime(Itm->DestFile.c_str(),&TimeBuf) != 0) - { - To.OpFail(); - return _error->Errno("utime",_("Failed to set modification time")); - } - + struct timeval times[2]; + times[0].tv_sec = Buf.st_atime; + times[1].tv_sec = Buf.st_mtime; + times[0].tv_usec = times[1].tv_usec = 0; + if (utimes(Res.Filename.c_str(), times) != 0) + return _error->Errno("utimes",_("Failed to set modification time")); + Hashes Hash; FileFd Fd(Res.Filename, FileFd::ReadOnly); Hash.AddFD(Fd); diff --git a/methods/file.cc b/methods/file.cc index 7ed4e6f60..3d0687c5b 100644 --- a/methods/file.cc +++ b/methods/file.cc @@ -5,7 +5,7 @@ File URI method for APT - This simply checks that the file specified exists, if so the relevent + This simply checks that the file specified exists, if so the relevant information is returned. If a .gz filename is specified then the file name with .gz removed will also be checked and information about it will be returned in Alt-* diff --git a/methods/ftp.cc b/methods/ftp.cc index 979adca62..621f48476 100644 --- a/methods/ftp.cc +++ b/methods/ftp.cc @@ -3,7 +3,7 @@ // $Id: ftp.cc,v 1.31.2.1 2004/01/16 18:58:50 mdz Exp $ /* ###################################################################### - FTP Aquire Method - This is the FTP aquire method for APT. + FTP Acquire Method - This is the FTP acquire method for APT. This is a very simple implementation that does not try to optimize at all. Commands are sent syncronously with the FTP server (as the @@ -26,7 +26,6 @@ #include <sys/stat.h> #include <sys/time.h> -#include <utime.h> #include <unistd.h> #include <signal.h> #include <stdio.h> @@ -947,20 +946,22 @@ FtpMethod::FtpMethod() : pkgAcqMethod("1.0",SendConfig) /*}}}*/ // FtpMethod::SigTerm - Handle a fatal signal /*{{{*/ // --------------------------------------------------------------------- -/* This closes and timestamps the open file. This is neccessary to get +/* This closes and timestamps the open file. This is necessary to get resume behavoir on user abort */ void FtpMethod::SigTerm(int) { if (FailFd == -1) _exit(100); - close(FailFd); - + // Timestamp - struct utimbuf UBuf; - UBuf.actime = FailTime; - UBuf.modtime = FailTime; - utime(FailFile.c_str(),&UBuf); - + struct timeval times[2]; + times[0].tv_sec = FailTime; + times[1].tv_sec = FailTime; + times[0].tv_usec = times[1].tv_usec = 0; + utimes(FailFile.c_str(), times); + + close(FailFd); + _exit(100); } /*}}}*/ @@ -1059,13 +1060,14 @@ bool FtpMethod::Fetch(FetchItem *Itm) if (Server->Get(File,Fd,Res.ResumePoint,Hash,Missing) == false) { Fd.Close(); - + // Timestamp - struct utimbuf UBuf; - UBuf.actime = FailTime; - UBuf.modtime = FailTime; - utime(FailFile.c_str(),&UBuf); - + struct timeval times[2]; + times[0].tv_sec = FailTime; + times[1].tv_sec = FailTime; + times[0].tv_usec = times[1].tv_usec = 0; + utimes(FailFile.c_str(), times); + // If the file is missing we hard fail and delete the destfile // otherwise transient fail if (Missing == true) { @@ -1077,20 +1079,21 @@ bool FtpMethod::Fetch(FetchItem *Itm) } Res.Size = Fd.Size(); + + // Timestamp + struct timeval times[2]; + times[0].tv_sec = FailTime; + times[1].tv_sec = FailTime; + times[0].tv_usec = times[1].tv_usec = 0; + utimes(Fd.Name().c_str(), times); + FailFd = -1; } - + Res.LastModified = FailTime; Res.TakeHashes(Hash); - - // Timestamp - struct utimbuf UBuf; - UBuf.actime = FailTime; - UBuf.modtime = FailTime; - utime(Queue->DestFile.c_str(),&UBuf); - FailFd = -1; URIDone(Res); - + return true; } /*}}}*/ diff --git a/methods/ftp.h b/methods/ftp.h index 2634f0732..8055c389f 100644 --- a/methods/ftp.h +++ b/methods/ftp.h @@ -3,7 +3,7 @@ // $Id: ftp.h,v 1.4 2001/03/06 07:15:29 jgg Exp $ /* ###################################################################### - FTP Aquire Method - This is the FTP aquire method for APT. + FTP Acquire Method - This is the FTP acquire method for APT. ##################################################################### */ /*}}}*/ diff --git a/methods/gpgv.cc b/methods/gpgv.cc index ea8a26fd4..25bf64ddd 100644 --- a/methods/gpgv.cc +++ b/methods/gpgv.cc @@ -8,7 +8,6 @@ #include <apt-pkg/configuration.h> #include <apt-pkg/gpgv.h> -#include <utime.h> #include <stdio.h> #include <fcntl.h> #include <errno.h> diff --git a/methods/gzip.cc b/methods/gzip.cc index 48c8e9892..a2844e969 100644 --- a/methods/gzip.cc +++ b/methods/gzip.cc @@ -18,8 +18,8 @@ #include <apt-pkg/hashes.h> #include <sys/stat.h> +#include <sys/time.h> #include <unistd.h> -#include <utime.h> #include <stdio.h> #include <errno.h> #include <apti18n.h> @@ -94,32 +94,28 @@ bool GzipMethod::Fetch(FetchItem *Itm) } From.Close(); + Res.Size = To.FileSize(); To.Close(); - + if (Failed == true) return false; - + // Transfer the modification times struct stat Buf; if (stat(Path.c_str(),&Buf) != 0) return _error->Errno("stat",_("Failed to stat")); - struct utimbuf TimeBuf; - TimeBuf.actime = Buf.st_atime; - TimeBuf.modtime = Buf.st_mtime; - if (utime(Itm->DestFile.c_str(),&TimeBuf) != 0) - return _error->Errno("utime",_("Failed to set modification time")); + struct timeval times[2]; + times[0].tv_sec = Buf.st_atime; + Res.LastModified = times[1].tv_sec = Buf.st_mtime; + times[0].tv_usec = times[1].tv_usec = 0; + if (utimes(Itm->DestFile.c_str(), times) != 0) + return _error->Errno("utimes",_("Failed to set modification time")); - if (stat(Itm->DestFile.c_str(),&Buf) != 0) - return _error->Errno("stat",_("Failed to stat")); - // Return a Done response - Res.LastModified = Buf.st_mtime; - Res.Size = Buf.st_size; Res.TakeHashes(Hash); URIDone(Res); - return true; } /*}}}*/ diff --git a/methods/http.cc b/methods/http.cc index 278ddb290..42b31beeb 100644 --- a/methods/http.cc +++ b/methods/http.cc @@ -3,7 +3,7 @@ // $Id: http.cc,v 1.59 2004/05/08 19:42:35 mdz Exp $ /* ###################################################################### - HTTP Acquire Method - This is the HTTP aquire method for APT. + HTTP Acquire Method - This is the HTTP acquire method for APT. It uses HTTP/1.1 and many of the fancy options there-in, such as pipelining, range, if-range and so on. @@ -36,7 +36,6 @@ #include <sys/stat.h> #include <sys/time.h> -#include <utime.h> #include <unistd.h> #include <signal.h> #include <stdio.h> @@ -58,15 +57,6 @@ /*}}}*/ using namespace std; -string HttpMethod::FailFile; -int HttpMethod::FailFd = -1; -time_t HttpMethod::FailTime = 0; -unsigned long PipelineDepth = 0; -unsigned long TimeOut = 120; -bool AllowRedirect = false; -bool Debug = false; -URI Proxy; - unsigned long long CircleBuf::BwReadLimit=0; unsigned long long CircleBuf::BwTickReadData=0; struct timeval CircleBuf::BwReadTick={0,0}; @@ -106,8 +96,6 @@ void CircleBuf::Reset() is non-blocking.. */ bool CircleBuf::Read(int Fd) { - unsigned long long BwReadMax; - while (1) { // Woops, buffer is full @@ -115,7 +103,7 @@ bool CircleBuf::Read(int Fd) return true; // what's left to read in this tick - BwReadMax = CircleBuf::BwReadLimit/BW_HZ; + unsigned long long const BwReadMax = CircleBuf::BwReadLimit/BW_HZ; if(CircleBuf::BwReadLimit) { struct timeval now; @@ -296,20 +284,17 @@ CircleBuf::~CircleBuf() delete Hash; } -// ServerState::ServerState - Constructor /*{{{*/ -// --------------------------------------------------------------------- -/* */ -ServerState::ServerState(URI Srv,HttpMethod *Owner) : Owner(Owner), - In(64*1024), Out(4*1024), - ServerName(Srv) +// HttpServerState::HttpServerState - Constructor /*{{{*/ +HttpServerState::HttpServerState(URI Srv,HttpMethod *Owner) : ServerState(Srv, Owner), In(64*1024), Out(4*1024) { + TimeOut = _config->FindI("Acquire::http::Timeout",TimeOut); Reset(); } /*}}}*/ -// ServerState::Open - Open a connection to the server /*{{{*/ +// HttpServerState::Open - Open a connection to the server /*{{{*/ // --------------------------------------------------------------------- /* This opens a connection to the server. */ -bool ServerState::Open() +bool HttpServerState::Open() { // Use the already open connection if possible. if (ServerFd != -1) @@ -373,72 +358,18 @@ bool ServerState::Open() return true; } /*}}}*/ -// ServerState::Close - Close a connection to the server /*{{{*/ +// HttpServerState::Close - Close a connection to the server /*{{{*/ // --------------------------------------------------------------------- /* */ -bool ServerState::Close() +bool HttpServerState::Close() { close(ServerFd); ServerFd = -1; return true; } /*}}}*/ -// ServerState::RunHeaders - Get the headers before the data /*{{{*/ -// --------------------------------------------------------------------- -/* Returns 0 if things are OK, 1 if an IO error occurred and 2 if a header - parse error occurred */ -ServerState::RunHeadersResult ServerState::RunHeaders() -{ - State = Header; - - Owner->Status(_("Waiting for headers")); - - Major = 0; - Minor = 0; - Result = 0; - Size = 0; - StartPos = 0; - Encoding = Closes; - HaveContent = false; - time(&Date); - - do - { - string Data; - if (In.WriteTillEl(Data) == false) - continue; - - if (Debug == true) - clog << Data; - - for (string::const_iterator I = Data.begin(); I < Data.end(); ++I) - { - string::const_iterator J = I; - for (; J != Data.end() && *J != '\n' && *J != '\r'; ++J); - if (HeaderLine(string(I,J)) == false) - return RUN_HEADERS_PARSE_ERROR; - I = J; - } - - // 100 Continue is a Nop... - if (Result == 100) - continue; - - // Tidy up the connection persistance state. - if (Encoding == Closes && HaveContent == true) - Persistent = false; - - return RUN_HEADERS_OK; - } - while (Owner->Go(false,this) == true); - - return RUN_HEADERS_IO_ERROR; -} - /*}}}*/ -// ServerState::RunData - Transfer the data from the socket /*{{{*/ -// --------------------------------------------------------------------- -/* */ -bool ServerState::RunData() +// HttpServerState::RunData - Transfer the data from the socket /*{{{*/ +bool HttpServerState::RunData(FileFd * const File) { State = Data; @@ -456,7 +387,7 @@ bool ServerState::RunData() if (In.WriteTillEl(Data,true) == true) break; } - while ((Last = Owner->Go(false,this)) == true); + while ((Last = Go(false, File)) == true); if (Last == false) return false; @@ -474,7 +405,7 @@ bool ServerState::RunData() if (In.WriteTillEl(Data,true) == true && Data.length() <= 2) break; } - while ((Last = Owner->Go(false,this)) == true); + while ((Last = Go(false, File)) == true); if (Last == false) return false; return !_error->PendingError(); @@ -482,7 +413,7 @@ bool ServerState::RunData() // Transfer the block In.Limit(Len); - while (Owner->Go(true,this) == true) + while (Go(true, File) == true) if (In.IsLimit() == true) break; @@ -498,7 +429,7 @@ bool ServerState::RunData() if (In.WriteTillEl(Data,true) == true) break; } - while ((Last = Owner->Go(false,this)) == true); + while ((Last = Go(false, File)) == true); if (Last == false) return false; } @@ -521,139 +452,217 @@ bool ServerState::RunData() In.Limit(-1); return !_error->PendingError(); } - while (Owner->Go(true,this) == true); + while (Go(true, File) == true); } - return Owner->Flush(this) && !_error->PendingError(); + return Owner->Flush() && !_error->PendingError(); } /*}}}*/ -// ServerState::HeaderLine - Process a header line /*{{{*/ -// --------------------------------------------------------------------- -/* */ -bool ServerState::HeaderLine(string Line) +bool HttpServerState::ReadHeaderLines(std::string &Data) /*{{{*/ { - if (Line.empty() == true) - return true; + return In.WriteTillEl(Data); +} + /*}}}*/ +bool HttpServerState::LoadNextResponse(bool const ToFile, FileFd * const File)/*{{{*/ +{ + return Go(ToFile, File); +} + /*}}}*/ +bool HttpServerState::WriteResponse(const std::string &Data) /*{{{*/ +{ + return Out.Read(Data); +} + /*}}}*/ +bool HttpServerState::IsOpen() /*{{{*/ +{ + return (ServerFd != -1); +} + /*}}}*/ +bool HttpServerState::InitHashes(FileFd &File) /*{{{*/ +{ + delete In.Hash; + In.Hash = new Hashes; - string::size_type Pos = Line.find(' '); - if (Pos == string::npos || Pos+1 > Line.length()) + // Set the expected size and read file for the hashes + if (StartPos >= 0) { - // Blah, some servers use "connection:closes", evil. - Pos = Line.find(':'); - if (Pos == string::npos || Pos + 2 > Line.length()) - return _error->Error(_("Bad header line")); - Pos++; + File.Truncate(StartPos); + + return In.Hash->AddFD(File, StartPos); } + return true; +} + /*}}}*/ +Hashes * HttpServerState::GetHashes() /*{{{*/ +{ + return In.Hash; +} + /*}}}*/ +// HttpServerState::Die - The server has closed the connection. /*{{{*/ +bool HttpServerState::Die(FileFd &File) +{ + unsigned int LErrno = errno; - // Parse off any trailing spaces between the : and the next word. - string::size_type Pos2 = Pos; - while (Pos2 < Line.length() && isspace(Line[Pos2]) != 0) - Pos2++; - - string Tag = string(Line,0,Pos); - string Val = string(Line,Pos2); - - if (stringcasecmp(Tag.c_str(),Tag.c_str()+4,"HTTP") == 0) + // Dump the buffer to the file + if (State == ServerState::Data) { - // Evil servers return no version - if (Line[4] == '/') - { - int const elements = sscanf(Line.c_str(),"HTTP/%3u.%3u %3u%359[^\n]",&Major,&Minor,&Result,Code); - if (elements == 3) - { - Code[0] = '\0'; - if (Debug == true) - clog << "HTTP server doesn't give Reason-Phrase for " << Result << std::endl; - } - else if (elements != 4) - return _error->Error(_("The HTTP server sent an invalid reply header")); - } - else + // on GNU/kFreeBSD, apt dies on /dev/null because non-blocking + // can't be set + if (File.Name() != "/dev/null") + SetNonBlock(File.Fd(),false); + while (In.WriteSpace() == true) { - Major = 0; - Minor = 9; - if (sscanf(Line.c_str(),"HTTP %3u%359[^\n]",&Result,Code) != 2) - return _error->Error(_("The HTTP server sent an invalid reply header")); - } + if (In.Write(File.Fd()) == false) + return _error->Errno("write",_("Error writing to the file")); - /* Check the HTTP response header to get the default persistance - state. */ - if (Major < 1) - Persistent = false; - else - { - if (Major == 1 && Minor == 0) - Persistent = false; - else - Persistent = true; + // Done + if (In.IsLimit() == true) + return true; } + } - return true; - } - - if (stringcasecmp(Tag,"Content-Length:") == 0) + // See if this is because the server finished the data stream + if (In.IsLimit() == false && State != HttpServerState::Header && + Encoding != HttpServerState::Closes) { - if (Encoding == Closes) - Encoding = Stream; - HaveContent = true; - - // The length is already set from the Content-Range header - if (StartPos != 0) - return true; + Close(); + if (LErrno == 0) + return _error->Error(_("Error reading from server. Remote end closed connection")); + errno = LErrno; + return _error->Errno("read",_("Error reading from server")); + } + else + { + In.Limit(-1); - Size = strtoull(Val.c_str(), NULL, 10); - if (Size >= std::numeric_limits<unsigned long long>::max()) - return _error->Errno("HeaderLine", _("The HTTP server sent an invalid Content-Length header")); + // Nothing left in the buffer + if (In.WriteSpace() == false) + return false; + + // We may have got multiple responses back in one packet.. + Close(); return true; } - if (stringcasecmp(Tag,"Content-Type:") == 0) + return false; +} + /*}}}*/ +// HttpServerState::Flush - Dump the buffer into the file /*{{{*/ +// --------------------------------------------------------------------- +/* This takes the current input buffer from the Server FD and writes it + into the file */ +bool HttpServerState::Flush(FileFd * const File) +{ + if (File != NULL) { - HaveContent = true; - return true; + // on GNU/kFreeBSD, apt dies on /dev/null because non-blocking + // can't be set + if (File->Name() != "/dev/null") + SetNonBlock(File->Fd(),false); + if (In.WriteSpace() == false) + return true; + + while (In.WriteSpace() == true) + { + if (In.Write(File->Fd()) == false) + return _error->Errno("write",_("Error writing to file")); + if (In.IsLimit() == true) + return true; + } + + if (In.IsLimit() == true || Encoding == ServerState::Closes) + return true; } + return false; +} + /*}}}*/ +// HttpServerState::Go - Run a single loop /*{{{*/ +// --------------------------------------------------------------------- +/* This runs the select loop over the server FDs, Output file FDs and + stdin. */ +bool HttpServerState::Go(bool ToFile, FileFd * const File) +{ + // Server has closed the connection + if (ServerFd == -1 && (In.WriteSpace() == false || + ToFile == false)) + return false; + + fd_set rfds,wfds; + FD_ZERO(&rfds); + FD_ZERO(&wfds); + + /* Add the server. We only send more requests if the connection will + be persisting */ + if (Out.WriteSpace() == true && ServerFd != -1 + && Persistent == true) + FD_SET(ServerFd,&wfds); + if (In.ReadSpace() == true && ServerFd != -1) + FD_SET(ServerFd,&rfds); + + // Add the file + int FileFD = -1; + if (File != NULL) + FileFD = File->Fd(); - if (stringcasecmp(Tag,"Content-Range:") == 0) + if (In.WriteSpace() == true && ToFile == true && FileFD != -1) + FD_SET(FileFD,&wfds); + + // Add stdin + if (_config->FindB("Acquire::http::DependOnSTDIN", true) == true) + FD_SET(STDIN_FILENO,&rfds); + + // Figure out the max fd + int MaxFd = FileFD; + if (MaxFd < ServerFd) + MaxFd = ServerFd; + + // Select + struct timeval tv; + tv.tv_sec = TimeOut; + tv.tv_usec = 0; + int Res = 0; + if ((Res = select(MaxFd+1,&rfds,&wfds,0,&tv)) < 0) { - HaveContent = true; - - if (sscanf(Val.c_str(),"bytes %llu-%*u/%llu",&StartPos,&Size) != 2) - return _error->Error(_("The HTTP server sent an invalid Content-Range header")); - if ((unsigned long long)StartPos > Size) - return _error->Error(_("This HTTP server has broken range support")); - return true; + if (errno == EINTR) + return true; + return _error->Errno("select",_("Select failed")); } - if (stringcasecmp(Tag,"Transfer-Encoding:") == 0) + if (Res == 0) { - HaveContent = true; - if (stringcasecmp(Val,"chunked") == 0) - Encoding = Chunked; - return true; + _error->Error(_("Connection timed out")); + return Die(*File); } - - if (stringcasecmp(Tag,"Connection:") == 0) + + // Handle server IO + if (ServerFd != -1 && FD_ISSET(ServerFd,&rfds)) { - if (stringcasecmp(Val,"close") == 0) - Persistent = false; - if (stringcasecmp(Val,"keep-alive") == 0) - Persistent = true; - return true; + errno = 0; + if (In.Read(ServerFd) == false) + return Die(*File); } - - if (stringcasecmp(Tag,"Last-Modified:") == 0) + + if (ServerFd != -1 && FD_ISSET(ServerFd,&wfds)) { - if (RFC1123StrToTime(Val.c_str(), Date) == false) - return _error->Error(_("Unknown date format")); - return true; + errno = 0; + if (Out.Write(ServerFd) == false) + return Die(*File); } - if (stringcasecmp(Tag,"Location:") == 0) + // Send data to the file + if (FileFD != -1 && FD_ISSET(FileFD,&wfds)) { - Location = Val; - return true; + if (In.Write(FileFD) == false) + return _error->Errno("write",_("Error writing to output file")); } + // Handle commands from APT + if (FD_ISSET(STDIN_FILENO,&rfds)) + { + if (Owner->Run(true) != -1) + exit(100); + } + return true; } /*}}}*/ @@ -661,7 +670,7 @@ bool ServerState::HeaderLine(string Line) // HttpMethod::SendReq - Send the HTTP request /*{{{*/ // --------------------------------------------------------------------- /* This places the http request in the outbound buffer */ -void HttpMethod::SendReq(FetchItem *Itm,CircleBuf &Out) +void HttpMethod::SendReq(FetchItem *Itm) { URI Uri = Itm->Uri; @@ -687,7 +696,7 @@ void HttpMethod::SendReq(FetchItem *Itm,CircleBuf &Out) but while its a must for all servers to accept absolute URIs, it is assumed clients will sent an absolute path for non-proxies */ std::string requesturi; - if (Proxy.empty() == true || Proxy.Host.empty()) + if (Server->Proxy.empty() == true || Server->Proxy.Host.empty()) requesturi = Uri.Path; else requesturi = Itm->Uri; @@ -723,7 +732,7 @@ void HttpMethod::SendReq(FetchItem *Itm,CircleBuf &Out) } // If we ask for uncompressed files servers might respond with content- - // negotation which lets us end up with compressed files we do not support, + // negotiation which lets us end up with compressed files we do not support, // see 657029, 657560 and co, so if we have no extension on the request // ask for text only. As a sidenote: If there is nothing to negotate servers // seem to be nice and ignore it. @@ -742,7 +751,7 @@ void HttpMethod::SendReq(FetchItem *Itm,CircleBuf &Out) if (stat(Itm->DestFile.c_str(),&SBuf) >= 0 && SBuf.st_size > 0) { // In this case we send an if-range query with a range header - sprintf(Buf,"Range: bytes=%lli-\r\nIf-Range: %s\r\n",(long long)SBuf.st_size - 1, + sprintf(Buf,"Range: bytes=%lli-\r\nIf-Range: %s\r\n",(long long)SBuf.st_size, TimeRFC1123(SBuf.st_mtime).c_str()); Req += Buf; } @@ -755,9 +764,9 @@ void HttpMethod::SendReq(FetchItem *Itm,CircleBuf &Out) } } - if (Proxy.User.empty() == false || Proxy.Password.empty() == false) + if (Server->Proxy.User.empty() == false || Server->Proxy.Password.empty() == false) Req += string("Proxy-Authorization: Basic ") + - Base64Encode(Proxy.User + ":" + Proxy.Password) + "\r\n"; + Base64Encode(Server->Proxy.User + ":" + Server->Proxy.Password) + "\r\n"; maybe_add_auth (Uri, _config->FindFile("Dir::Etc::netrc")); if (Uri.User.empty() == false || Uri.Password.empty() == false) @@ -771,344 +780,21 @@ void HttpMethod::SendReq(FetchItem *Itm,CircleBuf &Out) if (Debug == true) cerr << Req << endl; - Out.Read(Req); + Server->WriteResponse(Req); } /*}}}*/ -// HttpMethod::Go - Run a single loop /*{{{*/ -// --------------------------------------------------------------------- -/* This runs the select loop over the server FDs, Output file FDs and - stdin. */ -bool HttpMethod::Go(bool ToFile,ServerState *Srv) -{ - // Server has closed the connection - if (Srv->ServerFd == -1 && (Srv->In.WriteSpace() == false || - ToFile == false)) - return false; - - fd_set rfds,wfds; - FD_ZERO(&rfds); - FD_ZERO(&wfds); - - /* Add the server. We only send more requests if the connection will - be persisting */ - if (Srv->Out.WriteSpace() == true && Srv->ServerFd != -1 - && Srv->Persistent == true) - FD_SET(Srv->ServerFd,&wfds); - if (Srv->In.ReadSpace() == true && Srv->ServerFd != -1) - FD_SET(Srv->ServerFd,&rfds); - - // Add the file - int FileFD = -1; - if (File != 0) - FileFD = File->Fd(); - - if (Srv->In.WriteSpace() == true && ToFile == true && FileFD != -1) - FD_SET(FileFD,&wfds); - - // Add stdin - if (_config->FindB("Acquire::http::DependOnSTDIN", true) == true) - FD_SET(STDIN_FILENO,&rfds); - - // Figure out the max fd - int MaxFd = FileFD; - if (MaxFd < Srv->ServerFd) - MaxFd = Srv->ServerFd; - - // Select - struct timeval tv; - tv.tv_sec = TimeOut; - tv.tv_usec = 0; - int Res = 0; - if ((Res = select(MaxFd+1,&rfds,&wfds,0,&tv)) < 0) - { - if (errno == EINTR) - return true; - return _error->Errno("select",_("Select failed")); - } - - if (Res == 0) - { - _error->Error(_("Connection timed out")); - return ServerDie(Srv); - } - - // Handle server IO - if (Srv->ServerFd != -1 && FD_ISSET(Srv->ServerFd,&rfds)) - { - errno = 0; - if (Srv->In.Read(Srv->ServerFd) == false) - return ServerDie(Srv); - } - - if (Srv->ServerFd != -1 && FD_ISSET(Srv->ServerFd,&wfds)) - { - errno = 0; - if (Srv->Out.Write(Srv->ServerFd) == false) - return ServerDie(Srv); - } - - // Send data to the file - if (FileFD != -1 && FD_ISSET(FileFD,&wfds)) - { - if (Srv->In.Write(FileFD) == false) - return _error->Errno("write",_("Error writing to output file")); - } - - // Handle commands from APT - if (FD_ISSET(STDIN_FILENO,&rfds)) - { - if (Run(true) != -1) - exit(100); - } - - return true; -} - /*}}}*/ -// HttpMethod::Flush - Dump the buffer into the file /*{{{*/ -// --------------------------------------------------------------------- -/* This takes the current input buffer from the Server FD and writes it - into the file */ -bool HttpMethod::Flush(ServerState *Srv) -{ - if (File != 0) - { - // on GNU/kFreeBSD, apt dies on /dev/null because non-blocking - // can't be set - if (File->Name() != "/dev/null") - SetNonBlock(File->Fd(),false); - if (Srv->In.WriteSpace() == false) - return true; - - while (Srv->In.WriteSpace() == true) - { - if (Srv->In.Write(File->Fd()) == false) - return _error->Errno("write",_("Error writing to file")); - if (Srv->In.IsLimit() == true) - return true; - } - - if (Srv->In.IsLimit() == true || Srv->Encoding == ServerState::Closes) - return true; - } - return false; -} - /*}}}*/ -// HttpMethod::ServerDie - The server has closed the connection. /*{{{*/ -// --------------------------------------------------------------------- -/* */ -bool HttpMethod::ServerDie(ServerState *Srv) -{ - unsigned int LErrno = errno; - - // Dump the buffer to the file - if (Srv->State == ServerState::Data) - { - // on GNU/kFreeBSD, apt dies on /dev/null because non-blocking - // can't be set - if (File->Name() != "/dev/null") - SetNonBlock(File->Fd(),false); - while (Srv->In.WriteSpace() == true) - { - if (Srv->In.Write(File->Fd()) == false) - return _error->Errno("write",_("Error writing to the file")); - - // Done - if (Srv->In.IsLimit() == true) - return true; - } - } - - // See if this is because the server finished the data stream - if (Srv->In.IsLimit() == false && Srv->State != ServerState::Header && - Srv->Encoding != ServerState::Closes) - { - Srv->Close(); - if (LErrno == 0) - return _error->Error(_("Error reading from server. Remote end closed connection")); - errno = LErrno; - return _error->Errno("read",_("Error reading from server")); - } - else - { - Srv->In.Limit(-1); - - // Nothing left in the buffer - if (Srv->In.WriteSpace() == false) - return false; - - // We may have got multiple responses back in one packet.. - Srv->Close(); - return true; - } - - return false; -} - /*}}}*/ -// HttpMethod::DealWithHeaders - Handle the retrieved header data /*{{{*/ -// --------------------------------------------------------------------- -/* We look at the header data we got back from the server and decide what - to do. Returns DealWithHeadersResult (see http.h for details). - */ -HttpMethod::DealWithHeadersResult -HttpMethod::DealWithHeaders(FetchResult &Res,ServerState *Srv) -{ - // Not Modified - if (Srv->Result == 304) - { - unlink(Queue->DestFile.c_str()); - Res.IMSHit = true; - Res.LastModified = Queue->LastModified; - return IMS_HIT; - } - - /* Redirect - * - * Note that it is only OK for us to treat all redirection the same - * because we *always* use GET, not other HTTP methods. There are - * three redirection codes for which it is not appropriate that we - * redirect. Pass on those codes so the error handling kicks in. - */ - if (AllowRedirect - && (Srv->Result > 300 && Srv->Result < 400) - && (Srv->Result != 300 // Multiple Choices - && Srv->Result != 304 // Not Modified - && Srv->Result != 306)) // (Not part of HTTP/1.1, reserved) - { - if (Srv->Location.empty() == true); - else if (Srv->Location[0] == '/' && Queue->Uri.empty() == false) - { - URI Uri = Queue->Uri; - if (Uri.Host.empty() == false) - NextURI = URI::SiteOnly(Uri); - else - NextURI.clear(); - NextURI.append(DeQuoteString(Srv->Location)); - return TRY_AGAIN_OR_REDIRECT; - } - else - { - NextURI = DeQuoteString(Srv->Location); - URI tmpURI = NextURI; - // Do not allow a redirection to switch protocol - if (tmpURI.Access == "http") - return TRY_AGAIN_OR_REDIRECT; - } - /* else pass through for error message */ - } - - /* We have a reply we dont handle. This should indicate a perm server - failure */ - if (Srv->Result < 200 || Srv->Result >= 300) - { - char err[255]; - snprintf(err,sizeof(err)-1,"HttpError%i",Srv->Result); - SetFailReason(err); - _error->Error("%u %s",Srv->Result,Srv->Code); - if (Srv->HaveContent == true) - return ERROR_WITH_CONTENT_PAGE; - return ERROR_UNRECOVERABLE; - } - - // This is some sort of 2xx 'data follows' reply - Res.LastModified = Srv->Date; - Res.Size = Srv->Size; - - // Open the file - delete File; - File = new FileFd(Queue->DestFile,FileFd::WriteAny); - if (_error->PendingError() == true) - return ERROR_NOT_FROM_SERVER; - - FailFile = Queue->DestFile; - FailFile.c_str(); // Make sure we dont do a malloc in the signal handler - FailFd = File->Fd(); - FailTime = Srv->Date; - - delete Srv->In.Hash; - Srv->In.Hash = new Hashes; - - // Set the expected size and read file for the hashes - if (Srv->StartPos >= 0) - { - Res.ResumePoint = Srv->StartPos; - File->Truncate(Srv->StartPos); - - if (Srv->In.Hash->AddFD(*File,Srv->StartPos) == false) - { - _error->Errno("read",_("Problem hashing file")); - return ERROR_NOT_FROM_SERVER; - } - } - - SetNonBlock(File->Fd(),true); - return FILE_IS_OPEN; -} - /*}}}*/ -// HttpMethod::SigTerm - Handle a fatal signal /*{{{*/ -// --------------------------------------------------------------------- -/* This closes and timestamps the open file. This is neccessary to get - resume behavoir on user abort */ -void HttpMethod::SigTerm(int) -{ - if (FailFd == -1) - _exit(100); - close(FailFd); - - // Timestamp - struct utimbuf UBuf; - UBuf.actime = FailTime; - UBuf.modtime = FailTime; - utime(FailFile.c_str(),&UBuf); - - _exit(100); -} - /*}}}*/ -// HttpMethod::Fetch - Fetch an item /*{{{*/ -// --------------------------------------------------------------------- -/* This adds an item to the pipeline. We keep the pipeline at a fixed - depth. */ -bool HttpMethod::Fetch(FetchItem *) -{ - if (Server == 0) - return true; - - // Queue the requests - int Depth = -1; - for (FetchItem *I = Queue; I != 0 && Depth < (signed)PipelineDepth; - I = I->Next, Depth++) - { - // If pipelining is disabled, we only queue 1 request - if (Server->Pipeline == false && Depth >= 0) - break; - - // Make sure we stick with the same server - if (Server->Comp(I->Uri) == false) - break; - if (QueueBack == I) - { - QueueBack = I->Next; - SendReq(I,Server->Out); - continue; - } - } - - return true; -}; - /*}}}*/ // HttpMethod::Configuration - Handle a configuration message /*{{{*/ // --------------------------------------------------------------------- /* We stash the desired pipeline depth */ bool HttpMethod::Configuration(string Message) { - if (pkgAcqMethod::Configuration(Message) == false) + if (ServerMethod::Configuration(Message) == false) return false; - + AllowRedirect = _config->FindB("Acquire::http::AllowRedirect",true); - TimeOut = _config->FindI("Acquire::http::Timeout",TimeOut); PipelineDepth = _config->FindI("Acquire::http::Pipeline-Depth", PipelineDepth); Debug = _config->FindB("Debug::Acquire::http",false); - AutoDetectProxyCmd = _config->Find("Acquire::http::ProxyAutoDetect"); // Get the proxy to use AutoDetectProxy(); @@ -1116,263 +802,16 @@ bool HttpMethod::Configuration(string Message) return true; } /*}}}*/ -// HttpMethod::Loop - Main loop /*{{{*/ -// --------------------------------------------------------------------- -/* */ -int HttpMethod::Loop() -{ - typedef vector<string> StringVector; - typedef vector<string>::iterator StringVectorIterator; - map<string, StringVector> Redirected; - - signal(SIGTERM,SigTerm); - signal(SIGINT,SigTerm); - - Server = 0; - - int FailCounter = 0; - while (1) - { - // We have no commands, wait for some to arrive - if (Queue == 0) - { - if (WaitFd(STDIN_FILENO) == false) - return 0; - } - - /* Run messages, we can accept 0 (no message) if we didn't - do a WaitFd above.. Otherwise the FD is closed. */ - int Result = Run(true); - if (Result != -1 && (Result != 0 || Queue == 0)) - { - if(FailReason.empty() == false || - _config->FindB("Acquire::http::DependOnSTDIN", true) == true) - return 100; - else - return 0; - } - - if (Queue == 0) - continue; - - // Connect to the server - if (Server == 0 || Server->Comp(Queue->Uri) == false) - { - delete Server; - Server = new ServerState(Queue->Uri,this); - } - /* If the server has explicitly said this is the last connection - then we pre-emptively shut down the pipeline and tear down - the connection. This will speed up HTTP/1.0 servers a tad - since we don't have to wait for the close sequence to - complete */ - if (Server->Persistent == false) - Server->Close(); - - // Reset the pipeline - if (Server->ServerFd == -1) - QueueBack = Queue; - - // Connnect to the host - if (Server->Open() == false) - { - Fail(true); - delete Server; - Server = 0; - continue; - } - - // Fill the pipeline. - Fetch(0); - - // Fetch the next URL header data from the server. - switch (Server->RunHeaders()) - { - case ServerState::RUN_HEADERS_OK: - break; - - // The header data is bad - case ServerState::RUN_HEADERS_PARSE_ERROR: - { - _error->Error(_("Bad header data")); - Fail(true); - RotateDNS(); - continue; - } - - // The server closed a connection during the header get.. - default: - case ServerState::RUN_HEADERS_IO_ERROR: - { - FailCounter++; - _error->Discard(); - Server->Close(); - Server->Pipeline = false; - - if (FailCounter >= 2) - { - Fail(_("Connection failed"),true); - FailCounter = 0; - } - - RotateDNS(); - continue; - } - }; - - // Decide what to do. - FetchResult Res; - Res.Filename = Queue->DestFile; - switch (DealWithHeaders(Res,Server)) - { - // Ok, the file is Open - case FILE_IS_OPEN: - { - URIStart(Res); - - // Run the data - bool Result = Server->RunData(); - - /* If the server is sending back sizeless responses then fill in - the size now */ - if (Res.Size == 0) - Res.Size = File->Size(); - - // Close the file, destroy the FD object and timestamp it - FailFd = -1; - delete File; - File = 0; - - // Timestamp - struct utimbuf UBuf; - time(&UBuf.actime); - UBuf.actime = Server->Date; - UBuf.modtime = Server->Date; - utime(Queue->DestFile.c_str(),&UBuf); - - // Send status to APT - if (Result == true) - { - Res.TakeHashes(*Server->In.Hash); - URIDone(Res); - } - else - { - if (Server->ServerFd == -1) - { - FailCounter++; - _error->Discard(); - Server->Close(); - - if (FailCounter >= 2) - { - Fail(_("Connection failed"),true); - FailCounter = 0; - } - - QueueBack = Queue; - } - else - Fail(true); - } - break; - } - - // IMS hit - case IMS_HIT: - { - URIDone(Res); - break; - } - - // Hard server error, not found or something - case ERROR_UNRECOVERABLE: - { - Fail(); - break; - } - - // Hard internal error, kill the connection and fail - case ERROR_NOT_FROM_SERVER: - { - delete File; - File = 0; - - Fail(); - RotateDNS(); - Server->Close(); - break; - } - - // We need to flush the data, the header is like a 404 w/ error text - case ERROR_WITH_CONTENT_PAGE: - { - Fail(); - - // Send to content to dev/null - File = new FileFd("/dev/null",FileFd::WriteExists); - Server->RunData(); - delete File; - File = 0; - break; - } - - // Try again with a new URL - case TRY_AGAIN_OR_REDIRECT: - { - // Clear rest of response if there is content - if (Server->HaveContent) - { - File = new FileFd("/dev/null",FileFd::WriteExists); - Server->RunData(); - delete File; - File = 0; - } - - /* Detect redirect loops. No more redirects are allowed - after the same URI is seen twice in a queue item. */ - StringVector &R = Redirected[Queue->DestFile]; - bool StopRedirects = false; - if (R.empty() == true) - R.push_back(Queue->Uri); - else if (R[0] == "STOP" || R.size() > 10) - StopRedirects = true; - else - { - for (StringVectorIterator I = R.begin(); I != R.end(); ++I) - if (Queue->Uri == *I) - { - R[0] = "STOP"; - break; - } - - R.push_back(Queue->Uri); - } - - if (StopRedirects == false) - Redirect(NextURI); - else - Fail(); - - break; - } - - default: - Fail(_("Internal error")); - break; - } - - FailCounter = 0; - } - - return 0; -} - /*}}}*/ // HttpMethod::AutoDetectProxy - auto detect proxy /*{{{*/ // --------------------------------------------------------------------- /* */ bool HttpMethod::AutoDetectProxy() { + // option is "Acquire::http::Proxy-Auto-Detect" but we allow the old + // name without the dash ("-") + AutoDetectProxyCmd = _config->Find("Acquire::http::Proxy-Auto-Detect", + _config->Find("Acquire::http::ProxyAutoDetect")); + if (AutoDetectProxyCmd.empty()) return true; @@ -1420,5 +859,13 @@ bool HttpMethod::AutoDetectProxy() return true; } /*}}}*/ - - +ServerState * HttpMethod::CreateServerState(URI uri) /*{{{*/ +{ + return new HttpServerState(uri, this); +} + /*}}}*/ +void HttpMethod::RotateDNS() /*{{{*/ +{ + ::RotateDNS(); +} + /*}}}*/ diff --git a/methods/http.h b/methods/http.h index 7446119cd..450a42eed 100644 --- a/methods/http.h +++ b/methods/http.h @@ -3,7 +3,7 @@ // $Id: http.h,v 1.12 2002/04/18 05:09:38 jgg Exp $ /* ###################################################################### - HTTP Acquire Method - This is the HTTP aquire method for APT. + HTTP Acquire Method - This is the HTTP acquire method for APT. ##################################################################### */ /*}}}*/ @@ -15,6 +15,8 @@ #include <string> +#include "server.h" + using std::cout; using std::endl; @@ -31,7 +33,7 @@ class CircleBuf unsigned long long StrPos; unsigned long long MaxGet; struct timeval Start; - + static unsigned long long BwReadLimit; static unsigned long long BwTickReadData; static struct timeval BwReadTick; @@ -54,21 +56,20 @@ class CircleBuf return Sz; } void FillOut(); - + public: - Hashes *Hash; - + // Read data in bool Read(int Fd); bool Read(std::string Data); - + // Write data out bool Write(int Fd); bool WriteTillEl(std::string &Data,bool Single = false); - + // Control the write limit - void Limit(long long Max) {if (Max == -1) MaxGet = 0-1; else MaxGet = OutP + Max;} + void Limit(long long Max) {if (Max == -1) MaxGet = 0-1; else MaxGet = OutP + Max;} bool IsLimit() const {return MaxGet == OutP;}; void Print() const {cout << MaxGet << ',' << OutP << endl;}; @@ -84,114 +85,56 @@ class CircleBuf ~CircleBuf(); }; -struct ServerState +struct HttpServerState: public ServerState { - // This is the last parsed Header Line - unsigned int Major; - unsigned int Minor; - unsigned int Result; - char Code[360]; - - // These are some statistics from the last parsed header lines - unsigned long long Size; - signed long long StartPos; - time_t Date; - bool HaveContent; - enum {Chunked,Stream,Closes} Encoding; - enum {Header, Data} State; - bool Persistent; - std::string Location; - - // This is a Persistent attribute of the server itself. - bool Pipeline; - - HttpMethod *Owner; - // This is the connection itself. Output is data FROM the server CircleBuf In; CircleBuf Out; int ServerFd; - URI ServerName; - - bool HeaderLine(std::string Line); - bool Comp(URI Other) const {return Other.Host == ServerName.Host && Other.Port == ServerName.Port;}; - void Reset() {Major = 0; Minor = 0; Result = 0; Code[0] = '\0'; Size = 0; - StartPos = 0; Encoding = Closes; time(&Date); HaveContent = false; - State = Header; Persistent = false; ServerFd = -1; - Pipeline = true;}; - - /** \brief Result of the header acquire */ - enum RunHeadersResult { - /** \brief Header ok */ - RUN_HEADERS_OK, - /** \brief IO error while retrieving */ - RUN_HEADERS_IO_ERROR, - /** \brief Parse error after retrieving */ - RUN_HEADERS_PARSE_ERROR, - }; - /** \brief Get the headers before the data */ - RunHeadersResult RunHeaders(); - /** \brief Transfer the data from the socket */ - bool RunData(); - - bool Open(); - bool Close(); - - ServerState(URI Srv,HttpMethod *Owner); - ~ServerState() {Close();}; + + protected: + virtual bool ReadHeaderLines(std::string &Data); + virtual bool LoadNextResponse(bool const ToFile, FileFd * const File); + virtual bool WriteResponse(std::string const &Data); + + public: + virtual void Reset() { ServerState::Reset(); ServerFd = -1; }; + + virtual bool RunData(FileFd * const File); + + virtual bool Open(); + virtual bool IsOpen(); + virtual bool Close(); + virtual bool InitHashes(FileFd &File); + virtual Hashes * GetHashes(); + virtual bool Die(FileFd &File); + virtual bool Flush(FileFd * const File); + virtual bool Go(bool ToFile, FileFd * const File); + + HttpServerState(URI Srv, HttpMethod *Owner); + virtual ~HttpServerState() {Close();}; }; -class HttpMethod : public pkgAcqMethod +class HttpMethod : public ServerMethod { - void SendReq(FetchItem *Itm,CircleBuf &Out); - bool Go(bool ToFile,ServerState *Srv); - bool Flush(ServerState *Srv); - bool ServerDie(ServerState *Srv); - - /** \brief Result of the header parsing */ - enum DealWithHeadersResult { - /** \brief The file is open and ready */ - FILE_IS_OPEN, - /** \brief We got a IMS hit, the file has not changed */ - IMS_HIT, - /** \brief The server reported a unrecoverable error */ - ERROR_UNRECOVERABLE, - /** \brief The server reported a error with a error content page */ - ERROR_WITH_CONTENT_PAGE, - /** \brief An error on the client side */ - ERROR_NOT_FROM_SERVER, - /** \brief A redirect or retry request */ - TRY_AGAIN_OR_REDIRECT - }; - /** \brief Handle the retrieved header data */ - DealWithHeadersResult DealWithHeaders(FetchResult &Res,ServerState *Srv); + public: + virtual void SendReq(FetchItem *Itm); /** \brief Try to AutoDetect the proxy */ bool AutoDetectProxy(); virtual bool Configuration(std::string Message); - - // In the event of a fatal signal this file will be closed and timestamped. - static std::string FailFile; - static int FailFd; - static time_t FailTime; - static void SigTerm(int); + + virtual ServerState * CreateServerState(URI uri); + virtual void RotateDNS(); protected: - virtual bool Fetch(FetchItem *); - - std::string NextURI; std::string AutoDetectProxyCmd; public: - friend struct ServerState; - - FileFd *File; - ServerState *Server; - - int Loop(); - - HttpMethod() : pkgAcqMethod("1.2",Pipeline | SendConfig) + friend struct HttpServerState; + + HttpMethod() : ServerMethod("1.2",Pipeline | SendConfig) { File = 0; Server = 0; diff --git a/methods/https.cc b/methods/https.cc index 84ce2d68f..febe6a0f0 100644 --- a/methods/https.cc +++ b/methods/https.cc @@ -3,7 +3,7 @@ // $Id: http.cc,v 1.59 2004/05/08 19:42:35 mdz Exp $ /* ###################################################################### - HTTPS Acquire Method - This is the HTTPS aquire method for APT. + HTTPS Acquire Method - This is the HTTPS acquire method for APT. It uses libcurl @@ -21,7 +21,6 @@ #include <sys/stat.h> #include <sys/time.h> -#include <utime.h> #include <unistd.h> #include <signal.h> #include <stdio.h> @@ -36,11 +35,48 @@ /*}}}*/ using namespace std; +size_t +HttpsMethod::parse_header(void *buffer, size_t size, size_t nmemb, void *userp) +{ + size_t len = size * nmemb; + HttpsMethod *me = (HttpsMethod *)userp; + std::string line((char*) buffer, len); + for (--len; len > 0; --len) + if (isspace(line[len]) == 0) + { + ++len; + break; + } + line.erase(len); + + if (line.empty() == true) + { + if (me->Server->Result != 416 && me->Server->StartPos != 0) + ; + else if (me->Server->Result == 416 && me->Server->Size == me->File->FileSize()) + { + me->Server->Result = 200; + me->Server->StartPos = me->Server->Size; + } + else + me->Server->StartPos = 0; + + me->File->Truncate(me->Server->StartPos); + me->File->Seek(me->Server->StartPos); + } + else if (me->Server->HeaderLine(line) == false) + return 0; + + return size*nmemb; +} + size_t HttpsMethod::write_data(void *buffer, size_t size, size_t nmemb, void *userp) { HttpsMethod *me = (HttpsMethod *)userp; + if (me->Res.Size == 0) + me->URIStart(me->Res); if(me->File->Write(buffer, size*nmemb) != true) return false; @@ -54,11 +90,18 @@ HttpsMethod::progress_callback(void *clientp, double dltotal, double dlnow, HttpsMethod *me = (HttpsMethod *)clientp; if(dltotal > 0 && me->Res.Size == 0) { me->Res.Size = (unsigned long long)dltotal; - me->URIStart(me->Res); } return 0; } +// HttpsServerState::HttpsServerState - Constructor /*{{{*/ +HttpsServerState::HttpsServerState(URI Srv,HttpsMethod *Owner) : ServerState(Srv, NULL) +{ + TimeOut = _config->FindI("Acquire::https::Timeout",TimeOut); + Reset(); +} + /*}}}*/ + void HttpsMethod::SetupProxy() /*{{{*/ { URI ServerName = Queue->Uri; @@ -121,7 +164,6 @@ bool HttpsMethod::Fetch(FetchItem *Itm) struct stat SBuf; struct curl_slist *headers=NULL; char curl_errorstr[CURL_ERROR_SIZE]; - long curl_responsecode; URI Uri = Itm->Uri; string remotehost = Uri.Host; @@ -137,12 +179,18 @@ bool HttpsMethod::Fetch(FetchItem *Itm) // callbacks curl_easy_setopt(curl, CURLOPT_URL, static_cast<string>(Uri).c_str()); + curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, parse_header); + curl_easy_setopt(curl, CURLOPT_WRITEHEADER, this); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data); curl_easy_setopt(curl, CURLOPT_WRITEDATA, this); curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, progress_callback); curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, this); + // options curl_easy_setopt(curl, CURLOPT_NOPROGRESS, false); curl_easy_setopt(curl, CURLOPT_FILETIME, true); + // only allow curl to handle https, not the other stuff it supports + curl_easy_setopt(curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTPS); + curl_easy_setopt(curl, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS); // SSL parameters are set by default to the common (non mirror-specific) value // if available (or a default one) and gets overload by mirror-specific ones. @@ -261,7 +309,7 @@ bool HttpsMethod::Fetch(FetchItem *Itm) curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_errorstr); // If we ask for uncompressed files servers might respond with content- - // negotation which lets us end up with compressed files we do not support, + // negotiation which lets us end up with compressed files we do not support, // see 657029, 657560 and co, so if we have no extension on the request // ask for text only. As a sidenote: If there is nothing to negotate servers // seem to be nice and ignore it. @@ -277,7 +325,7 @@ bool HttpsMethod::Fetch(FetchItem *Itm) if (stat(Itm->DestFile.c_str(),&SBuf) >= 0 && SBuf.st_size > 0) { char Buf[1000]; - sprintf(Buf, "Range: bytes=%li-", (long) SBuf.st_size - 1); + sprintf(Buf, "Range: bytes=%li-", (long) SBuf.st_size); headers = curl_slist_append(headers, Buf); sprintf(Buf, "If-Range: %s", TimeRFC1123(SBuf.st_mtime).c_str()); headers = curl_slist_append(headers, Buf); @@ -290,18 +338,13 @@ bool HttpsMethod::Fetch(FetchItem *Itm) // go for it - if the file exists, append on it File = new FileFd(Itm->DestFile, FileFd::WriteAny); - if (File->Size() > 0) - File->Seek(File->Size() - 1); - + Server = new HttpsServerState(Itm->Uri, this); + // keep apt updated Res.Filename = Itm->DestFile; // get it! CURLcode success = curl_easy_perform(curl); - curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &curl_responsecode); - - long curl_servdate; - curl_easy_getinfo(curl, CURLINFO_FILETIME, &curl_servdate); // If the server returns 200 OK but the If-Modified-Since condition is not // met, CURLINFO_CONDITION_UNMET will be set to 1 @@ -309,57 +352,84 @@ bool HttpsMethod::Fetch(FetchItem *Itm) curl_easy_getinfo(curl, CURLINFO_CONDITION_UNMET, &curl_condition_unmet); File->Close(); + curl_slist_free_all(headers); // cleanup - if(success != 0 || (curl_responsecode != 200 && curl_responsecode != 304)) + if (success != 0) { _error->Error("%s", curl_errorstr); - // unlink, no need keep 401/404 page content in partial/ unlink(File->Name().c_str()); - Fail(); + return false; + } + + // server says file not modified + if (Server->Result == 304 || curl_condition_unmet == 1) + { + unlink(File->Name().c_str()); + Res.IMSHit = true; + Res.LastModified = Itm->LastModified; + Res.Size = 0; + URIDone(Res); return true; } + Res.IMSHit = false; - // Timestamp - struct utimbuf UBuf; - if (curl_servdate != -1) { - UBuf.actime = curl_servdate; - UBuf.modtime = curl_servdate; - utime(File->Name().c_str(),&UBuf); + if (Server->Result != 200 && // OK + Server->Result != 206 && // Partial + Server->Result != 416) // invalid Range + { + char err[255]; + snprintf(err, sizeof(err) - 1, "HttpError%i", Server->Result); + SetFailReason(err); + _error->Error("%s", err); + // unlink, no need keep 401/404 page content in partial/ + unlink(File->Name().c_str()); + return false; } - // check the downloaded result - struct stat Buf; - if (stat(File->Name().c_str(),&Buf) == 0) + struct stat resultStat; + if (unlikely(stat(File->Name().c_str(), &resultStat) != 0)) { - Res.Filename = File->Name(); - Res.LastModified = Buf.st_mtime; - Res.IMSHit = false; - if (curl_responsecode == 304 || curl_condition_unmet) - { - unlink(File->Name().c_str()); - Res.IMSHit = true; - Res.LastModified = Itm->LastModified; - Res.Size = 0; - URIDone(Res); - return true; - } - Res.Size = Buf.st_size; + _error->Errno("stat", "Unable to access file %s", File->Name().c_str()); + return false; } + Res.Size = resultStat.st_size; + + // invalid range-request + if (Server->Result == 416) + { + unlink(File->Name().c_str()); + Res.Size = 0; + delete File; + Redirect(Itm->Uri); + return true; + } + + // Timestamp + curl_easy_getinfo(curl, CURLINFO_FILETIME, &Res.LastModified); + if (Res.LastModified != -1) + { + struct timeval times[2]; + times[0].tv_sec = Res.LastModified; + times[1].tv_sec = Res.LastModified; + times[0].tv_usec = times[1].tv_usec = 0; + utimes(File->Name().c_str(), times); + } + else + Res.LastModified = resultStat.st_mtime; // take hashes Hashes Hash; FileFd Fd(Res.Filename, FileFd::ReadOnly); Hash.AddFD(Fd); Res.TakeHashes(Hash); - + // keep apt updated URIDone(Res); // cleanup Res.Size = 0; delete File; - curl_slist_free_all(headers); return true; }; @@ -374,4 +444,3 @@ int main() return Mth.Run(); } - diff --git a/methods/https.h b/methods/https.h index 293e288e0..ab0dd3407 100644 --- a/methods/https.h +++ b/methods/https.h @@ -3,7 +3,7 @@ // $Id: http.h,v 1.12 2002/04/18 05:09:38 jgg Exp $ /* ###################################################################### - HTTP Acquire Method - This is the HTTP aquire method for APT. + HTTP Acquire Method - This is the HTTP acquire method for APT. ##################################################################### */ /*}}}*/ @@ -14,29 +14,58 @@ #include <iostream> #include <curl/curl.h> +#include "server.h" + using std::cout; using std::endl; class HttpsMethod; class FileFd; +class HttpsServerState : public ServerState +{ + protected: + virtual bool ReadHeaderLines(std::string &Data) { return false; } + virtual bool LoadNextResponse(bool const ToFile, FileFd * const File) { return false; } + + public: + virtual bool WriteResponse(std::string const &Data) { return false; } + + /** \brief Transfer the data from the socket */ + virtual bool RunData(FileFd * const File) { return false; } + + virtual bool Open() { return false; } + virtual bool IsOpen() { return false; } + virtual bool Close() { return false; } + virtual bool InitHashes(FileFd &File) { return false; } + virtual Hashes * GetHashes() { return NULL; } + virtual bool Die(FileFd &File) { return false; } + virtual bool Flush(FileFd * const File) { return false; } + virtual bool Go(bool ToFile, FileFd * const File) { return false; } + + HttpsServerState(URI Srv, HttpsMethod *Owner); + virtual ~HttpsServerState() {Close();}; +}; + class HttpsMethod : public pkgAcqMethod { // minimum speed in bytes/se that triggers download timeout handling static const int DL_MIN_SPEED = 10; virtual bool Fetch(FetchItem *); + static size_t parse_header(void *buffer, size_t size, size_t nmemb, void *userp); static size_t write_data(void *buffer, size_t size, size_t nmemb, void *userp); static int progress_callback(void *clientp, double dltotal, double dlnow, double ultotal, double ulnow); void SetupProxy(); CURL *curl; FetchResult Res; + HttpsServerState *Server; public: FileFd *File; - HttpsMethod() : pkgAcqMethod("1.2",Pipeline | SendConfig) + HttpsMethod() : pkgAcqMethod("1.2",Pipeline | SendConfig), File(NULL) { File = 0; curl = curl_easy_init(); diff --git a/methods/makefile b/methods/makefile index 294c55d23..6b7781294 100644 --- a/methods/makefile +++ b/methods/makefile @@ -48,14 +48,14 @@ include $(PROGRAM_H) PROGRAM=http SLIBS = -lapt-pkg $(SOCKETLIBS) $(INTLLIBS) LIB_MAKES = apt-pkg/makefile -SOURCE = http.cc http_main.cc rfc2553emu.cc connect.cc +SOURCE = http.cc http_main.cc rfc2553emu.cc connect.cc server.cc include $(PROGRAM_H) # The https method PROGRAM=https SLIBS = -lapt-pkg -lcurl $(INTLLIBS) LIB_MAKES = apt-pkg/makefile -SOURCE = https.cc +SOURCE = https.cc server.cc include $(PROGRAM_H) # The ftp method @@ -83,7 +83,7 @@ include $(PROGRAM_H) PROGRAM=mirror SLIBS = -lapt-pkg $(SOCKETLIBS) LIB_MAKES = apt-pkg/makefile -SOURCE = mirror.cc http.cc rfc2553emu.cc connect.cc +SOURCE = mirror.cc http.cc rfc2553emu.cc connect.cc server.cc include $(PROGRAM_H) # SSH method symlink diff --git a/methods/mirror.cc b/methods/mirror.cc index 854366318..085f3717b 100644 --- a/methods/mirror.cc +++ b/methods/mirror.cc @@ -3,7 +3,7 @@ // $Id: mirror.cc,v 1.59 2004/05/08 19:42:35 mdz Exp $ /* ###################################################################### - Mirror Aquire Method - This is the Mirror aquire method for APT. + Mirror Acquire Method - This is the Mirror acquire method for APT. ##################################################################### */ /*}}}*/ @@ -49,7 +49,7 @@ using namespace std; * of the failure that is also send to LP * * TODO: - * - deal with runing as non-root because we can't write to the lists + * - deal with running as non-root because we can't write to the lists dir then -> use the cached mirror file * - better method to download than having a pkgAcquire interface here * and better error handling there! @@ -114,7 +114,7 @@ bool MirrorMethod::Clean(string Dir) for(I=list.begin(); I != list.end(); ++I) { string uri = (*I)->GetURI(); - if(uri.find("mirror://") != 0) + if(uri.compare(0, strlen("mirror://"), "mirror://") != 0) continue; string BaseUri = uri.substr(0,uri.size()-1); if (URItoFileName(BaseUri) == Dir->d_name) @@ -198,9 +198,9 @@ bool MirrorMethod::RandomizeMirrorFile(string mirror_file) // "stable" on the same machine. this is to avoid running into out-of-sync // issues (i.e. Release/Release.gpg different on each mirror) struct utsname buf; - int seed=1, i; + int seed=1; if(uname(&buf) == 0) { - for(i=0,seed=1; buf.nodename[i] != 0; i++) { + for(int i=0,seed=1; buf.nodename[i] != 0; ++i) { seed = seed * 31 + buf.nodename[i]; } } @@ -290,7 +290,7 @@ bool MirrorMethod::InitMirrors() // FIXME: make the mirror selection more clever, do not // just use the first one! // BUT: we can not make this random, the mirror has to be - // stable accross session, because otherwise we can + // stable across session, because otherwise we can // get into sync issues (got indexfiles from mirror A, // but packages from mirror B - one might be out of date etc) ifstream in(MirrorFile.c_str()); @@ -306,7 +306,7 @@ bool MirrorMethod::InitMirrors() if (s.size() == 0) continue; // ignore non http lines - if (s.find("http://") != 0) + if (s.compare(0, strlen("http://"), "http://") != 0) continue; AllMirrors.push_back(s); diff --git a/methods/mirror.h b/methods/mirror.h index 81e531e21..1dd9f2ec6 100644 --- a/methods/mirror.h +++ b/methods/mirror.h @@ -3,7 +3,7 @@ // $Id: http.h,v 1.12 2002/04/18 05:09:38 jgg Exp $ /* ###################################################################### - MIRROR Aquire Method - This is the MIRROR aquire method for APT. + MIRROR Acquire Method - This is the MIRROR acquire method for APT. ##################################################################### */ /*}}}*/ diff --git a/methods/rfc2553emu.h b/methods/rfc2553emu.h index b15facb31..ad7ddf48a 100644 --- a/methods/rfc2553emu.h +++ b/methods/rfc2553emu.h @@ -75,7 +75,7 @@ #endif /* If we don't have getaddrinfo then we probably don't have - sockaddr_storage either (same RFC) so we definately will not be + sockaddr_storage either (same RFC) so we definitely will not be doing any IPv6 stuff. Do not use the members of this structure to retain portability, cast to a sockaddr. */ #define sockaddr_storage sockaddr_in diff --git a/methods/rred.cc b/methods/rred.cc index 7c65f8f92..fe7ef7322 100644 --- a/methods/rred.cc +++ b/methods/rred.cc @@ -1,4 +1,10 @@ -// Includes /*{{{*/ +// Copyright (c) 2014 Anthony Towns +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. + #include <config.h> #include <apt-pkg/fileutl.h> @@ -9,563 +15,646 @@ #include <apt-pkg/hashes.h> #include <apt-pkg/configuration.h> -#include <sys/stat.h> -#include <sys/uio.h> -#include <unistd.h> -#include <utime.h> +#include <string> +#include <list> +#include <vector> +#include <iterator> + +#include <fcntl.h> +#include <assert.h> #include <stdio.h> -#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/time.h> + #include <apti18n.h> - /*}}}*/ -/** \brief RredMethod - ed-style incremential patch method {{{ - * - * This method implements a patch functionality similar to "patch --ed" that is - * used by the "tiffany" incremental packages download stuff. It differs from - * "ed" insofar that it is way more restricted (and therefore secure). - * The currently supported ed commands are "<em>c</em>hange", "<em>a</em>dd" and - * "<em>d</em>elete" (diff doesn't output any other). - * Additionally the records must be reverse sorted by line number and - * may not overlap (diff *seems* to produce this kind of output). - * */ -class RredMethod : public pkgAcqMethod { - bool Debug; - // the size of this doesn't really matter (except for performance) - const static int BUF_SIZE = 1024; - // the supported ed commands - enum Mode {MODE_CHANGED='c', MODE_DELETED='d', MODE_ADDED='a'}; - // return values - enum State {ED_OK, ED_ORDERING, ED_PARSER, ED_FAILURE, MMAP_FAILED}; - - State applyFile(FileFd &ed_cmds, FileFd &in_file, FileFd &out_file, - unsigned long &line, char *buffer, Hashes *hash) const; - void ignoreLineInFile(FileFd &fin, char *buffer) const; - void copyLinesFromFileToFile(FileFd &fin, FileFd &fout, unsigned int lines, - Hashes *hash, char *buffer) const; - - State patchFile(FileFd &Patch, FileFd &From, FileFd &out_file, Hashes *hash) const; - State patchMMap(FileFd &Patch, FileFd &From, FileFd &out_file, Hashes *hash) const; - -protected: - // the methods main method - virtual bool Fetch(FetchItem *Itm); - -public: - RredMethod() : pkgAcqMethod("1.1",SingleInstance | SendConfig), Debug(false) {}; + +#define BLOCK_SIZE (512*1024) + +class MemBlock { + char *start; + size_t size; + char *free; + struct MemBlock *next; + + MemBlock(size_t size) : size(size), next(NULL) + { + free = start = new char[size]; + } + + size_t avail(void) { return size - (free - start); } + + public: + + MemBlock(void) { + free = start = new char[BLOCK_SIZE]; + size = BLOCK_SIZE; + next = NULL; + } + + ~MemBlock() { + delete [] start; + delete next; + } + + void clear(void) { + free = start; + if (next) + next->clear(); + } + + char *add_easy(char *src, size_t len, char *last) + { + if (last) { + for (MemBlock *k = this; k; k = k->next) { + if (k->free == last) { + if (len <= k->avail()) { + char *n = k->add(src, len); + assert(last == n); + if (last == n) + return NULL; + return n; + } else { + break; + } + } else if (last >= start && last < free) { + break; + } + } + } + return add(src, len); + } + + char *add(char *src, size_t len) { + if (len > avail()) { + if (!next) { + if (len > BLOCK_SIZE) { + next = new MemBlock(len); + } else { + next = new MemBlock; + } + } + return next->add(src, len); + } + char *dst = free; + free += len; + memcpy(dst, src, len); + return dst; + } }; - /*}}}*/ -/** \brief applyFile - in reverse order with a tail recursion {{{ - * - * As it is expected that the commands are in reversed order in the patch file - * we check in the first half if the command is valid, but doesn't execute it - * and move a step deeper. After reaching the end of the file we apply the - * patches in the correct order: last found command first. - * - * \param ed_cmds patch file to apply - * \param in_file base file we want to patch - * \param out_file file to write the patched result to - * \param line of command operation - * \param buffer internal used read/write buffer - * \param hash the created file for correctness - * \return the success State of the ed command executor - */ -RredMethod::State RredMethod::applyFile(FileFd &ed_cmds, FileFd &in_file, FileFd &out_file, - unsigned long &line, char *buffer, Hashes *hash) const { - // get the current command and parse it - if (ed_cmds.ReadLine(buffer, BUF_SIZE) == NULL) { - if (Debug == true) - std::clog << "rred: encounter end of file - we can start patching now." << std::endl; - line = 0; - return ED_OK; - } - - // parse in the effected linenumbers - char* idx; - errno=0; - unsigned long const startline = strtol(buffer, &idx, 10); - if (errno == ERANGE || errno == EINVAL) { - _error->Errno("rred", "startline is an invalid number"); - return ED_PARSER; - } - if (startline > line) { - _error->Error("rred: The start line (%lu) of the next command is higher than the last line (%lu). This is not allowed.", startline, line); - return ED_ORDERING; - } - unsigned long stopline; - if (*idx == ',') { - idx++; - errno=0; - stopline = strtol(idx, &idx, 10); - if (errno == ERANGE || errno == EINVAL) { - _error->Errno("rred", "stopline is an invalid number"); - return ED_PARSER; - } - } - else { - stopline = startline; - } - line = startline; - - // which command to execute on this line(s)? - switch (*idx) { - case MODE_CHANGED: - if (Debug == true) - std::clog << "Change from line " << startline << " to " << stopline << std::endl; - break; - case MODE_ADDED: - if (Debug == true) - std::clog << "Insert after line " << startline << std::endl; - break; - case MODE_DELETED: - if (Debug == true) - std::clog << "Delete from line " << startline << " to " << stopline << std::endl; - break; - default: - _error->Error("rred: Unknown ed command '%c'. Abort.", *idx); - return ED_PARSER; - } - unsigned char mode = *idx; - - // save the current position - unsigned const long long pos = ed_cmds.Tell(); - - // if this is add or change then go to the next full stop - unsigned int data_length = 0; - if (mode == MODE_CHANGED || mode == MODE_ADDED) { - do { - ignoreLineInFile(ed_cmds, buffer); - data_length++; - } - while (strncmp(buffer, ".", 1) != 0); - data_length--; // the dot should not be copied - } - - // do the recursive call - the last command is the one we need to execute at first - const State child = applyFile(ed_cmds, in_file, out_file, line, buffer, hash); - if (child != ED_OK) { - return child; - } - - // change and delete are working on "line" - add is done after "line" - if (mode != MODE_ADDED) - line++; - - // first wind to the current position and copy over all unchanged lines - if (line < startline) { - copyLinesFromFileToFile(in_file, out_file, (startline - line), hash, buffer); - line = startline; - } - - if (mode != MODE_ADDED) - line--; - - // include data from ed script - if (mode == MODE_CHANGED || mode == MODE_ADDED) { - ed_cmds.Seek(pos); - copyLinesFromFileToFile(ed_cmds, out_file, data_length, hash, buffer); - } - - // ignore the corresponding number of lines from input - if (mode == MODE_CHANGED || mode == MODE_DELETED) { - while (line < stopline) { - ignoreLineInFile(in_file, buffer); - line++; - } - } - return ED_OK; -} - /*}}}*/ -void RredMethod::copyLinesFromFileToFile(FileFd &fin, FileFd &fout, unsigned int lines,/*{{{*/ - Hashes *hash, char *buffer) const { - while (0 < lines--) { - do { - fin.ReadLine(buffer, BUF_SIZE); - unsigned long long const towrite = strlen(buffer); - fout.Write(buffer, towrite); - hash->Add((unsigned char*)buffer, towrite); - } while (strlen(buffer) == (BUF_SIZE - 1) && - buffer[BUF_SIZE - 2] != '\n'); - } -} - /*}}}*/ -void RredMethod::ignoreLineInFile(FileFd &fin, char *buffer) const { /*{{{*/ - fin.ReadLine(buffer, BUF_SIZE); - while (strlen(buffer) == (BUF_SIZE - 1) && - buffer[BUF_SIZE - 2] != '\n') { - fin.ReadLine(buffer, BUF_SIZE); - buffer[0] = ' '; - } -} - /*}}}*/ -RredMethod::State RredMethod::patchFile(FileFd &Patch, FileFd &From, /*{{{*/ - FileFd &out_file, Hashes *hash) const { - char buffer[BUF_SIZE]; - - /* we do a tail recursion to read the commands in the right order */ - unsigned long line = -1; // assign highest possible value - State const result = applyFile(Patch, From, out_file, line, buffer, hash); - - /* read the rest from infile */ - if (result == ED_OK) { - while (From.ReadLine(buffer, BUF_SIZE) != NULL) { - unsigned long long const towrite = strlen(buffer); - out_file.Write(buffer, towrite); - hash->Add((unsigned char*)buffer, towrite); + +struct Change { + /* Ordering: + * + * 1. write out <offset> lines unchanged + * 2. skip <del_cnt> lines from source + * 3. write out <add_cnt> lines (<add>/<add_len>) + */ + size_t offset; + size_t del_cnt; + size_t add_cnt; /* lines */ + size_t add_len; /* bytes */ + char *add; + + Change(int off) + { + offset = off; + del_cnt = add_cnt = add_len = 0; + add = NULL; + } + + /* actually, don't write <lines> lines from <add> */ + void skip_lines(size_t lines) + { + while (lines > 0) { + char *s = (char*) memchr(add, '\n', add_len); + assert(s != NULL); + s++; + add_len -= (s - add); + add_cnt--; + lines--; + if (add_len == 0) { + add = NULL; + assert(add_cnt == 0); + assert(lines == 0); + } else { + add = s; + assert(add_cnt > 0); + } } } - return result; -} - /*}}}*/ -/* struct EdCommand {{{*/ -#ifdef _POSIX_MAPPED_FILES -struct EdCommand { - size_t data_start; - size_t data_end; - size_t data_lines; - size_t first_line; - size_t last_line; - char type; }; -#define IOV_COUNT 1024 /* Don't really want IOV_MAX since it can be arbitrarily large */ -static ssize_t retry_writev(int fd, const struct iovec *iov, int iovcnt) { - ssize_t Res; - errno = 0; - ssize_t i = 0; - do { - Res = writev(fd, iov + i, iovcnt); - if (Res < 0 && errno == EINTR) - continue; - if (Res < 0) - return _error->Errno("writev",_("Write error")); - iovcnt -= Res; - i += Res; - } while (Res > 0 && iovcnt > 0); - return i; -} -#endif - /*}}}*/ -RredMethod::State RredMethod::patchMMap(FileFd &Patch, FileFd &From, /*{{{*/ - FileFd &out_file, Hashes *hash) const { -#ifdef _POSIX_MAPPED_FILES - MMap ed_cmds(Patch, MMap::ReadOnly); - MMap in_file(From, MMap::ReadOnly); - - unsigned long long const ed_size = ed_cmds.Size(); - unsigned long long const in_size = in_file.Size(); - if (ed_size == 0 || in_size == 0) - return MMAP_FAILED; - - EdCommand* commands = 0; - size_t command_count = 0; - size_t command_alloc = 0; - - const char* begin = (char*) ed_cmds.Data(); - const char* end = begin; - const char* ed_end = (char*) ed_cmds.Data() + ed_size; - - const char* input = (char*) in_file.Data(); - const char* input_end = (char*) in_file.Data() + in_size; - - size_t i; - - /* 1. Parse entire script. It is executed in reverse order, so we cather it - * in the `commands' buffer first - */ - - for(;;) { - EdCommand cmd; - cmd.data_start = 0; - cmd.data_end = 0; - - while(begin != ed_end && *begin == '\n') - ++begin; - while(end != ed_end && *end != '\n') - ++end; - if(end == ed_end && begin == end) - break; - - /* Determine command range */ - const char* tmp = begin; - - for(;;) { - /* atoll is safe despite lacking NUL-termination; we know there's an - * alphabetic character at end[-1] - */ - if(tmp == end) { - cmd.first_line = atol(begin); - cmd.last_line = cmd.first_line; - break; - } - if(*tmp == ',') { - cmd.first_line = atol(begin); - cmd.last_line = atol(tmp + 1); - break; - } - ++tmp; - } - - // which command to execute on this line(s)? - switch (end[-1]) { - case MODE_CHANGED: - if (Debug == true) - std::clog << "Change from line " << cmd.first_line << " to " << cmd.last_line << std::endl; - break; - case MODE_ADDED: - if (Debug == true) - std::clog << "Insert after line " << cmd.first_line << std::endl; - break; - case MODE_DELETED: - if (Debug == true) - std::clog << "Delete from line " << cmd.first_line << " to " << cmd.last_line << std::endl; - break; - default: - _error->Error("rred: Unknown ed command '%c'. Abort.", end[-1]); - free(commands); - return ED_PARSER; - } - cmd.type = end[-1]; - - /* Determine the size of the inserted text, so we don't have to scan this - * text again later. - */ - begin = end + 1; - end = begin; - cmd.data_lines = 0; - - if(cmd.type == MODE_ADDED || cmd.type == MODE_CHANGED) { - cmd.data_start = begin - (char*) ed_cmds.Data(); - while(end != ed_end) { - if(*end == '\n') { - if(end[-1] == '.' && end[-2] == '\n') - break; - ++cmd.data_lines; - } - ++end; - } - cmd.data_end = end - (char*) ed_cmds.Data() - 1; - begin = end + 1; - end = begin; - } - if(command_count == command_alloc) { - command_alloc = (command_alloc + 64) * 3 / 2; - EdCommand* newCommands = (EdCommand*) realloc(commands, command_alloc * sizeof(EdCommand)); - if (newCommands == NULL) { - free(commands); - return MMAP_FAILED; - } - commands = newCommands; - } - commands[command_count++] = cmd; - } - - struct iovec* iov = new struct iovec[IOV_COUNT]; - size_t iov_size = 0; - - size_t amount, remaining; - size_t line = 1; - EdCommand* cmd; - - /* 2. Execute script. We gather writes in a `struct iov' array, and flush - * using writev to minimize the number of system calls. Data is read - * directly from the memory mappings of the input file and the script. - */ - - for(i = command_count; i-- > 0; ) { - cmd = &commands[i]; - if(cmd->type == MODE_ADDED) - amount = cmd->first_line + 1; - else - amount = cmd->first_line; - - if(line < amount) { - begin = input; - while(line != amount) { - input = (const char*) memchr(input, '\n', input_end - input); - if(!input) - break; - ++line; - ++input; - } - - iov[iov_size].iov_base = (void*) begin; - iov[iov_size].iov_len = input - begin; - hash->Add((const unsigned char*) begin, input - begin); - - if(++iov_size == IOV_COUNT) { - retry_writev(out_file.Fd(), iov, IOV_COUNT); - iov_size = 0; - } - } - - if(cmd->type == MODE_DELETED || cmd->type == MODE_CHANGED) { - remaining = (cmd->last_line - cmd->first_line) + 1; - line += remaining; - while(remaining) { - input = (const char*) memchr(input, '\n', input_end - input); - if(!input) - break; - --remaining; - ++input; - } - } - - if(cmd->type == MODE_CHANGED || cmd->type == MODE_ADDED) { - if(cmd->data_end != cmd->data_start) { - iov[iov_size].iov_base = (void*) ((char*)ed_cmds.Data() + cmd->data_start); - iov[iov_size].iov_len = cmd->data_end - cmd->data_start; - hash->Add((const unsigned char*) ((char*)ed_cmds.Data() + cmd->data_start), - iov[iov_size].iov_len); - - if(++iov_size == IOV_COUNT) { - retry_writev(out_file.Fd(), iov, IOV_COUNT); - iov_size = 0; - } - } - } - } - - if(input != input_end) { - iov[iov_size].iov_base = (void*) input; - iov[iov_size].iov_len = input_end - input; - hash->Add((const unsigned char*) input, input_end - input); - ++iov_size; - } - - if(iov_size) { - retry_writev(out_file.Fd(), iov, iov_size); - iov_size = 0; - } - - for(i = 0; i < iov_size; i += IOV_COUNT) { - if(iov_size - i < IOV_COUNT) - retry_writev(out_file.Fd(), iov + i, iov_size - i); - else - retry_writev(out_file.Fd(), iov + i, IOV_COUNT); - } - - delete [] iov; - free(commands); - - return ED_OK; + +class FileChanges { + std::list<struct Change> changes; + std::list<struct Change>::iterator where; + size_t pos; // line number is as far left of iterator as possible + + bool pos_is_okay(void) + { +#ifdef POSDEBUG + size_t cpos = 0; + std::list<struct Change>::iterator x; + for (x = changes.begin(); x != where; ++x) { + assert(x != changes.end()); + cpos += x->offset + x->add_cnt; + } + return cpos == pos; #else - return MMAP_FAILED; + return true; #endif -} - /*}}}*/ -bool RredMethod::Fetch(FetchItem *Itm) /*{{{*/ -{ - Debug = _config->FindB("Debug::pkgAcquire::RRed", false); - URI Get = Itm->Uri; - std::string Path = Get.Host + Get.Path; // To account for relative paths - - FetchResult Res; - Res.Filename = Itm->DestFile; - if (Itm->Uri.empty() == true) { - Path = Itm->DestFile; - Itm->DestFile.append(".result"); - } else - URIStart(Res); - - if (Debug == true) - std::clog << "Patching " << Path << " with " << Path - << ".ed and putting result into " << Itm->DestFile << std::endl; - // Open the source and destination files (the d'tor of FileFd will do - // the cleanup/closing of the fds) - FileFd From(Path,FileFd::ReadOnly); - FileFd Patch(Path+".ed",FileFd::ReadOnly, FileFd::Gzip); - FileFd To(Itm->DestFile,FileFd::WriteAtomic); - To.EraseOnFailure(); - if (_error->PendingError() == true) - return false; - - Hashes Hash; - // now do the actual patching - State const result = patchMMap(Patch, From, To, &Hash); - if (result == MMAP_FAILED) { - // retry with patchFile - Patch.Seek(0); - From.Seek(0); - To.Open(Itm->DestFile,FileFd::WriteAtomic); - if (_error->PendingError() == true) - return false; - if (patchFile(Patch, From, To, &Hash) != ED_OK) { - return _error->WarningE("rred", _("Could not patch %s with mmap and with file operation usage - the patch seems to be corrupt."), Path.c_str()); - } else if (Debug == true) { - std::clog << "rred: finished file patching of " << Path << " after mmap failed." << std::endl; + } + + public: + FileChanges() { + where = changes.end(); + pos = 0; + } + + std::list<struct Change>::iterator begin(void) { return changes.begin(); } + std::list<struct Change>::iterator end(void) { return changes.end(); } + + std::list<struct Change>::reverse_iterator rbegin(void) { return changes.rbegin(); } + std::list<struct Change>::reverse_iterator rend(void) { return changes.rend(); } + + void add_change(Change c) { + assert(pos_is_okay()); + go_to_change_for(c.offset); + assert(pos + where->offset == c.offset); + if (c.del_cnt > 0) + delete_lines(c.del_cnt); + assert(pos + where->offset == c.offset); + if (c.add_len > 0) { + assert(pos_is_okay()); + if (where->add_len > 0) + new_change(); + assert(where->add_len == 0 && where->add_cnt == 0); + + where->add_len = c.add_len; + where->add_cnt = c.add_cnt; + where->add = c.add; } - } else if (result != ED_OK) { - return _error->Errno("rred", _("Could not patch %s with mmap (but no mmap specific fail) - the patch seems to be corrupt."), Path.c_str()); - } else if (Debug == true) { - std::clog << "rred: finished mmap patching of " << Path << std::endl; + assert(pos_is_okay()); + merge(); + assert(pos_is_okay()); } - // write out the result - From.Close(); - Patch.Close(); - To.Close(); - - /* Transfer the modification times from the patch file - to be able to see in which state the file should be - and use the access time from the "old" file */ - struct stat BufBase, BufPatch; - if (stat(Path.c_str(),&BufBase) != 0 || - stat(std::string(Path+".ed").c_str(),&BufPatch) != 0) - return _error->Errno("stat",_("Failed to stat")); - - struct utimbuf TimeBuf; - TimeBuf.actime = BufBase.st_atime; - TimeBuf.modtime = BufPatch.st_mtime; - if (utime(Itm->DestFile.c_str(),&TimeBuf) != 0) - return _error->Errno("utime",_("Failed to set modification time")); - - if (stat(Itm->DestFile.c_str(),&BufBase) != 0) - return _error->Errno("stat",_("Failed to stat")); - - // return done - Res.LastModified = BufBase.st_mtime; - Res.Size = BufBase.st_size; - Res.TakeHashes(Hash); - URIDone(Res); - - return true; -} - /*}}}*/ -/** \brief Wrapper class for testing rred */ /*{{{*/ -class TestRredMethod : public RredMethod { -public: - /** \brief Run rred in debug test mode - * - * This method can be used to run the rred method outside - * of the "normal" acquire environment for easier testing. - * - * \param base basename of all files involved in this rred test - */ - bool Run(char const *base) { - _config->CndSet("Debug::pkgAcquire::RRed", "true"); - FetchItem *test = new FetchItem; - test->DestFile = base; - return Fetch(test); - } + private: + void merge(void) + { + while (where->offset == 0 && where != changes.begin()) { + left(); + } + std::list<struct Change>::iterator next = where; + ++next; + + while (next != changes.end() && next->offset == 0) { + where->del_cnt += next->del_cnt; + next->del_cnt = 0; + if (next->add == NULL) { + next = changes.erase(next); + } else if (where->add == NULL) { + where->add = next->add; + where->add_len = next->add_len; + where->add_cnt = next->add_cnt; + next = changes.erase(next); + } else { + ++next; + } + } + } + + void go_to_change_for(size_t line) + { + while(where != changes.end()) { + if (line < pos) { + left(); + continue; + } + if (pos + where->offset + where->add_cnt <= line) { + right(); + continue; + } + // line is somewhere in this slot + if (line < pos + where->offset) { + break; + } else if (line == pos + where->offset) { + return; + } else { + split(line - pos); + right(); + return; + } + } + /* it goes before this patch */ + insert(line-pos); + } + + void new_change(void) { insert(where->offset); } + + void insert(size_t offset) + { + assert(pos_is_okay()); + assert(where == changes.end() || offset <= where->offset); + if (where != changes.end()) + where->offset -= offset; + changes.insert(where, Change(offset)); + --where; + assert(pos_is_okay()); + } + + void split(size_t offset) + { + assert(pos_is_okay()); + + assert(where->offset < offset); + assert(offset < where->offset + where->add_cnt); + + size_t keep_lines = offset - where->offset; + + Change before(*where); + + where->del_cnt = 0; + where->offset = 0; + where->skip_lines(keep_lines); + + before.add_cnt = keep_lines; + before.add_len -= where->add_len; + + changes.insert(where, before); + --where; + assert(pos_is_okay()); + } + + void delete_lines(size_t cnt) + { + std::list<struct Change>::iterator x = where; + assert(pos_is_okay()); + while (cnt > 0) + { + size_t del; + del = x->add_cnt; + if (del > cnt) + del = cnt; + x->skip_lines(del); + cnt -= del; + + ++x; + if (x == changes.end()) { + del = cnt; + } else { + del = x->offset; + if (del > cnt) + del = cnt; + x->offset -= del; + } + where->del_cnt += del; + cnt -= del; + } + assert(pos_is_okay()); + } + + void left(void) { + assert(pos_is_okay()); + --where; + pos -= where->offset + where->add_cnt; + assert(pos_is_okay()); + } + + void right(void) { + assert(pos_is_okay()); + pos += where->offset + where->add_cnt; + ++where; + assert(pos_is_okay()); + } +}; + +class Patch { + FileChanges filechanges; + MemBlock add_text; + + static bool retry_fwrite(char *b, size_t l, FILE *f, Hashes *hash) + { + size_t r = 1; + while (r > 0 && l > 0) + { + r = fwrite(b, 1, l, f); + if (hash) + hash->Add((unsigned char*)b, r); + l -= r; + b += r; + } + return l == 0; + } + + static void dump_rest(FILE *o, FILE *i, Hashes *hash) + { + char buffer[BLOCK_SIZE]; + size_t l; + while (0 < (l = fread(buffer, 1, sizeof(buffer), i))) { + if (!retry_fwrite(buffer, l, o, hash)) + break; + } + } + + static void dump_lines(FILE *o, FILE *i, size_t n, Hashes *hash) + { + char buffer[BLOCK_SIZE]; + while (n > 0) { + if (fgets(buffer, sizeof(buffer), i) == 0) + buffer[0] = '\0'; + size_t const l = strlen(buffer); + if (l == 0 || buffer[l-1] == '\n') + n--; + retry_fwrite(buffer, l, o, hash); + } + } + + static void skip_lines(FILE *i, int n) + { + char buffer[BLOCK_SIZE]; + while (n > 0) { + if (fgets(buffer, sizeof(buffer), i) == 0) + buffer[0] = '\0'; + size_t const l = strlen(buffer); + if (l == 0 || buffer[l-1] == '\n') + n--; + } + } + + static void dump_mem(FILE *o, char *p, size_t s, Hashes *hash) { + retry_fwrite(p, s, o, hash); + } + + public: + + void read_diff(FileFd &f) + { + char buffer[BLOCK_SIZE]; + bool cmdwanted = true; + + Change ch(0); + while(f.ReadLine(buffer, sizeof(buffer))) + { + if (cmdwanted) { + char *m, *c; + size_t s, e; + s = strtol(buffer, &m, 10); + if (m == buffer) { + s = e = ch.offset + ch.add_cnt; + c = buffer; + } else if (*m == ',') { + m++; + e = strtol(m, &c, 10); + } else { + e = s; + c = m; + } + switch(*c) { + case 'a': + cmdwanted = false; + ch.add = NULL; + ch.add_cnt = 0; + ch.add_len = 0; + ch.offset = s; + ch.del_cnt = 0; + break; + case 'c': + cmdwanted = false; + ch.add = NULL; + ch.add_cnt = 0; + ch.add_len = 0; + ch.offset = s - 1; + ch.del_cnt = e - s + 1; + break; + case 'd': + ch.offset = s - 1; + ch.del_cnt = e - s + 1; + ch.add = NULL; + ch.add_cnt = 0; + ch.add_len = 0; + filechanges.add_change(ch); + break; + } + } else { /* !cmdwanted */ + if (buffer[0] == '.' && buffer[1] == '\n') { + cmdwanted = true; + filechanges.add_change(ch); + } else { + char *last = NULL; + char *add; + size_t l; + if (ch.add) + last = ch.add + ch.add_len; + l = strlen(buffer); + add = add_text.add_easy(buffer, l, last); + if (!add) { + ch.add_len += l; + ch.add_cnt++; + } else { + if (ch.add) { + filechanges.add_change(ch); + ch.del_cnt = 0; + } + ch.offset += ch.add_cnt; + ch.add = add; + ch.add_len = l; + ch.add_cnt = 1; + } + } + } + } + } + + void write_diff(FILE *f) + { + size_t line = 0; + std::list<struct Change>::reverse_iterator ch; + for (ch = filechanges.rbegin(); ch != filechanges.rend(); ++ch) { + line += ch->offset + ch->del_cnt; + } + + for (ch = filechanges.rbegin(); ch != filechanges.rend(); ++ch) { + std::list<struct Change>::reverse_iterator mg_i, mg_e = ch; + while (ch->del_cnt == 0 && ch->offset == 0) + ++ch; + line -= ch->del_cnt; + if (ch->add_cnt > 0) { + if (ch->del_cnt == 0) { + fprintf(f, "%lua\n", line); + } else if (ch->del_cnt == 1) { + fprintf(f, "%luc\n", line+1); + } else { + fprintf(f, "%lu,%luc\n", line+1, line+ch->del_cnt); + } + + mg_i = ch; + do { + dump_mem(f, mg_i->add, mg_i->add_len, NULL); + } while (mg_i-- != mg_e); + + fprintf(f, ".\n"); + } else if (ch->del_cnt == 1) { + fprintf(f, "%lud\n", line+1); + } else if (ch->del_cnt > 1) { + fprintf(f, "%lu,%lud\n", line+1, line+ch->del_cnt); + } + line -= ch->offset; + } + } + + void apply_against_file(FILE *out, FILE *in, Hashes *hash = NULL) + { + std::list<struct Change>::iterator ch; + for (ch = filechanges.begin(); ch != filechanges.end(); ++ch) { + dump_lines(out, in, ch->offset, hash); + skip_lines(in, ch->del_cnt); + dump_mem(out, ch->add, ch->add_len, hash); + } + dump_rest(out, in, hash); + } }; - /*}}}*/ -/** \brief Starter for the rred method (or its test method) {{{ - * - * Used without parameters is the normal behavior for methods for - * the APT acquire system. While this works great for the acquire system - * it is very hard to test the method and therefore the method also - * accepts one parameter which will switch it directly to debug test mode: - * The test mode expects that if "Testfile" is given as parameter - * the file "Testfile" should be ed-style patched with "Testfile.ed" - * and will write the result to "Testfile.result". - */ -int main(int argc, char *argv[]) { - if (argc <= 1) { - RredMethod Mth; - return Mth.Run(); - } else { - TestRredMethod Mth; - bool result = Mth.Run(argv[1]); - _error->DumpErrors(); - return result; - } + +class RredMethod : public pkgAcqMethod { + private: + bool Debug; + + protected: + virtual bool Fetch(FetchItem *Itm) { + Debug = _config->FindB("Debug::pkgAcquire::RRed", false); + URI Get = Itm->Uri; + std::string Path = Get.Host + Get.Path; // rred:/path - no host + + FetchResult Res; + Res.Filename = Itm->DestFile; + if (Itm->Uri.empty()) + { + Path = Itm->DestFile; + Itm->DestFile.append(".result"); + } else + URIStart(Res); + + std::vector<std::string> patchpaths; + Patch patch; + + if (FileExists(Path + ".ed") == true) + patchpaths.push_back(Path + ".ed"); + else + { + _error->PushToStack(); + std::vector<std::string> patches = GetListOfFilesInDir(flNotFile(Path), "gz", true, false); + _error->RevertToStack(); + + std::string const baseName = Path + ".ed."; + for (std::vector<std::string>::const_iterator p = patches.begin(); + p != patches.end(); ++p) + if (p->compare(0, baseName.length(), baseName) == 0) + patchpaths.push_back(*p); + } + + std::string patch_name; + for (std::vector<std::string>::iterator I = patchpaths.begin(); + I != patchpaths.end(); + ++I) + { + patch_name = *I; + if (Debug == true) + std::clog << "Patching " << Path << " with " << patch_name + << std::endl; + + FileFd p; + // all patches are compressed, even if the name doesn't reflect it + if (p.Open(patch_name, FileFd::ReadOnly, FileFd::Gzip) == false) { + std::cerr << "Could not open patch file " << patch_name << std::endl; + _error->DumpErrors(std::cerr); + abort(); + } + patch.read_diff(p); + p.Close(); + } + + if (Debug == true) + std::clog << "Applying patches against " << Path + << " and writing results to " << Itm->DestFile + << std::endl; + + FILE *inp = fopen(Path.c_str(), "r"); + FILE *out = fopen(Itm->DestFile.c_str(), "w"); + + Hashes hash; + + patch.apply_against_file(out, inp, &hash); + + fclose(out); + fclose(inp); + + if (Debug == true) { + std::clog << "rred: finished file patching of " << Path << "." << std::endl; + } + + struct stat bufbase, bufpatch; + if (stat(Path.c_str(), &bufbase) != 0 || + stat(patch_name.c_str(), &bufpatch) != 0) + return _error->Errno("stat", _("Failed to stat")); + + struct timeval times[2]; + times[0].tv_sec = bufbase.st_atime; + times[1].tv_sec = bufpatch.st_mtime; + times[0].tv_usec = times[1].tv_usec = 0; + if (utimes(Itm->DestFile.c_str(), times) != 0) + return _error->Errno("utimes",_("Failed to set modification time")); + + if (stat(Itm->DestFile.c_str(), &bufbase) != 0) + return _error->Errno("stat", _("Failed to stat")); + + Res.LastModified = bufbase.st_mtime; + Res.Size = bufbase.st_size; + Res.TakeHashes(hash); + URIDone(Res); + + return true; + } + + public: + RredMethod() : pkgAcqMethod("2.0",SingleInstance | SendConfig), Debug(false) {} +}; + +int main(int argc, char **argv) +{ + int i; + bool just_diff = true; + Patch patch; + + if (argc <= 1) { + RredMethod Mth; + return Mth.Run(); + } + + if (argc > 1 && strcmp(argv[1], "-f") == 0) { + just_diff = false; + i = 2; + } else { + i = 1; + } + + for (; i < argc; i++) { + FileFd p; + if (p.Open(argv[i], FileFd::ReadOnly) == false) { + _error->DumpErrors(std::cerr); + exit(1); + } + patch.read_diff(p); + } + + if (just_diff) { + patch.write_diff(stdout); + } else { + FILE *out, *inp; + out = stdout; + inp = stdin; + + patch.apply_against_file(out, inp); + } + return 0; } - /*}}}*/ diff --git a/methods/rsh.cc b/methods/rsh.cc index d76dca6ef..550f77eca 100644 --- a/methods/rsh.cc +++ b/methods/rsh.cc @@ -20,7 +20,6 @@ #include <sys/stat.h> #include <sys/time.h> -#include <utime.h> #include <unistd.h> #include <signal.h> #include <stdio.h> @@ -256,7 +255,7 @@ bool RSHConn::WriteMsg(std::string &Text,bool Sync,const char *Fmt,...) /*}}}*/ // RSHConn::Size - Return the size of the file /*{{{*/ // --------------------------------------------------------------------- -/* Right now for successfull transfer the file size must be known in +/* Right now for successful transfer the file size must be known in advance. */ bool RSHConn::Size(const char *Path,unsigned long long &Size) { @@ -395,13 +394,14 @@ void RSHMethod::SigTerm(int sig) { if (FailFd == -1) _exit(100); - close(FailFd); - // Timestamp - struct utimbuf UBuf; - UBuf.actime = FailTime; - UBuf.modtime = FailTime; - utime(FailFile.c_str(),&UBuf); + // Transfer the modification times + struct timeval times[2]; + times[0].tv_sec = FailTime; + times[1].tv_sec = FailTime; + times[0].tv_usec = times[1].tv_usec = 0; + utimes(FailFile.c_str(), times); + close(FailFd); _exit(100); } @@ -488,10 +488,11 @@ bool RSHMethod::Fetch(FetchItem *Itm) Fd.Close(); // Timestamp - struct utimbuf UBuf; - UBuf.actime = FailTime; - UBuf.modtime = FailTime; - utime(FailFile.c_str(),&UBuf); + struct timeval times[2]; + times[0].tv_sec = FailTime; + times[1].tv_sec = FailTime; + times[0].tv_usec = times[1].tv_usec = 0; + utimes(FailFile.c_str(), times); // If the file is missing we hard fail otherwise transient fail if (Missing == true) @@ -501,18 +502,17 @@ bool RSHMethod::Fetch(FetchItem *Itm) } Res.Size = Fd.Size(); + struct timeval times[2]; + times[0].tv_sec = FailTime; + times[1].tv_sec = FailTime; + times[0].tv_usec = times[1].tv_usec = 0; + utimes(Fd.Name().c_str(), times); + FailFd = -1; } Res.LastModified = FailTime; Res.TakeHashes(Hash); - // Timestamp - struct utimbuf UBuf; - UBuf.actime = FailTime; - UBuf.modtime = FailTime; - utime(Queue->DestFile.c_str(),&UBuf); - FailFd = -1; - URIDone(Res); return true; diff --git a/methods/server.cc b/methods/server.cc new file mode 100644 index 000000000..ef90c809c --- /dev/null +++ b/methods/server.cc @@ -0,0 +1,668 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + HTTP and HTTPS share a lot of common code and these classes are + exactly the dumping ground for this common code + + ##################################################################### */ + /*}}}*/ +// Include Files /*{{{*/ +#include <config.h> + +#include <apt-pkg/fileutl.h> +#include <apt-pkg/acquire-method.h> +#include <apt-pkg/configuration.h> +#include <apt-pkg/error.h> +#include <apt-pkg/hashes.h> +#include <apt-pkg/netrc.h> + +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <unistd.h> +#include <signal.h> +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <climits> +#include <iostream> +#include <map> + +// Internet stuff +#include <netdb.h> + +#include "config.h" +#include "connect.h" +#include "rfc2553emu.h" +#include "http.h" + +#include <apti18n.h> + /*}}}*/ +using namespace std; + +string ServerMethod::FailFile; +int ServerMethod::FailFd = -1; +time_t ServerMethod::FailTime = 0; + +// ServerState::RunHeaders - Get the headers before the data /*{{{*/ +// --------------------------------------------------------------------- +/* Returns 0 if things are OK, 1 if an IO error occurred and 2 if a header + parse error occurred */ +ServerState::RunHeadersResult ServerState::RunHeaders(FileFd * const File) +{ + State = Header; + + Owner->Status(_("Waiting for headers")); + + Major = 0; + Minor = 0; + Result = 0; + Size = 0; + StartPos = 0; + Encoding = Closes; + HaveContent = false; + time(&Date); + + do + { + string Data; + if (ReadHeaderLines(Data) == false) + continue; + + if (Owner->Debug == true) + clog << Data; + + for (string::const_iterator I = Data.begin(); I < Data.end(); ++I) + { + string::const_iterator J = I; + for (; J != Data.end() && *J != '\n' && *J != '\r'; ++J); + if (HeaderLine(string(I,J)) == false) + return RUN_HEADERS_PARSE_ERROR; + I = J; + } + + // 100 Continue is a Nop... + if (Result == 100) + continue; + + // Tidy up the connection persistence state. + if (Encoding == Closes && HaveContent == true) + Persistent = false; + + return RUN_HEADERS_OK; + } + while (LoadNextResponse(false, File) == true); + + return RUN_HEADERS_IO_ERROR; +} + /*}}}*/ +// ServerState::HeaderLine - Process a header line /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool ServerState::HeaderLine(string Line) +{ + if (Line.empty() == true) + return true; + + string::size_type Pos = Line.find(' '); + if (Pos == string::npos || Pos+1 > Line.length()) + { + // Blah, some servers use "connection:closes", evil. + Pos = Line.find(':'); + if (Pos == string::npos || Pos + 2 > Line.length()) + return _error->Error(_("Bad header line")); + Pos++; + } + + // Parse off any trailing spaces between the : and the next word. + string::size_type Pos2 = Pos; + while (Pos2 < Line.length() && isspace(Line[Pos2]) != 0) + Pos2++; + + string Tag = string(Line,0,Pos); + string Val = string(Line,Pos2); + + if (stringcasecmp(Tag.c_str(),Tag.c_str()+4,"HTTP") == 0) + { + // Evil servers return no version + if (Line[4] == '/') + { + int const elements = sscanf(Line.c_str(),"HTTP/%3u.%3u %3u%359[^\n]",&Major,&Minor,&Result,Code); + if (elements == 3) + { + Code[0] = '\0'; + if (Owner->Debug == true) + clog << "HTTP server doesn't give Reason-Phrase for " << Result << std::endl; + } + else if (elements != 4) + return _error->Error(_("The HTTP server sent an invalid reply header")); + } + else + { + Major = 0; + Minor = 9; + if (sscanf(Line.c_str(),"HTTP %3u%359[^\n]",&Result,Code) != 2) + return _error->Error(_("The HTTP server sent an invalid reply header")); + } + + /* Check the HTTP response header to get the default persistence + state. */ + if (Major < 1) + Persistent = false; + else + { + if (Major == 1 && Minor == 0) + Persistent = false; + else + Persistent = true; + } + + return true; + } + + if (stringcasecmp(Tag,"Content-Length:") == 0) + { + if (Encoding == Closes) + Encoding = Stream; + HaveContent = true; + + // The length is already set from the Content-Range header + if (StartPos != 0) + return true; + + Size = strtoull(Val.c_str(), NULL, 10); + if (Size >= std::numeric_limits<unsigned long long>::max()) + return _error->Errno("HeaderLine", _("The HTTP server sent an invalid Content-Length header")); + else if (Size == 0) + HaveContent = false; + return true; + } + + if (stringcasecmp(Tag,"Content-Type:") == 0) + { + HaveContent = true; + return true; + } + + if (stringcasecmp(Tag,"Content-Range:") == 0) + { + HaveContent = true; + + // ยง14.16 says 'byte-range-resp-spec' should be a '*' in case of 416 + if (Result == 416 && sscanf(Val.c_str(), "bytes */%llu",&Size) == 1) + { + StartPos = 1; // ignore Content-Length, it would override Size + HaveContent = false; + } + else if (sscanf(Val.c_str(),"bytes %llu-%*u/%llu",&StartPos,&Size) != 2) + return _error->Error(_("The HTTP server sent an invalid Content-Range header")); + if ((unsigned long long)StartPos > Size) + return _error->Error(_("This HTTP server has broken range support")); + return true; + } + + if (stringcasecmp(Tag,"Transfer-Encoding:") == 0) + { + HaveContent = true; + if (stringcasecmp(Val,"chunked") == 0) + Encoding = Chunked; + return true; + } + + if (stringcasecmp(Tag,"Connection:") == 0) + { + if (stringcasecmp(Val,"close") == 0) + Persistent = false; + if (stringcasecmp(Val,"keep-alive") == 0) + Persistent = true; + return true; + } + + if (stringcasecmp(Tag,"Last-Modified:") == 0) + { + if (RFC1123StrToTime(Val.c_str(), Date) == false) + return _error->Error(_("Unknown date format")); + return true; + } + + if (stringcasecmp(Tag,"Location:") == 0) + { + Location = Val; + return true; + } + + return true; +} + /*}}}*/ +// ServerState::ServerState - Constructor /*{{{*/ +ServerState::ServerState(URI Srv, ServerMethod *Owner) : ServerName(Srv), TimeOut(120), Owner(Owner) +{ + Reset(); +} + /*}}}*/ + +bool ServerMethod::Configuration(string Message) /*{{{*/ +{ + return pkgAcqMethod::Configuration(Message); +} + /*}}}*/ + +// ServerMethod::DealWithHeaders - Handle the retrieved header data /*{{{*/ +// --------------------------------------------------------------------- +/* We look at the header data we got back from the server and decide what + to do. Returns DealWithHeadersResult (see http.h for details). + */ +ServerMethod::DealWithHeadersResult +ServerMethod::DealWithHeaders(FetchResult &Res) +{ + // Not Modified + if (Server->Result == 304) + { + unlink(Queue->DestFile.c_str()); + Res.IMSHit = true; + Res.LastModified = Queue->LastModified; + return IMS_HIT; + } + + /* Redirect + * + * Note that it is only OK for us to treat all redirection the same + * because we *always* use GET, not other HTTP methods. There are + * three redirection codes for which it is not appropriate that we + * redirect. Pass on those codes so the error handling kicks in. + */ + if (AllowRedirect + && (Server->Result > 300 && Server->Result < 400) + && (Server->Result != 300 // Multiple Choices + && Server->Result != 304 // Not Modified + && Server->Result != 306)) // (Not part of HTTP/1.1, reserved) + { + if (Server->Location.empty() == true); + else if (Server->Location[0] == '/' && Queue->Uri.empty() == false) + { + URI Uri = Queue->Uri; + if (Uri.Host.empty() == false) + NextURI = URI::SiteOnly(Uri); + else + NextURI.clear(); + NextURI.append(DeQuoteString(Server->Location)); + return TRY_AGAIN_OR_REDIRECT; + } + else + { + NextURI = DeQuoteString(Server->Location); + URI tmpURI = NextURI; + URI Uri = Queue->Uri; + // same protocol redirects are okay + if (tmpURI.Access == Uri.Access) + return TRY_AGAIN_OR_REDIRECT; + // as well as http to https + else if (Uri.Access == "http" && tmpURI.Access == "https") + return TRY_AGAIN_OR_REDIRECT; + } + /* else pass through for error message */ + } + // retry after an invalid range response without partial data + else if (Server->Result == 416) + { + struct stat SBuf; + if (stat(Queue->DestFile.c_str(),&SBuf) >= 0 && SBuf.st_size > 0) + { + if ((unsigned long long)SBuf.st_size == Server->Size) + { + // the file is completely downloaded, but was not moved + Server->StartPos = Server->Size; + Server->Result = 200; + Server->HaveContent = false; + } + else if (unlink(Queue->DestFile.c_str()) == 0) + { + NextURI = Queue->Uri; + return TRY_AGAIN_OR_REDIRECT; + } + } + } + + /* We have a reply we dont handle. This should indicate a perm server + failure */ + if (Server->Result < 200 || Server->Result >= 300) + { + char err[255]; + snprintf(err,sizeof(err)-1,"HttpError%i",Server->Result); + SetFailReason(err); + _error->Error("%u %s",Server->Result,Server->Code); + if (Server->HaveContent == true) + return ERROR_WITH_CONTENT_PAGE; + return ERROR_UNRECOVERABLE; + } + + // This is some sort of 2xx 'data follows' reply + Res.LastModified = Server->Date; + Res.Size = Server->Size; + + // Open the file + delete File; + File = new FileFd(Queue->DestFile,FileFd::WriteAny); + if (_error->PendingError() == true) + return ERROR_NOT_FROM_SERVER; + + FailFile = Queue->DestFile; + FailFile.c_str(); // Make sure we dont do a malloc in the signal handler + FailFd = File->Fd(); + FailTime = Server->Date; + + if (Server->InitHashes(*File) == false) + { + _error->Errno("read",_("Problem hashing file")); + return ERROR_NOT_FROM_SERVER; + } + if (Server->StartPos > 0) + Res.ResumePoint = Server->StartPos; + + SetNonBlock(File->Fd(),true); + return FILE_IS_OPEN; +} + /*}}}*/ +// ServerMethod::SigTerm - Handle a fatal signal /*{{{*/ +// --------------------------------------------------------------------- +/* This closes and timestamps the open file. This is necessary to get + resume behavoir on user abort */ +void ServerMethod::SigTerm(int) +{ + if (FailFd == -1) + _exit(100); + + struct timeval times[2]; + times[0].tv_sec = FailTime; + times[1].tv_sec = FailTime; + times[0].tv_usec = times[1].tv_usec = 0; + utimes(FailFile.c_str(), times); + close(FailFd); + + _exit(100); +} + /*}}}*/ +// ServerMethod::Fetch - Fetch an item /*{{{*/ +// --------------------------------------------------------------------- +/* This adds an item to the pipeline. We keep the pipeline at a fixed + depth. */ +bool ServerMethod::Fetch(FetchItem *) +{ + if (Server == 0) + return true; + + // Queue the requests + int Depth = -1; + for (FetchItem *I = Queue; I != 0 && Depth < (signed)PipelineDepth; + I = I->Next, Depth++) + { + // If pipelining is disabled, we only queue 1 request + if (Server->Pipeline == false && Depth >= 0) + break; + + // Make sure we stick with the same server + if (Server->Comp(I->Uri) == false) + break; + if (QueueBack == I) + { + QueueBack = I->Next; + SendReq(I); + continue; + } + } + + return true; +}; + /*}}}*/ +// ServerMethod::Loop - Main loop /*{{{*/ +int ServerMethod::Loop() +{ + typedef vector<string> StringVector; + typedef vector<string>::iterator StringVectorIterator; + map<string, StringVector> Redirected; + + signal(SIGTERM,SigTerm); + signal(SIGINT,SigTerm); + + Server = 0; + + int FailCounter = 0; + while (1) + { + // We have no commands, wait for some to arrive + if (Queue == 0) + { + if (WaitFd(STDIN_FILENO) == false) + return 0; + } + + /* Run messages, we can accept 0 (no message) if we didn't + do a WaitFd above.. Otherwise the FD is closed. */ + int Result = Run(true); + if (Result != -1 && (Result != 0 || Queue == 0)) + { + if(FailReason.empty() == false || + _config->FindB("Acquire::http::DependOnSTDIN", true) == true) + return 100; + else + return 0; + } + + if (Queue == 0) + continue; + + // Connect to the server + if (Server == 0 || Server->Comp(Queue->Uri) == false) + { + delete Server; + Server = CreateServerState(Queue->Uri); + } + /* If the server has explicitly said this is the last connection + then we pre-emptively shut down the pipeline and tear down + the connection. This will speed up HTTP/1.0 servers a tad + since we don't have to wait for the close sequence to + complete */ + if (Server->Persistent == false) + Server->Close(); + + // Reset the pipeline + if (Server->IsOpen() == false) + QueueBack = Queue; + + // Connnect to the host + if (Server->Open() == false) + { + Fail(true); + delete Server; + Server = 0; + continue; + } + + // Fill the pipeline. + Fetch(0); + + // Fetch the next URL header data from the server. + switch (Server->RunHeaders(File)) + { + case ServerState::RUN_HEADERS_OK: + break; + + // The header data is bad + case ServerState::RUN_HEADERS_PARSE_ERROR: + { + _error->Error(_("Bad header data")); + Fail(true); + RotateDNS(); + continue; + } + + // The server closed a connection during the header get.. + default: + case ServerState::RUN_HEADERS_IO_ERROR: + { + FailCounter++; + _error->Discard(); + Server->Close(); + Server->Pipeline = false; + + if (FailCounter >= 2) + { + Fail(_("Connection failed"),true); + FailCounter = 0; + } + + RotateDNS(); + continue; + } + }; + + // Decide what to do. + FetchResult Res; + Res.Filename = Queue->DestFile; + switch (DealWithHeaders(Res)) + { + // Ok, the file is Open + case FILE_IS_OPEN: + { + URIStart(Res); + + // Run the data + bool Result = true; + if (Server->HaveContent) + Result = Server->RunData(File); + + /* If the server is sending back sizeless responses then fill in + the size now */ + if (Res.Size == 0) + Res.Size = File->Size(); + + // Close the file, destroy the FD object and timestamp it + FailFd = -1; + delete File; + File = 0; + + // Timestamp + struct timeval times[2]; + times[0].tv_sec = times[1].tv_sec = Server->Date; + times[0].tv_usec = times[1].tv_usec = 0; + utimes(Queue->DestFile.c_str(), times); + + // Send status to APT + if (Result == true) + { + Res.TakeHashes(*Server->GetHashes()); + URIDone(Res); + } + else + { + if (Server->IsOpen() == false) + { + FailCounter++; + _error->Discard(); + Server->Close(); + + if (FailCounter >= 2) + { + Fail(_("Connection failed"),true); + FailCounter = 0; + } + + QueueBack = Queue; + } + else + Fail(true); + } + break; + } + + // IMS hit + case IMS_HIT: + { + URIDone(Res); + break; + } + + // Hard server error, not found or something + case ERROR_UNRECOVERABLE: + { + Fail(); + break; + } + + // Hard internal error, kill the connection and fail + case ERROR_NOT_FROM_SERVER: + { + delete File; + File = 0; + + Fail(); + RotateDNS(); + Server->Close(); + break; + } + + // We need to flush the data, the header is like a 404 w/ error text + case ERROR_WITH_CONTENT_PAGE: + { + Fail(); + + // Send to content to dev/null + File = new FileFd("/dev/null",FileFd::WriteExists); + Server->RunData(File); + delete File; + File = 0; + break; + } + + // Try again with a new URL + case TRY_AGAIN_OR_REDIRECT: + { + // Clear rest of response if there is content + if (Server->HaveContent) + { + File = new FileFd("/dev/null",FileFd::WriteExists); + Server->RunData(File); + delete File; + File = 0; + } + + /* Detect redirect loops. No more redirects are allowed + after the same URI is seen twice in a queue item. */ + StringVector &R = Redirected[Queue->DestFile]; + bool StopRedirects = false; + if (R.empty() == true) + R.push_back(Queue->Uri); + else if (R[0] == "STOP" || R.size() > 10) + StopRedirects = true; + else + { + for (StringVectorIterator I = R.begin(); I != R.end(); ++I) + if (Queue->Uri == *I) + { + R[0] = "STOP"; + break; + } + + R.push_back(Queue->Uri); + } + + if (StopRedirects == false) + Redirect(NextURI); + else + Fail(); + + break; + } + + default: + Fail(_("Internal error")); + break; + } + + FailCounter = 0; + } + + return 0; +} + /*}}}*/ diff --git a/methods/server.h b/methods/server.h new file mode 100644 index 000000000..2b81e6173 --- /dev/null +++ b/methods/server.h @@ -0,0 +1,144 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + Classes dealing with the abstraction of talking to a end via a text + protocol like HTTP (which is used by the http and https methods) + + ##################################################################### */ + /*}}}*/ + +#ifndef APT_SERVER_H +#define APT_SERVER_H + +#include <apt-pkg/strutl.h> + +#include <string> + +using std::cout; +using std::endl; + +class Hashes; +class ServerMethod; +class FileFd; + +struct ServerState +{ + // This is the last parsed Header Line + unsigned int Major; + unsigned int Minor; + unsigned int Result; + char Code[360]; + + // These are some statistics from the last parsed header lines + unsigned long long Size; + signed long long StartPos; + time_t Date; + bool HaveContent; + enum {Chunked,Stream,Closes} Encoding; + enum {Header, Data} State; + bool Persistent; + std::string Location; + + // This is a Persistent attribute of the server itself. + bool Pipeline; + URI ServerName; + URI Proxy; + unsigned long TimeOut; + + protected: + ServerMethod *Owner; + + virtual bool ReadHeaderLines(std::string &Data) = 0; + virtual bool LoadNextResponse(bool const ToFile, FileFd * const File) = 0; + + public: + bool HeaderLine(std::string Line); + + /** \brief Result of the header acquire */ + enum RunHeadersResult { + /** \brief Header ok */ + RUN_HEADERS_OK, + /** \brief IO error while retrieving */ + RUN_HEADERS_IO_ERROR, + /** \brief Parse error after retrieving */ + RUN_HEADERS_PARSE_ERROR, + }; + /** \brief Get the headers before the data */ + RunHeadersResult RunHeaders(FileFd * const File); + + bool Comp(URI Other) const {return Other.Host == ServerName.Host && Other.Port == ServerName.Port;}; + virtual void Reset() {Major = 0; Minor = 0; Result = 0; Code[0] = '\0'; Size = 0; + StartPos = 0; Encoding = Closes; time(&Date); HaveContent = false; + State = Header; Persistent = false; Pipeline = true;}; + virtual bool WriteResponse(std::string const &Data) = 0; + + /** \brief Transfer the data from the socket */ + virtual bool RunData(FileFd * const File) = 0; + + virtual bool Open() = 0; + virtual bool IsOpen() = 0; + virtual bool Close() = 0; + virtual bool InitHashes(FileFd &File) = 0; + virtual Hashes * GetHashes() = 0; + virtual bool Die(FileFd &File) = 0; + virtual bool Flush(FileFd * const File) = 0; + virtual bool Go(bool ToFile, FileFd * const File) = 0; + + ServerState(URI Srv, ServerMethod *Owner); + virtual ~ServerState() {}; +}; + +class ServerMethod : public pkgAcqMethod +{ + protected: + virtual bool Fetch(FetchItem *); + + ServerState *Server; + std::string NextURI; + FileFd *File; + + unsigned long PipelineDepth; + bool AllowRedirect; + + public: + bool Debug; + + /** \brief Result of the header parsing */ + enum DealWithHeadersResult { + /** \brief The file is open and ready */ + FILE_IS_OPEN, + /** \brief We got a IMS hit, the file has not changed */ + IMS_HIT, + /** \brief The server reported a unrecoverable error */ + ERROR_UNRECOVERABLE, + /** \brief The server reported a error with a error content page */ + ERROR_WITH_CONTENT_PAGE, + /** \brief An error on the client side */ + ERROR_NOT_FROM_SERVER, + /** \brief A redirect or retry request */ + TRY_AGAIN_OR_REDIRECT + }; + /** \brief Handle the retrieved header data */ + DealWithHeadersResult DealWithHeaders(FetchResult &Res); + + // In the event of a fatal signal this file will be closed and timestamped. + static std::string FailFile; + static int FailFd; + static time_t FailTime; + static void SigTerm(int); + + virtual bool Configuration(std::string Message); + virtual bool Flush() { return Server->Flush(File); }; + + int Loop(); + + virtual void SendReq(FetchItem *Itm) = 0; + virtual ServerState * CreateServerState(URI uri) = 0; + virtual void RotateDNS() = 0; + + ServerMethod(const char *Ver,unsigned long Flags = 0) : pkgAcqMethod(Ver, Flags), Server(NULL), File(NULL), PipelineDepth(0), AllowRedirect(false), Debug(false) {}; + virtual ~ServerMethod() {}; +}; + +#endif |