- more size_t fixes (Ralf)
[apt.git] / methods / rsh.cc
1 // -*- mode: c++; mode: fold -*-
2 // Description                                                          /*{{{*/
3 // $Id: rsh.cc,v 1.6 2003/02/10 07:34:41 doogie Exp $
4 /* ######################################################################
5
6    RSH method - Transfer files via rsh compatible program
7
8    Written by Ben Collins <bcollins@debian.org>, Copyright (c) 2000
9    Licensed under the GNU General Public License v2 [no exception clauses]
10
11    ##################################################################### */
12                                                                         /*}}}*/
13 // Include Files                                                        /*{{{*/
14 #include "rsh.h"
15 #include <apt-pkg/error.h>
16
17 #include <sys/stat.h>
18 #include <sys/time.h>
19 #include <utime.h>
20 #include <unistd.h>
21 #include <signal.h>
22 #include <stdio.h>
23 #include <errno.h>
24 #include <stdarg.h>
25
26 // CNC:2003-02-20 - Moved header to fix compilation error when
27 //                  --disable-nls is used.
28 #include <apti18n.h>
29                                                                         /*}}}*/
30
31 const char *Prog;
32 unsigned long TimeOut = 120;
33 Configuration::Item const *RshOptions = 0;
34 time_t RSHMethod::FailTime = 0;
35 string RSHMethod::FailFile;
36 int RSHMethod::FailFd = -1;
37
38 // RSHConn::RSHConn - Constructor                                       /*{{{*/
39 // ---------------------------------------------------------------------
40 /* */
41 RSHConn::RSHConn(URI Srv) : Len(0), WriteFd(-1), ReadFd(-1),
42                             ServerName(Srv), Process(-1) {}
43                                                                         /*}}}*/
44 // RSHConn::RSHConn - Destructor                                        /*{{{*/
45 // ---------------------------------------------------------------------
46 /* */
47 RSHConn::~RSHConn()
48 {
49    Close();
50 }
51                                                                         /*}}}*/
52 // RSHConn::Close - Forcibly terminate the connection                   /*{{{*/
53 // ---------------------------------------------------------------------
54 /* Often this is called when things have gone wrong to indicate that the
55    connection is no longer usable. */
56 void RSHConn::Close()
57 {
58    if (Process == -1)
59       return;
60    
61    close(WriteFd);
62    close(ReadFd);
63    kill(Process,SIGINT);
64    ExecWait(Process,"",true);
65    WriteFd = -1;
66    ReadFd = -1;
67    Process = -1;
68 }
69                                                                         /*}}}*/
70 // RSHConn::Open - Connect to a host                                    /*{{{*/
71 // ---------------------------------------------------------------------
72 /* */
73 bool RSHConn::Open()
74 {
75    // Use the already open connection if possible.
76    if (Process != -1)
77       return true;
78
79    if (Connect(ServerName.Host,ServerName.User) == false)
80       return false;
81
82    return true;
83 }
84                                                                         /*}}}*/
85 // RSHConn::Connect - Fire up rsh and connect                           /*{{{*/
86 // ---------------------------------------------------------------------
87 /* */
88 bool RSHConn::Connect(string Host, string User)
89 {
90    // Create the pipes
91    int Pipes[4] = {-1,-1,-1,-1};
92    if (pipe(Pipes) != 0 || pipe(Pipes+2) != 0)
93    {
94       _error->Errno("pipe",_("Failed to create IPC pipe to subprocess"));
95       for (int I = 0; I != 4; I++)
96          close(Pipes[I]);
97       return false;
98    }
99    for (int I = 0; I != 4; I++)
100       SetCloseExec(Pipes[I],true);
101    
102    Process = ExecFork();
103
104    // The child
105    if (Process == 0)
106    {
107       const char *Args[400];
108       unsigned int i = 0;
109
110       dup2(Pipes[1],STDOUT_FILENO);
111       dup2(Pipes[2],STDIN_FILENO);
112
113       // Probably should do
114       // dup2(open("/dev/null",O_RDONLY),STDERR_FILENO);
115
116       // Insert user-supplied command line options
117       Configuration::Item const *Opts = RshOptions;
118       if (Opts != 0)
119       {
120          Opts = Opts->Child;
121          for (; Opts != 0; Opts = Opts->Next)
122          {
123             if (Opts->Value.empty() == true)
124                continue;
125             Args[i++] = Opts->Value.c_str();
126          }
127       }
128
129       Args[i++] = Prog;
130       if (User.empty() == false) {
131          Args[i++] = "-l";
132          Args[i++] = User.c_str();
133       }
134       if (Host.empty() == false) {
135          Args[i++] = Host.c_str();
136       }
137       Args[i++] = "/bin/sh";
138       Args[i] = 0;
139       execvp(Args[0],(char **)Args);
140       exit(100);
141    }
142
143    ReadFd = Pipes[0];
144    WriteFd = Pipes[3];
145    SetNonBlock(Pipes[0],true);
146    SetNonBlock(Pipes[3],true);
147    close(Pipes[1]);
148    close(Pipes[2]);
149    
150    return true;
151 }
152                                                                         /*}}}*/
153 // RSHConn::ReadLine - Very simple buffered read with timeout           /*{{{*/
154 // ---------------------------------------------------------------------
155 /* */
156 bool RSHConn::ReadLine(string &Text)
157 {
158    if (Process == -1 || ReadFd == -1)
159       return false;
160    
161    // Suck in a line
162    while (Len < sizeof(Buffer))
163    {
164       // Scan the buffer for a new line
165       for (size_t I = 0; I != Len; I++)
166       {
167          // Escape some special chars
168          if (Buffer[I] == 0)
169             Buffer[I] = '?';
170
171          // End of line?
172          if (Buffer[I] != '\n')
173             continue;
174
175          I++;
176          Text = string(Buffer,I);
177          memmove(Buffer,Buffer+I,Len - I);
178          Len -= I;
179          return true;
180       }
181
182       // Wait for some data..
183       if (WaitFd(ReadFd,false,TimeOut) == false)
184       {
185          Close();
186          return _error->Error(_("Connection timeout"));
187       }
188
189       // Suck it back
190       ssize_t Res = read(ReadFd,Buffer + Len,sizeof(Buffer) - Len);
191       if (Res <= 0)
192       {
193          _error->Errno("read",_("Read error"));
194          Close();
195          return false;
196       }
197       Len += Res;
198    }
199
200    return _error->Error(_("A response overflowed the buffer."));
201 }
202                                                                         /*}}}*/
203 // RSHConn::WriteMsg - Send a message with optional remote sync.        /*{{{*/
204 // ---------------------------------------------------------------------
205 /* The remote sync flag appends a || echo which will insert blank line
206    once the command completes. */
207 bool RSHConn::WriteMsg(string &Text,bool Sync,const char *Fmt,...)
208 {
209    va_list args;
210    va_start(args,Fmt);
211
212    // sprintf the description
213    char S[512];
214    vsnprintf(S,sizeof(S) - 4,Fmt,args);
215    if (Sync == true)
216       strcat(S," 2> /dev/null || echo\n");
217    else
218       strcat(S," 2> /dev/null\n");
219
220    // Send it off
221    size_t Len = strlen(S);
222    unsigned long Start = 0;
223    while (Len != 0)
224    {
225       if (WaitFd(WriteFd,true,TimeOut) == false)
226       {
227          
228          Close();
229          return _error->Error(_("Connection timeout"));
230       }      
231       
232       ssize_t Res = write(WriteFd,S + Start,Len);
233       if (Res <= 0)
234       {
235          _error->Errno("write",_("Write Error"));
236          Close();
237          return false;
238       }
239
240       Len -= Res;
241       Start += Res;
242    }
243
244    if (Sync == true)
245       return ReadLine(Text);
246    return true;
247 }
248                                                                         /*}}}*/
249 // RSHConn::Size - Return the size of the file                          /*{{{*/
250 // ---------------------------------------------------------------------
251 /* Right now for successfull transfer the file size must be known in 
252    advance. */
253 bool RSHConn::Size(const char *Path,size_t &Size)
254 {
255    // Query the size
256    string Msg;
257    Size = 0;
258
259    if (WriteMsg(Msg,true,"find %s -follow -printf '%%s\\n'",Path) == false)
260       return false;
261    
262    // FIXME: Sense if the bad reply is due to a File Not Found. 
263    
264    char *End;
265    Size = strtoul(Msg.c_str(),&End,10);
266    if (End == Msg.c_str())
267       return _error->Error(_("File Not Found"));
268    return true;
269 }
270                                                                         /*}}}*/
271 // RSHConn::ModTime - Get the modification time in UTC                  /*{{{*/
272 // ---------------------------------------------------------------------
273 /* */
274 bool RSHConn::ModTime(const char *Path, time_t &Time)
275 {
276    Time = time(&Time);
277    // Query the mod time
278    string Msg;
279
280    if (WriteMsg(Msg,true,"TZ=UTC find %s -follow -printf '%%TY%%Tm%%Td%%TH%%TM%%TS\\n'",Path) == false)
281       return false;
282
283    // Parse it
284    StrToTime(Msg,Time);
285    return true;
286 }
287                                                                         /*}}}*/
288 // RSHConn::Get - Get a file                                            /*{{{*/
289 // ---------------------------------------------------------------------
290 /* */
291 bool RSHConn::Get(const char *Path,FileFd &To,unsigned long Resume,
292                   Hashes &Hash,bool &Missing, unsigned long Size)
293 {
294    Missing = false;
295
296    // Round to a 2048 byte block
297    Resume = Resume - (Resume % 2048);
298
299    if (To.Truncate(Resume) == false)
300       return false;
301    if (To.Seek(0) == false)
302       return false;
303
304    if (Resume != 0) {
305       if (Hash.AddFD(To.Fd(),Resume) == false) {
306          _error->Errno("read",_("Problem hashing file"));
307          return false;
308       }
309    }
310    
311    // FIXME: Detect file-not openable type errors.
312    string Jnk;
313    if (WriteMsg(Jnk,false,"dd if=%s bs=2048 skip=%u", Path, Resume / 2048) == false)
314       return false;
315
316    // Copy loop
317    unsigned int MyLen = Resume;
318    unsigned char Buffer[4096];
319    while (MyLen < Size)
320    {
321       // Wait for some data..
322       if (WaitFd(ReadFd,false,TimeOut) == false)
323       {
324          Close();
325          return _error->Error(_("Data socket timed out"));
326       }
327
328       // Read the data..
329       ssize_t Res = read(ReadFd,Buffer,sizeof(Buffer));
330       if (Res == 0)
331       {
332          Close();
333          return _error->Error(_("Connection closed prematurely"));
334       }
335       
336       if (Res < 0)
337       {
338          if (errno == EAGAIN)
339             continue;
340          break;
341       }
342       MyLen += Res;
343
344       Hash.Add(Buffer,Res);
345       if (To.Write(Buffer,Res) == false)
346       {
347          Close();
348          return false;
349       }
350    }
351
352    return true;
353 }
354                                                                         /*}}}*/
355
356 // RSHMethod::RSHMethod - Constructor                                   /*{{{*/
357 // ---------------------------------------------------------------------
358 /* */
359 RSHMethod::RSHMethod() : pkgAcqMethod("1.0",SendConfig)
360 {
361    signal(SIGTERM,SigTerm);
362    signal(SIGINT,SigTerm);
363    Server = 0;
364    FailFd = -1;
365 }
366                                                                         /*}}}*/
367 // RSHMethod::Configuration - Handle a configuration message            /*{{{*/
368 // ---------------------------------------------------------------------
369 bool RSHMethod::Configuration(string Message)
370 {
371    char ProgStr[100];
372   
373    if (pkgAcqMethod::Configuration(Message) == false)
374       return false;
375
376    snprintf(ProgStr, sizeof ProgStr, "Acquire::%s::Timeout", Prog);
377    TimeOut = _config->FindI(ProgStr,TimeOut);
378    snprintf(ProgStr, sizeof ProgStr, "Acquire::%s::Options", Prog);
379    RshOptions = _config->Tree(ProgStr);
380
381    return true;
382 }
383                                                                         /*}}}*/
384 // RSHMethod::SigTerm - Clean up and timestamp the files on exit        /*{{{*/
385 // ---------------------------------------------------------------------
386 /* */
387 void RSHMethod::SigTerm(int sig)
388 {
389    if (FailFd == -1)
390       _exit(100);
391    close(FailFd);
392
393    // Timestamp
394    struct utimbuf UBuf;
395    UBuf.actime = FailTime;
396    UBuf.modtime = FailTime;
397    utime(FailFile.c_str(),&UBuf);
398
399    _exit(100);
400 }
401                                                                         /*}}}*/
402 // RSHMethod::Fetch - Fetch a URI                                       /*{{{*/
403 // ---------------------------------------------------------------------
404 /* */
405 bool RSHMethod::Fetch(FetchItem *Itm)
406 {
407    URI Get = Itm->Uri;
408    const char *File = Get.Path.c_str();
409    FetchResult Res;
410    Res.Filename = Itm->DestFile;
411    Res.IMSHit = false;
412
413    // Connect to the server
414    if (Server == 0 || Server->Comp(Get) == false) {
415       delete Server;
416       Server = new RSHConn(Get);
417    }
418
419    // Could not connect is a transient error..
420    if (Server->Open() == false) {
421       Server->Close();
422       Fail(true);
423       return true;
424    }
425
426    // We say this mainly because the pause here is for the
427    // ssh connection that is still going
428    Status(_("Connecting to %s"), Get.Host.c_str());
429
430    // Get the files information
431    size_t Size;
432    if (Server->Size(File,Size) == false ||
433        Server->ModTime(File,FailTime) == false)
434    {
435       //Fail(true);
436       //_error->Error(_("File Not Found")); // Will be handled by Size
437       return false;
438    }
439    Res.Size = Size;
440
441    // See if it is an IMS hit
442    if (Itm->LastModified == FailTime) {
443       Res.Size = 0;
444       Res.IMSHit = true;
445       URIDone(Res);
446       return true;
447    }
448
449    // See if the file exists
450    struct stat Buf;
451    if (stat(Itm->DestFile.c_str(),&Buf) == 0) {
452       if (Size == (unsigned)Buf.st_size && FailTime == Buf.st_mtime) {
453          Res.Size = Buf.st_size;
454          Res.LastModified = Buf.st_mtime;
455          Res.ResumePoint = Buf.st_size;
456          URIDone(Res);
457          return true;
458       }
459
460       // Resume?
461       if (FailTime == Buf.st_mtime && Size > (unsigned)Buf.st_size)
462          Res.ResumePoint = Buf.st_size;
463    }
464
465    // Open the file
466    Hashes Hash;
467    {
468       FileFd Fd(Itm->DestFile,FileFd::WriteAny);
469       if (_error->PendingError() == true)
470          return false;
471       
472       URIStart(Res);
473
474       FailFile = Itm->DestFile;
475       FailFile.c_str();   // Make sure we dont do a malloc in the signal handler
476       FailFd = Fd.Fd();
477
478       bool Missing;
479       if (Server->Get(File,Fd,Res.ResumePoint,Hash,Missing,Res.Size) == false)
480       {
481          Fd.Close();
482
483          // Timestamp
484          struct utimbuf UBuf;
485          UBuf.actime = FailTime;
486          UBuf.modtime = FailTime;
487          utime(FailFile.c_str(),&UBuf);
488
489          // If the file is missing we hard fail otherwise transient fail
490          if (Missing == true)
491             return false;
492          Fail(true);
493          return true;
494       }
495
496       Res.Size = Fd.Size();
497    }
498
499    Res.LastModified = FailTime;
500    Res.TakeHashes(Hash);
501
502    // Timestamp
503    struct utimbuf UBuf;
504    UBuf.actime = FailTime;
505    UBuf.modtime = FailTime;
506    utime(Queue->DestFile.c_str(),&UBuf);
507    FailFd = -1;
508
509    URIDone(Res);
510
511    return true;
512 }
513                                                                         /*}}}*/
514
515 int main(int argc, const char *argv[])
516 {
517    RSHMethod Mth;
518    Prog = strrchr(argv[0],'/');
519    Prog++;
520    return Mth.Run();
521 }