1 /*
2  * lftp - file transfer program
3  *
4  * Copyright (c) 1996-2016 by Alexander V. Lukyanov (lav@yars.free.net)
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include <config.h>
21 #include <stdlib.h>
22 #include <errno.h>
23 #include <assert.h>
24 #include "Job.h"
25 #include "misc.h"
26 
27 xlist_head<Job> Job::all_jobs;
28 #define waiting_num waiting.count()
29 
Job()30 Job::Job()
31    : all_jobs_node(this), children_jobs_node(this)
32 {
33    all_jobs.add(all_jobs_node);
34    parent=0;
35    jobno=-1;
36    fg=false;
37 }
38 
SetParent(Job * j)39 void Job::SetParent(Job *j)
40 {
41    if(children_jobs_node.listed())
42       children_jobs_node.remove();
43    parent=j;
44    if(j)
45       j->children_jobs.add(children_jobs_node);
46 }
47 
AllocJobno()48 void  Job::AllocJobno()
49 {
50    jobno=0;
51    xlist_for_each(Job,all_jobs,node,scan)
52       if(scan!=this && scan->jobno>=jobno)
53 	 jobno=scan->jobno+1;
54 }
55 
PrepareToDie()56 void Job::PrepareToDie()
57 {
58    // reparent or kill children (hm, that's sadistic)
59    xlist_for_each_safe(Job,children_jobs,child_node,child,next) {
60       child_node->remove();
61       if(child->jobno!=-1 && this->parent) {
62 	 child->parent=this->parent;
63 	 this->parent->children_jobs.add(child_node);
64       } else {
65 	 child->parent=0;
66 	 child->DeleteLater();
67       }
68    }
69    // if parent waits for the job, make it stop
70    if(parent)
71       parent->RemoveWaiting(this);
72    fg_data=0;
73    waiting.unset();
74    if(children_jobs_node.listed())
75       children_jobs_node.remove();
76    all_jobs_node.remove();
77 }
78 
~Job()79 Job::~Job()
80 {
81    assert(!all_jobs_node.listed());
82    assert(!children_jobs_node.listed());
83 }
84 
FindJob(int n)85 Job *Job::FindJob(int n)
86 {
87    xlist_for_each(Job,all_jobs,node,scan)
88    {
89       if(scan->jobno==n)
90 	 return scan;
91    }
92    return 0;
93 }
94 
FindWhoWaitsFor(Job * j)95 Job *Job::FindWhoWaitsFor(Job *j)
96 {
97    xlist_for_each(Job,all_jobs,node,scan)
98    {
99       if(scan->WaitsFor(j))
100 	 return scan;
101    }
102    return 0;
103 }
104 
WaitsFor(Job * j)105 bool Job::WaitsFor(Job *j)
106 {
107    return waiting.search(j)>=0;
108 }
109 
FindDoneAwaitedJob()110 Job *Job::FindDoneAwaitedJob()
111 {
112    for(int i=0; i<waiting_num; i++)
113    {
114       if(waiting[i]->Done())
115 	 return waiting[i];
116    }
117    return 0;
118 }
119 
WaitForAllChildren()120 void Job::WaitForAllChildren()
121 {
122    xlist_for_each(Job,children_jobs,node,scan)
123       AddWaiting(scan);
124 }
AllWaitingFg()125 void Job::AllWaitingFg()
126 {
127    for(int i=0; i<waiting_num; i++)
128       waiting[i]->Fg();
129 }
130 
ReplaceWaiting(Job * from,Job * to)131 void Job::ReplaceWaiting(Job *from,Job *to)
132 {
133    int i=waiting.search(from);
134    if(i>=0)
135       waiting[i]=to;
136 }
137 
AddWaiting(Job * j)138 void Job::AddWaiting(Job *j)
139 {
140    if(j==0 || this->WaitsFor(j))
141       return;
142    assert(FindWhoWaitsFor(j)==0);
143    j->SetParentFg(this);
144    waiting.append(j);
145 }
RemoveWaiting(const Job * j)146 void Job::RemoveWaiting(const Job *j)
147 {
148    int i=waiting.search(const_cast<Job*>(j));
149    if(i>=0)
150       waiting.remove(i);
151 }
152 
153 class KilledJob : public Job
154 {
155 public:
Do()156    int	 Do() { return STALL; }
Done()157    int	 Done() { return 1; }
ExitCode()158    int	 ExitCode() { return 255; }
159 };
160 
Kill(Job * j)161 void Job::Kill(Job *j)
162 {
163    if(j->AcceptSig(SIGTERM)!=WANTDIE)
164       return;
165    if(j->parent && j->parent->WaitsFor(j))
166    {
167       // someone waits for termination of this job, so
168       // we have to simulate normal death...
169       Job *r=new KilledJob();
170       r->parent=j->parent;
171       j->parent->children_jobs.add(r->children_jobs_node);
172       j->children_jobs_node.remove();
173       r->cmdline.set(j->cmdline);
174       r->waiting.move_here(j->waiting);
175       j->parent->ReplaceWaiting(j,r);
176    }
177    assert(FindWhoWaitsFor(j)==0);
178    j->DeleteLater();
179 }
180 
Kill(int n)181 void Job::Kill(int n)
182 {
183    Job *j=FindJob(n);
184    if(j)
185       Kill(j);
186 }
187 
KillAll()188 void Job::KillAll()
189 {
190    // Job::Kill may remove more than a single job from all_jobs list,
191    // collect list of jobs beforehand.
192    xarray<Job*> to_kill;
193    xlist_for_each(Job,all_jobs,node,scan)
194       if(scan->jobno>=0)
195 	 to_kill.append(scan);
196    for(int i=0; i<to_kill.count(); i++)
197       Kill(to_kill[i]);
198    CollectGarbage();
199 }
Cleanup()200 void Job::Cleanup()
201 {
202    xarray<Job*> to_kill;
203    xlist_for_each(Job,all_jobs,node,scan)
204       to_kill.append(scan);
205    for(int i=0; i<to_kill.count(); i++)
206       Kill(to_kill[i]);
207    CollectGarbage();
208 }
209 
SendSig(int n,int sig)210 void  Job::SendSig(int n,int sig)
211 {
212    Job *j=FindJob(n);
213    if(j)
214    {
215       int res=j->AcceptSig(sig);
216       if(res==WANTDIE)
217 	 Kill(n);
218    }
219 }
220 
NumberOfJobs()221 int Job::NumberOfJobs()
222 {
223    int count=0;
224    xlist_for_each(Job,all_jobs,node,scan)
225       if(!scan->Done())
226 	 count++;
227    return count;
228 }
NumberOfChildrenJobs()229 int Job::NumberOfChildrenJobs()
230 {
231    int count=0;
232    xlist_for_each(Job,children_jobs,node,scan)
233       if(!scan->Done())
234 	 count++;
235    return count;
236 }
237 
jobno_compare(Job * const * a,Job * const * b)238 static int jobno_compare(Job *const*a,Job *const*b)
239 {
240    return (*a)->jobno-(*b)->jobno;
241 }
242 
SortJobs()243 void  Job::SortJobs()
244 {
245    xarray<Job*> arr;
246 
247    xlist_for_each_safe(Job,all_jobs,node,scan,next) {
248       arr.append(scan);
249       node->remove();
250    }
251    arr.qsort(jobno_compare);
252 
253    int count=arr.count();
254    while(count--)
255       all_jobs.add(arr[count]->all_jobs_node);
256 
257    xlist_for_each(Job,all_jobs,node,scan_waiting)
258       scan_waiting->waiting.qsort(jobno_compare);
259 }
260 
261 static xstring print_buf("");
PrintJobTitle(int indent,const char * suffix)262 void Job::PrintJobTitle(int indent,const char *suffix)
263 {
264    print_buf.truncate();
265    printf("%s",FormatJobTitle(print_buf,indent,suffix).get());
266 }
ListOneJob(int verbose,int indent,const char * suffix)267 void Job::ListOneJob(int verbose,int indent,const char *suffix)
268 {
269    print_buf.truncate();
270    printf("%s",FormatOneJob(print_buf,verbose,indent,suffix).get());
271 }
ListOneJobRecursively(int verbose,int indent)272 void Job::ListOneJobRecursively(int verbose,int indent)
273 {
274    print_buf.truncate();
275    printf("%s",FormatOneJobRecursively(print_buf,verbose,indent).get());
276 }
PrintStatus(int v,const char * prefix)277 void Job::PrintStatus(int v,const char *prefix)
278 {
279    print_buf.truncate();
280    printf("%s",FormatStatus(print_buf,v,prefix).get());
281 }
282 
ListDoneJobs()283 void  Job::ListDoneJobs()
284 {
285    SortJobs();
286 
287    FILE *f=stdout;
288    xlist_for_each(Job,all_jobs,node,scan)
289    {
290       if(scan->jobno>=0 && (scan->parent==this || scan->parent==0)
291          && scan->Done())
292       {
293 	 fprintf(f,_("[%d] Done (%s)"),scan->jobno,scan->GetCmdLine().get());
294 	 const char *this_url=this->GetConnectURL();
295 	 this_url=alloca_strdup(this_url); // save it from overwriting.
296 	 const char *that_url=scan->GetConnectURL();
297 	 if(this_url && that_url && strcmp(this_url,that_url))
298 	    fprintf(f," (wd: %s)",that_url);
299 	 fprintf(f,"\n");
300 	 scan->PrintStatus(0);
301       }
302    }
303 }
304 
FormatJobTitle(xstring & s,int indent,const char * suffix)305 xstring& Job::FormatJobTitle(xstring& s,int indent,const char *suffix)
306 {
307    if(jobno<0 && !cmdline)
308       return s;
309    s.append_padding(indent,' ');
310    if(jobno>=0)
311       s.appendf("[%d] ",jobno);
312    s.append(GetCmdLine());
313    if(suffix) {
314       s.append(' ');
315       s.append(suffix);
316    }
317    if(waiting.count()>0) {
318       size_t len=s.length();
319       FormatShortStatus(s.append(" -- "));
320       if(s.length()<=len+4)
321 	 s.truncate(len);
322    }
323    s.append('\n');
324    return s;
325 }
326 
FormatOneJob(xstring & s,int verbose,int indent,const char * suffix)327 xstring& Job::FormatOneJob(xstring& s,int verbose,int indent,const char *suffix)
328 {
329    FormatJobTitle(s,indent,suffix);
330    FormatStatus(s,verbose);
331    for(int i=0; i<waiting_num; i++)
332    {
333       if(waiting[i]->jobno<0 && waiting[i]!=this && !waiting[i]->cmdline)
334 	 waiting[i]->FormatOneJob(s,verbose,indent+1,"");
335    }
336    return s;
337 }
338 
FormatOneJobRecursively(xstring & s,int verbose,int indent)339 xstring& Job::FormatOneJobRecursively(xstring& s,int verbose,int indent)
340 {
341    FormatJobTitle(s,indent,"");
342    FormatStatus(s,verbose);
343    FormatJobs(s,verbose,indent+1);
344    return s;
345 }
346 
FormatJobs(xstring & s,int verbose,int indent)347 xstring& Job::FormatJobs(xstring& s,int verbose,int indent)
348 {
349    if(indent==0)
350       SortJobs();
351 
352    // list the foreground job first.
353    for(int i=0; i<waiting_num; i++)
354    {
355       if(waiting[i]!=this && waiting[i]->parent==this)
356 	 waiting[i]->FormatOneJobRecursively(s,verbose,indent);
357    }
358 
359    xlist_for_each(Job,children_jobs,node,scan)
360       if(!scan->Done() && !this->WaitsFor(scan))
361 	 scan->FormatOneJobRecursively(s,verbose,indent);
362 
363    return s;
364 }
365 
BuryDoneJobs()366 void  Job::BuryDoneJobs()
367 {
368    xlist_for_each_safe(Job,all_jobs,node,scan,next)
369    {
370       if((scan->parent==this || scan->parent==0) && scan->jobno>=0
371 		  && scan->Done())
372 	 scan->DeleteLater();
373    }
374    CollectGarbage();
375 }
376 
fprintf(FILE * file,const char * fmt,...)377 void Job::fprintf(FILE *file,const char *fmt,...)
378 {
379    va_list v;
380    va_start(v,fmt);
381    vfprintf(file,fmt,v);
382    va_end(v);
383 }
384 
printf(const char * fmt,...)385 void Job::printf(const char *fmt,...)
386 {
387    va_list v;
388    va_start(v,fmt);
389    vfprintf(stdout,fmt,v);
390    va_end(v);
391 }
392 
eprintf(const char * fmt,...)393 void Job::eprintf(const char *fmt,...)
394 {
395    va_list v;
396    va_start(v,fmt);
397    vfprintf(stderr,fmt,v);
398    va_end(v);
399 }
400 
401 
FormatStatus(xstring & s,int v,const char * prefix)402 xstring& SessionJob::FormatStatus(xstring& s,int v,const char *prefix)
403 {
404    if(v<2 || !session)
405       return s;
406    const char *url=session->GetConnectURL();
407    if(url && *url) {
408       s.append(prefix);
409       s.append(url);
410       s.append('\n');
411    }
412    const char *last_dc=session->GetLastDisconnectCause();
413    if(last_dc && !session->IsConnected()) {
414       s.append(prefix);
415       s.appendf("Last disconnect cause: %s\n",last_dc);
416    }
417    return s;
418 }
419 
Fg()420 void SessionJob::Fg()
421 {
422    if(session)
423       session->SetPriority(1);
424    Job::Fg();
425 }
Bg()426 void SessionJob::Bg()
427 {
428    Job::Bg();
429    if(session)
430       session->SetPriority(0);
431 }
432 
vfprintf(FILE * file,const char * fmt,va_list v)433 void Job::vfprintf(FILE *file,const char *fmt,va_list v)
434 {
435    if(file!=stdout && file!=stderr)
436    {
437       ::vfprintf(file,fmt,v);
438       return;
439    }
440    if(parent)
441       parent->vfprintf(file,fmt,v);
442    else
443       top_vfprintf(file,fmt,v);
444 }
top_vfprintf(FILE * file,const char * fmt,va_list v)445 void Job::top_vfprintf(FILE *file,const char *fmt,va_list v)
446 {
447    ::vfprintf(file,fmt,v);
448 }
449 
perror(const char * f)450 void Job::perror(const char *f)
451 {
452    if(f)
453       eprintf("%s: %s\n",f,strerror(errno));
454    else
455       eprintf("%s\n",strerror(errno));
456 }
457 
puts(const char * s)458 void Job::puts(const char *s)
459 {
460    printf("%s\n",s);
461 }
462 
Bg()463 void Job::Bg()
464 {
465    if(!fg)
466       return;
467    fg=false;
468    for(int i=0; i<waiting_num; i++)
469    {
470       if(waiting[i]!=this)
471 	 waiting[i]->Bg();
472    }
473    if(fg_data)
474       fg_data->Bg();
475 }
Fg()476 void Job::Fg()
477 {
478    Resume();
479    if(fg)
480       return;
481    fg=true;
482    if(fg_data)
483       fg_data->Fg();
484    for(int i=0; i<waiting_num; i++)
485    {
486       if(waiting[i]!=this)
487 	 waiting[i]->Fg();
488    }
489 }
490 
AcceptSig(int s)491 int Job::AcceptSig(int s)
492 {
493    for(int i=0; i<waiting_num; i++)
494    {
495       if(waiting[i]==this)
496 	 continue;
497       if(waiting[i]->AcceptSig(s)==WANTDIE)
498       {
499 	 while(waiting[i]->waiting_num>0)
500 	 {
501 	    Job *new_waiting=waiting[i]->waiting[0];
502 	    waiting[i]->RemoveWaiting(new_waiting);
503 	    AddWaiting(new_waiting);
504 	 }
505 	 Job *j=waiting[i];
506 	 RemoveWaiting(j);
507 	 Delete(j);
508 	 i--;
509       }
510    }
511    return WANTDIE;
512 }
513 
ShowRunStatus(const SMTaskRef<StatusLine> & sl)514 void Job::ShowRunStatus(const SMTaskRef<StatusLine>& sl)
515 {
516    if(waiting_num==0)
517       return;
518    Job *j=waiting[0];
519    if(waiting_num>1)
520    {
521       j=waiting[(now/3)%waiting_num];
522       current->TimeoutS(3);
523    }
524    if(j!=this)
525       j->ShowRunStatus(sl);
526 }
527 
FindAnyChild()528 Job *Job::FindAnyChild()
529 {
530    xlist_for_each(Job,children_jobs,node,scan)
531       if(scan->jobno>=0)
532 	 return scan;
533    return 0;
534 }
535 
lftpMovesToBackground_ToAll()536 void Job::lftpMovesToBackground_ToAll()
537 {
538    xlist_for_each(Job,all_jobs,node,scan)
539       scan->lftpMovesToBackground();
540 }
541 
CheckForWaitLoop(Job * parent)542 bool Job::CheckForWaitLoop(Job *parent)
543 {
544    if(parent==this)
545       return true;
546    for(int i=0; i<waiting_num; i++)
547       if(waiting[i]->CheckForWaitLoop(parent))
548 	 return true;
549    return false;
550 }
551 
WaitDone()552 void Job::WaitDone()
553 {
554    IncRefCount();  // keep me in memory
555    for(;;)
556    {
557       SMTask::Schedule();
558       if(Deleted() || Done())
559 	 break;
560       SMTask::Block();
561    }
562    DecRefCount();
563 }
564 
GetBytesCount()565 off_t Job::GetBytesCount()
566 {
567    off_t sum=0;
568    for(int i=0; i<waiting.count(); i++)
569       sum+=waiting[i]->GetBytesCount();
570    return sum;
571 }
GetTimeSpent()572 double Job::GetTimeSpent()
573 {
574    return 0;
575 }
GetTransferRate()576 double Job::GetTransferRate()
577 {
578    double sum=0;
579    for(int i=0; i<waiting.count(); i++)
580       sum+=waiting[i]->GetTransferRate();
581    return sum;
582 }
583 
FormatShortStatus(xstring & s)584 xstring& Job::FormatShortStatus(xstring& s)
585 {
586    double rate=GetTransferRate();
587    if(rate>=1)
588       s.append(Speedometer::GetStrProper(rate));
589    return s;
590 }
591