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