]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_status.c
Start applying new FSFE copyright.
[bacula/bacula] / bacula / src / dird / ua_status.c
1 /*
2  *
3  *   Bacula Director -- User Agent Status Command
4  *
5  *     Kern Sibbald, August MMI
6  *
7  *   Version $Id$
8  */
9 /*
10    Bacula® - The Network Backup Solution
11
12    Copyright (C) 2001-2006 Free Software Foundation Europe e.V.
13
14    The main author of Bacula is Kern Sibbald, with contributions from
15    many others, a complete list can be found in the file AUTHORS.
16    This program is Free Software; you can redistribute it and/or
17    modify it under the terms of version two of the GNU General Public
18    License as published by the Free Software Foundation plus additions
19    that are listed in the file LICENSE.
20
21    This program is distributed in the hope that it will be useful, but
22    WITHOUT ANY WARRANTY; without even the implied warranty of
23    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
24    General Public License for more details.
25
26    You should have received a copy of the GNU General Public License
27    along with this program; if not, write to the Free Software
28    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
29    02110-1301, USA.
30
31    Bacula® is a registered trademark ofJohn Walker.
32    The licensor of Bacula is the Free Software Foundation Europe
33    (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
34    Switzerland, email:ftf@fsfeurope.org.
35 */
36
37
38 #include "bacula.h"
39 #include "dird.h"
40
41 static void list_scheduled_jobs(UAContext *ua);
42 static void list_running_jobs(UAContext *ua);
43 static void list_terminated_jobs(UAContext *ua);
44 static void do_storage_status(UAContext *ua, STORE *store);
45 static void do_client_status(UAContext *ua, CLIENT *client);
46 static void do_director_status(UAContext *ua);
47 static void do_all_status(UAContext *ua);
48
49 static char OKqstatus[]   = "1000 OK .status\n";
50 static char DotStatusJob[] = "JobId=%s JobStatus=%c JobErrors=%d\n";
51
52 /*
53  * .status command
54  */
55 int qstatus_cmd(UAContext *ua, const char *cmd)
56 {
57    JCR* njcr = NULL;
58    s_last_job* job;
59    char ed1[50];
60
61    if (!open_db(ua)) {
62       return 1;
63    }
64    Dmsg1(20, "status:%s:\n", cmd);
65
66    if ((ua->argc != 3) || (strcasecmp(ua->argk[1], "dir"))) {
67       bsendmsg(ua, "1900 Bad .status command, missing arguments.\n");
68       return 1;
69    }
70
71    if (strcasecmp(ua->argk[2], "current") == 0) {
72       bsendmsg(ua, OKqstatus, ua->argk[2]);
73       foreach_jcr(njcr) {
74          if (njcr->JobId != 0) {
75             bsendmsg(ua, DotStatusJob, edit_int64(njcr->JobId, ed1), 
76                      njcr->JobStatus, njcr->JobErrors);
77          }
78       }
79       endeach_jcr(njcr);
80    } else if (strcasecmp(ua->argk[2], "last") == 0) {
81       bsendmsg(ua, OKqstatus, ua->argk[2]);
82       if ((last_jobs) && (last_jobs->size() > 0)) {
83          job = (s_last_job*)last_jobs->last();
84          bsendmsg(ua, DotStatusJob, edit_int64(job->JobId, ed1), 
85                   job->JobStatus, job->Errors);
86       }
87    } else {
88       bsendmsg(ua, "1900 Bad .status command, wrong argument.\n");
89       return 1;
90    }
91
92    return 1;
93 }
94
95 /*
96  * status command
97  */
98 int status_cmd(UAContext *ua, const char *cmd)
99 {
100    STORE *store;
101    CLIENT *client;
102    int item, i;
103
104    if (!open_db(ua)) {
105       return 1;
106    }
107    Dmsg1(20, "status:%s:\n", cmd);
108
109    for (i=1; i<ua->argc; i++) {
110       if (strcasecmp(ua->argk[i], NT_("all")) == 0) {
111          do_all_status(ua);
112          return 1;
113       } else if (strcasecmp(ua->argk[i], NT_("dir")) == 0 ||
114                  strcasecmp(ua->argk[i], NT_("director")) == 0) {
115          do_director_status(ua);
116          return 1;
117       } else if (strcasecmp(ua->argk[i], NT_("client")) == 0) {
118          client = get_client_resource(ua);
119          if (client) {
120             do_client_status(ua, client);
121          }
122          return 1;
123       } else {
124          store = get_storage_resource(ua, false/*no default*/);
125          if (store) {
126             do_storage_status(ua, store);
127          }
128          return 1;
129       }
130    }
131    /* If no args, ask for status type */
132    if (ua->argc == 1) {
133        char prmt[MAX_NAME_LENGTH];
134
135       start_prompt(ua, _("Status available for:\n"));
136       add_prompt(ua, NT_("Director"));
137       add_prompt(ua, NT_("Storage"));
138       add_prompt(ua, NT_("Client"));
139       add_prompt(ua, NT_("All"));
140       Dmsg0(20, "do_prompt: select daemon\n");
141       if ((item=do_prompt(ua, "",  _("Select daemon type for status"), prmt, sizeof(prmt))) < 0) {
142          return 1;
143       }
144       Dmsg1(20, "item=%d\n", item);
145       switch (item) {
146       case 0:                         /* Director */
147          do_director_status(ua);
148          break;
149       case 1:
150          store = select_storage_resource(ua);
151          if (store) {
152             do_storage_status(ua, store);
153          }
154          break;
155       case 2:
156          client = select_client_resource(ua);
157          if (client) {
158             do_client_status(ua, client);
159          }
160          break;
161       case 3:
162          do_all_status(ua);
163          break;
164       default:
165          break;
166       }
167    }
168    return 1;
169 }
170
171 static void do_all_status(UAContext *ua)
172 {
173    STORE *store, **unique_store;
174    CLIENT *client, **unique_client;
175    int i, j;
176    bool found;
177
178    do_director_status(ua);
179
180    /* Count Storage items */
181    LockRes();
182    i = 0;
183    foreach_res(store, R_STORAGE) {
184       i++;
185    }
186    unique_store = (STORE **) malloc(i * sizeof(STORE));
187    /* Find Unique Storage address/port */
188    i = 0;
189    foreach_res(store, R_STORAGE) {
190       found = false;
191       if (!acl_access_ok(ua, Storage_ACL, store->name())) {
192          continue;
193       }
194       for (j=0; j<i; j++) {
195          if (strcmp(unique_store[j]->address, store->address) == 0 &&
196              unique_store[j]->SDport == store->SDport) {
197             found = true;
198             break;
199          }
200       }
201       if (!found) {
202          unique_store[i++] = store;
203          Dmsg2(40, "Stuffing: %s:%d\n", store->address, store->SDport);
204       }
205    }
206    UnlockRes();
207
208    /* Call each unique Storage daemon */
209    for (j=0; j<i; j++) {
210       do_storage_status(ua, unique_store[j]);
211    }
212    free(unique_store);
213
214    /* Count Client items */
215    LockRes();
216    i = 0;
217    foreach_res(client, R_CLIENT) {
218       i++;
219    }
220    unique_client = (CLIENT **)malloc(i * sizeof(CLIENT));
221    /* Find Unique Client address/port */
222    i = 0;
223    foreach_res(client, R_CLIENT) {
224       found = false;
225       if (!acl_access_ok(ua, Client_ACL, client->name())) {
226          continue;
227       }
228       for (j=0; j<i; j++) {
229          if (strcmp(unique_client[j]->address, client->address) == 0 &&
230              unique_client[j]->FDport == client->FDport) {
231             found = true;
232             break;
233          }
234       }
235       if (!found) {
236          unique_client[i++] = client;
237          Dmsg2(40, "Stuffing: %s:%d\n", client->address, client->FDport);
238       }
239    }
240    UnlockRes();
241
242    /* Call each unique File daemon */
243    for (j=0; j<i; j++) {
244       do_client_status(ua, unique_client[j]);
245    }
246    free(unique_client);
247
248 }
249
250 static void do_director_status(UAContext *ua)
251 {
252    char dt[MAX_TIME_LENGTH];
253    char b1[35], b2[35], b3[35], b4[35];
254
255    bsendmsg(ua, _("%s Version: %s (%s) %s %s %s\n"), my_name, VERSION, BDATE,
256             HOST_OS, DISTNAME, DISTVER);
257    bstrftime_nc(dt, sizeof(dt), daemon_start_time);
258    if (num_jobs_run == 1) {
259       bsendmsg(ua, _("Daemon started %s, 1 Job run since started.\n"), dt);
260    }
261    else {
262       bsendmsg(ua, _("Daemon started %s, %d Jobs run since started.\n"),
263         dt, num_jobs_run);
264    }
265    bsendmsg(ua, _(" Heap: bytes=%s max_bytes=%s bufs=%s max_bufs=%s\n"),
266             edit_uint64_with_commas(sm_bytes, b1),
267             edit_uint64_with_commas(sm_max_bytes, b2),
268             edit_uint64_with_commas(sm_buffers, b3),
269             edit_uint64_with_commas(sm_max_buffers, b4));
270
271    /*
272     * List scheduled Jobs
273     */
274    list_scheduled_jobs(ua);
275
276    /*
277     * List running jobs
278     */
279    list_running_jobs(ua);
280
281    /*
282     * List terminated jobs
283     */
284    list_terminated_jobs(ua);
285    bsendmsg(ua, _("====\n"));
286 }
287
288 static void do_storage_status(UAContext *ua, STORE *store)
289 {
290    BSOCK *sd;
291
292    set_wstorage(ua->jcr, store);
293    /* Try connecting for up to 15 seconds */
294    bsendmsg(ua, _("Connecting to Storage daemon %s at %s:%d\n"),
295       store->name(), store->address, store->SDport);
296    if (!connect_to_storage_daemon(ua->jcr, 1, 15, 0)) {
297       bsendmsg(ua, _("\nFailed to connect to Storage daemon %s.\n====\n"),
298          store->name());
299       if (ua->jcr->store_bsock) {
300          bnet_close(ua->jcr->store_bsock);
301          ua->jcr->store_bsock = NULL;
302       }
303       return;
304    }
305    Dmsg0(20, _("Connected to storage daemon\n"));
306    sd = ua->jcr->store_bsock;
307    bnet_fsend(sd, "status");
308    while (bnet_recv(sd) >= 0) {
309       bsendmsg(ua, "%s", sd->msg);
310    }
311    bnet_sig(sd, BNET_TERMINATE);
312    bnet_close(sd);
313    ua->jcr->store_bsock = NULL;
314    return;
315 }
316
317 static void do_client_status(UAContext *ua, CLIENT *client)
318 {
319    BSOCK *fd;
320
321    /* Connect to File daemon */
322
323    ua->jcr->client = client;
324    /* Release any old dummy key */
325    if (ua->jcr->sd_auth_key) {
326       free(ua->jcr->sd_auth_key);
327    }
328    /* Create a new dummy SD auth key */
329    ua->jcr->sd_auth_key = bstrdup("dummy");
330
331    /* Try to connect for 15 seconds */
332    bsendmsg(ua, _("Connecting to Client %s at %s:%d\n"),
333       client->name(), client->address, client->FDport);
334    if (!connect_to_file_daemon(ua->jcr, 1, 15, 0)) {
335       bsendmsg(ua, _("Failed to connect to Client %s.\n====\n"),
336          client->name());
337       if (ua->jcr->file_bsock) {
338          bnet_close(ua->jcr->file_bsock);
339          ua->jcr->file_bsock = NULL;
340       }
341       return;
342    }
343    Dmsg0(20, _("Connected to file daemon\n"));
344    fd = ua->jcr->file_bsock;
345    bnet_fsend(fd, "status");
346    while (bnet_recv(fd) >= 0) {
347       bsendmsg(ua, "%s", fd->msg);
348    }
349    bnet_sig(fd, BNET_TERMINATE);
350    bnet_close(fd);
351    ua->jcr->file_bsock = NULL;
352
353    return;
354 }
355
356 static void prt_runhdr(UAContext *ua)
357 {
358    bsendmsg(ua, _("\nScheduled Jobs:\n"));
359    bsendmsg(ua, _("Level          Type     Pri  Scheduled          Name               Volume\n"));
360    bsendmsg(ua, _("===================================================================================\n"));
361 }
362
363 /* Scheduling packet */
364 struct sched_pkt {
365    dlink link;                        /* keep this as first item!!! */
366    JOB *job;
367    int level;
368    int priority;
369    time_t runtime;
370    POOL *pool;
371    STORE *store;
372 };
373
374 static void prt_runtime(UAContext *ua, sched_pkt *sp)
375 {
376    char dt[MAX_TIME_LENGTH];
377    const char *level_ptr;
378    bool ok = false;
379    bool close_db = false;
380    JCR *jcr = ua->jcr;
381    MEDIA_DBR mr;
382
383    memset(&mr, 0, sizeof(mr));
384    if (sp->job->JobType == JT_BACKUP) {
385       jcr->db = NULL;
386       ok = complete_jcr_for_job(jcr, sp->job, sp->pool);
387       if (jcr->db) {
388          close_db = true;             /* new db opened, remember to close it */
389       }
390       if (ok) {
391          mr.PoolId = jcr->jr.PoolId;
392          mr.StorageId = sp->store->StorageId;
393          jcr->wstore = sp->store;
394          ok = find_next_volume_for_append(jcr, &mr, 1, false/*no create*/);
395       }
396       if (!ok) {
397          bstrncpy(mr.VolumeName, "*unknown*", sizeof(mr.VolumeName));
398       }
399    }
400    bstrftime_nc(dt, sizeof(dt), sp->runtime);
401    switch (sp->job->JobType) {
402    case JT_ADMIN:
403    case JT_RESTORE:
404       level_ptr = " ";
405       break;
406    default:
407       level_ptr = level_to_str(sp->level);
408       break;
409    }
410    bsendmsg(ua, _("%-14s %-8s %3d  %-18s %-18s %s\n"),
411       level_ptr, job_type_to_str(sp->job->JobType), sp->priority, dt,
412       sp->job->name(), mr.VolumeName);
413    if (close_db) {
414       db_close_database(jcr, jcr->db);
415    }
416    jcr->db = ua->db;                  /* restore ua db to jcr */
417 }
418
419 /*
420  * Sort items by runtime, priority
421  */
422 static int my_compare(void *item1, void *item2)
423 {
424    sched_pkt *p1 = (sched_pkt *)item1;
425    sched_pkt *p2 = (sched_pkt *)item2;
426    if (p1->runtime < p2->runtime) {
427       return -1;
428    } else if (p1->runtime > p2->runtime) {
429       return 1;
430    }
431    if (p1->priority < p2->priority) {
432       return -1;
433    } else if (p1->priority > p2->priority) {
434       return 1;
435    }
436    return 0;
437 }
438
439 /*
440  * Find all jobs to be run in roughly the
441  *  next 24 hours.
442  */
443 static void list_scheduled_jobs(UAContext *ua)
444 {
445    time_t runtime;
446    RUN *run;
447    JOB *job;
448    STORE* store;
449    int level, num_jobs = 0;
450    int priority;
451    bool hdr_printed = false;
452    dlist sched;
453    sched_pkt *sp;
454    int days, i;
455
456    Dmsg0(200, "enter list_sched_jobs()\n");
457
458    days = 1;
459    i = find_arg_with_value(ua, NT_("days"));
460    if (i >= 0) {
461      days = atoi(ua->argv[i]);
462      if ((days < 0) || (days > 50)) {
463        bsendmsg(ua, _("Ignoring illegal value for days.\n"));
464        days = 1;
465      }
466    }
467
468    /* Loop through all jobs */
469    LockRes();
470    foreach_res(job, R_JOB) {
471       if (!acl_access_ok(ua, Job_ACL, job->name()) || !job->enabled) {
472          continue;
473       }
474       for (run=NULL; (run = find_next_run(run, job, runtime, days)); ) {
475          level = job->JobLevel;
476          if (run->level) {
477             level = run->level;
478          }
479          priority = job->Priority;
480          if (run->Priority) {
481             priority = run->Priority;
482          }
483          if (run->storage) {
484             store = run->storage;
485          } else {
486             store = (STORE *)job->storage->first();
487          }
488          if (!hdr_printed) {
489             prt_runhdr(ua);
490             hdr_printed = true;
491          }
492          sp = (sched_pkt *)malloc(sizeof(sched_pkt));
493          sp->job = job;
494          sp->level = level;
495          sp->priority = priority;
496          sp->runtime = runtime;
497          sp->pool = run->pool;
498          sp->store = store;
499          sched.binary_insert_multiple(sp, my_compare);
500          num_jobs++;
501       }
502    } /* end for loop over resources */
503    UnlockRes();
504    foreach_dlist(sp, &sched) {
505       prt_runtime(ua, sp);
506    }
507    if (num_jobs == 0) {
508       bsendmsg(ua, _("No Scheduled Jobs.\n"));
509    }
510    bsendmsg(ua, _("====\n"));
511    Dmsg0(200, "Leave list_sched_jobs_runs()\n");
512 }
513
514 static void list_running_jobs(UAContext *ua)
515 {
516    JCR *jcr;
517    int njobs = 0;
518    const char *msg;
519    char *emsg;                        /* edited message */
520    char dt[MAX_TIME_LENGTH];
521    char level[10];
522    bool pool_mem = false;
523
524    Dmsg0(200, "enter list_run_jobs()\n");
525    bsendmsg(ua, _("\nRunning Jobs:\n"));
526    foreach_jcr(jcr) {
527       if (jcr->JobId == 0) {      /* this is us */
528          /* this is a console or other control job. We only show console
529           * jobs in the status output.
530           */
531          if (jcr->JobType == JT_CONSOLE) {
532             bstrftime_nc(dt, sizeof(dt), jcr->start_time);
533             bsendmsg(ua, _("Console connected at %s\n"), dt);
534          }
535          continue;
536       }       
537       njobs++;
538    }
539    endeach_jcr(jcr);
540
541    if (njobs == 0) {
542       /* Note the following message is used in regress -- don't change */
543       bsendmsg(ua, _("No Jobs running.\n====\n"));
544       Dmsg0(200, "leave list_run_jobs()\n");
545       return;
546    }
547    njobs = 0;
548    bsendmsg(ua, _(" JobId Level   Name                       Status\n"));
549    bsendmsg(ua, _("======================================================================\n"));
550    foreach_jcr(jcr) {
551       if (jcr->JobId == 0 || !acl_access_ok(ua, Job_ACL, jcr->job->name())) {
552          continue;
553       }
554       njobs++;
555       switch (jcr->JobStatus) {
556       case JS_Created:
557          msg = _("is waiting execution");
558          break;
559       case JS_Running:
560          msg = _("is running");
561          break;
562       case JS_Blocked:
563          msg = _("is blocked");
564          break;
565       case JS_Terminated:
566          msg = _("has terminated");
567          break;
568       case JS_ErrorTerminated:
569          msg = _("has erred");
570          break;
571       case JS_Error:
572          msg = _("has errors");
573          break;
574       case JS_FatalError:
575          msg = _("has a fatal error");
576          break;
577       case JS_Differences:
578          msg = _("has verify differences");
579          break;
580       case JS_Canceled:
581          msg = _("has been canceled");
582          break;
583       case JS_WaitFD:
584          emsg = (char *) get_pool_memory(PM_FNAME);
585          Mmsg(emsg, _("is waiting on Client %s"), jcr->client->name());
586          pool_mem = true;
587          msg = emsg;
588          break;
589       case JS_WaitSD:
590          emsg = (char *) get_pool_memory(PM_FNAME);
591          if (jcr->wstore) {
592             Mmsg(emsg, _("is waiting on Storage %s"), jcr->wstore->name());
593          } else {
594             Mmsg(emsg, _("is waiting on Storage %s"), jcr->rstore->name());
595          }
596          pool_mem = true;
597          msg = emsg;
598          break;
599       case JS_WaitStoreRes:
600          msg = _("is waiting on max Storage jobs");
601          break;
602       case JS_WaitClientRes:
603          msg = _("is waiting on max Client jobs");
604          break;
605       case JS_WaitJobRes:
606          msg = _("is waiting on max Job jobs");
607          break;
608       case JS_WaitMaxJobs:
609          msg = _("is waiting on max total jobs");
610          break;
611       case JS_WaitStartTime:
612          msg = _("is waiting for its start time");
613          break;
614       case JS_WaitPriority:
615          msg = _("is waiting for higher priority jobs to finish");
616          break;
617
618       default:
619          emsg = (char *) get_pool_memory(PM_FNAME);
620          Mmsg(emsg, _("is in unknown state %c"), jcr->JobStatus);
621          pool_mem = true;
622          msg = emsg;
623          break;
624       }
625       /*
626        * Now report Storage daemon status code
627        */
628       switch (jcr->SDJobStatus) {
629       case JS_WaitMount:
630          if (pool_mem) {
631             free_pool_memory(emsg);
632             pool_mem = false;
633          }
634          msg = _("is waiting for a mount request");
635          break;
636       case JS_WaitMedia:
637          if (pool_mem) {
638             free_pool_memory(emsg);
639             pool_mem = false;
640          }
641          msg = _("is waiting for an appendable Volume");
642          break;
643       case JS_WaitFD:
644          if (!pool_mem) {
645             emsg = (char *) get_pool_memory(PM_FNAME);
646             pool_mem = true;
647          }
648          Mmsg(emsg, _("is waiting for Client %s to connect to Storage %s"),
649               jcr->client->name(), jcr->wstore->name());
650          msg = emsg;
651          break;
652       }
653       switch (jcr->JobType) {
654       case JT_ADMIN:
655       case JT_RESTORE:
656          bstrncpy(level, "      ", sizeof(level));
657          break;
658       default:
659          bstrncpy(level, level_to_str(jcr->JobLevel), sizeof(level));
660          level[7] = 0;
661          break;
662       }
663
664       bsendmsg(ua, _("%6d %-6s  %-20s %s\n"),
665          jcr->JobId,
666          level,
667          jcr->Job,
668          msg);
669
670       if (pool_mem) {
671          free_pool_memory(emsg);
672          pool_mem = false;
673       }
674    }
675    endeach_jcr(jcr);
676    bsendmsg(ua, _("====\n"));
677    Dmsg0(200, "leave list_run_jobs()\n");
678 }
679
680 static void list_terminated_jobs(UAContext *ua)
681 {
682    char dt[MAX_TIME_LENGTH], b1[30], b2[30];
683    char level[10];
684
685    if (last_jobs->empty()) {
686       bsendmsg(ua, _("No Terminated Jobs.\n"));
687       return;
688    }
689    lock_last_jobs_list();
690    struct s_last_job *je;
691    bsendmsg(ua, _("\nTerminated Jobs:\n"));
692    bsendmsg(ua, _(" JobId  Level    Files      Bytes   Status   Finished        Name \n"));
693    bsendmsg(ua, _("====================================================================\n"));
694    foreach_dlist(je, last_jobs) {
695       char JobName[MAX_NAME_LENGTH];
696       const char *termstat;
697
698       bstrncpy(JobName, je->Job, sizeof(JobName));
699       /* There are three periods after the Job name */
700       char *p;
701       for (int i=0; i<3; i++) {
702          if ((p=strrchr(JobName, '.')) != NULL) {
703             *p = 0;
704          }
705       }
706
707       if (!acl_access_ok(ua, Job_ACL, JobName)) {
708          continue;
709       }
710
711       bstrftime_nc(dt, sizeof(dt), je->end_time);
712       switch (je->JobType) {
713       case JT_ADMIN:
714       case JT_RESTORE:
715          bstrncpy(level, "    ", sizeof(level));
716          break;
717       default:
718          bstrncpy(level, level_to_str(je->JobLevel), sizeof(level));
719          level[4] = 0;
720          break;
721       }
722       switch (je->JobStatus) {
723       case JS_Created:
724          termstat = _("Created");
725          break;
726       case JS_FatalError:
727       case JS_ErrorTerminated:
728          termstat = _("Error");
729          break;
730       case JS_Differences:
731          termstat = _("Diffs");
732          break;
733       case JS_Canceled:
734          termstat = _("Cancel");
735          break;
736       case JS_Terminated:
737          termstat = _("OK");
738          break;
739       default:
740          termstat = _("Other");
741          break;
742       }
743       bsendmsg(ua, _("%6d  %-6s %8s %10s  %-7s  %-8s %s\n"),
744          je->JobId,
745          level,
746          edit_uint64_with_commas(je->JobFiles, b1),
747          edit_uint64_with_suffix(je->JobBytes, b2),
748          termstat,
749          dt, JobName);
750    }
751    bsendmsg(ua, _("\n"));
752    unlock_last_jobs_list();
753 }