c49319a2ec507abcb45c346424c7af37af6be92c
[debian/amanda] / server-src / changer.c
1 /*
2  * Amanda, The Advanced Maryland Automatic Network Disk Archiver
3  * Copyright (c) 1991-1998 University of Maryland at College Park
4  * All Rights Reserved.
5  *
6  * Permission to use, copy, modify, distribute, and sell this software and its
7  * documentation for any purpose is hereby granted without fee, provided that
8  * the above copyright notice appear in all copies and that both that
9  * copyright notice and this permission notice appear in supporting
10  * documentation, and that the name of U.M. not be used in advertising or
11  * publicity pertaining to distribution of the software without specific,
12  * written prior permission.  U.M. makes no representations about the
13  * suitability of this software for any purpose.  It is provided "as is"
14  * without express or implied warranty.
15  *
16  * U.M. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL U.M.
18  * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
19  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
20  * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
21  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
22  *
23  * Authors: the Amanda Development Team.  Its members are listed in a
24  * file named AUTHORS, in the root directory of this distribution.
25  */
26 /*
27  * $Id: changer.c,v 1.36 2006/08/24 01:57:16 paddy_s Exp $
28  *
29  * interface routines for tape changers
30  */
31 #include "amanda.h"
32 #include "util.h"
33 #include "conffile.h"
34 #include "version.h"
35
36 #include "changer.h"
37
38 /*
39  * If we don't have the new-style wait access functions, use our own,
40  * compatible with old-style BSD systems at least.  Note that we don't
41  * care about the case w_stopval == WSTOPPED since we don't ask to see
42  * stopped processes, so should never get them from wait.
43  */
44 #ifndef WEXITSTATUS
45 #   define WEXITSTATUS(r)       (((union wait *) &(r))->w_retcode)
46 #   define WTERMSIG(r)          (((union wait *) &(r))->w_termsig)
47
48 #   undef  WIFSIGNALED
49 #   define WIFSIGNALED(r)       (((union wait *) &(r))->w_termsig != 0)
50 #endif
51
52
53 int changer_debug = 0;
54 char *changer_resultstr = NULL;
55
56 static char *tapechanger = NULL;
57
58 /* local functions */
59 static int changer_command(char *cmd, char *arg);
60 static int report_bad_resultstr(char *cmd);
61 static int run_changer_command(char *cmd, char *arg, char **slotstr, char **rest);
62
63 int
64 changer_init(void)
65 {
66     if (tapechanger == NULL)
67         tapechanger = getconf_str(CNF_TPCHANGER);
68     if (*tapechanger != '\0' && *tapechanger != '/') {
69         tapechanger = vstralloc(amlibexecdir, "/", tapechanger, versionsuffix(),
70                                 NULL);
71     }
72     return strcmp(tapechanger, "") != 0;
73 }
74
75
76 static int
77 report_bad_resultstr(char *cmd)
78 {
79     char *s;
80
81     s = vstrallocf(_("badly formed result from changer command %s: \"%s\""),
82                   cmd, changer_resultstr);
83     amfree(changer_resultstr);
84     changer_resultstr = s;
85     return 2;
86 }
87
88 static int
89 run_changer_command(
90     char *      cmd,
91     char *      arg,
92     char **     slotstr,
93     char **     rest)
94 {
95     int exitcode;
96     char *result_copy;
97     char *slot;
98     char *s;
99     int ch;
100
101     if (slotstr) {
102         *slotstr = NULL;
103     }
104     if (rest) {
105         *rest = NULL;
106     }
107     exitcode = changer_command(cmd, arg);
108     s = changer_resultstr;
109     ch = *s++;
110
111     skip_whitespace(s, ch);
112     if(ch == '\0') return report_bad_resultstr(cmd);
113     slot = s - 1;
114     skip_non_whitespace(s, ch);
115     s[-1] = '\0';
116     if (slotstr) {
117         *slotstr = newstralloc(*slotstr, slot);
118     }
119     s[-1] = (char)ch;
120
121     skip_whitespace(s, ch);
122     if (rest) {
123         *rest = s - 1;
124     }
125
126     if(exitcode) {
127         if(ch == '\0') return report_bad_resultstr(cmd);
128         result_copy = stralloc(s - 1);
129         amfree(changer_resultstr);
130         changer_resultstr = result_copy;
131         return exitcode;
132     }
133     return 0;
134 }
135
136 int
137 changer_reset(
138     char **     slotstr)
139 {
140     char *rest;
141
142     return run_changer_command("-reset", (char *) NULL, slotstr, &rest);
143 }
144
145 int
146 changer_clean(
147     char **     slotstr)
148 {
149     char *rest;
150
151     return run_changer_command("-clean", (char *) NULL, slotstr, &rest);
152 }
153
154 int
155 changer_eject(
156     char **     slotstr)
157 {
158     char *rest;
159
160     return run_changer_command("-eject", (char *) NULL, slotstr, &rest);
161 }
162
163 int
164 changer_loadslot(
165     char *inslotstr,
166     char **outslotstr,
167     char **devicename)
168 {
169     char *rest;
170     int rc;
171
172     rc = run_changer_command("-slot", inslotstr, outslotstr, &rest);
173
174     if(rc) return rc;
175     if(*rest == '\0') return report_bad_resultstr("-slot");
176
177     *devicename = newstralloc(*devicename, rest);
178     return 0;
179 }
180
181
182 /*
183  * This function is somewhat equal to changer_info with one additional
184  * parameter, to get information, if the changer is able to search for
185  * tapelabels himself. E.g. Barcodereader
186  * The changer_script answers with an additional parameter, if it is able
187  * to search. This one should be 1, if it is able to search, and 0 if it
188  * knows about the extension. If the additional answer is omitted, the
189  * changer is not able to search for a tape. 
190  */
191
192 int
193 changer_query(
194     int *       nslotsp,
195     char **     curslotstr,
196     int *       backwardsp,
197     int *       searchable)
198 {
199     char *rest;
200     int rc;
201
202     rc = run_changer_command("-info", (char *) NULL, curslotstr, &rest);
203     if(rc) return rc;
204
205     dbprintf(_("changer_query: changer return was %s\n"),rest);
206     if (sscanf(rest, "%d %d %d", nslotsp, backwardsp, searchable) != 3) {
207       if (sscanf(rest, "%d %d", nslotsp, backwardsp) != 2) {
208         return report_bad_resultstr("-info");
209       } else {
210         *searchable = 0;
211       }
212     }
213     dbprintf(_("changer_query: searchable = %d\n"),*searchable);
214     return 0;
215 }
216
217 int
218 changer_info(
219     int *       nslotsp,
220     char **     curslotstr,
221     int *       backwardsp)
222 {
223     char *rest;
224     int rc;
225
226     rc = run_changer_command("-info", (char *) NULL, curslotstr, &rest);
227     if(rc) return rc;
228
229     if (sscanf(rest, "%d %d", nslotsp, backwardsp) != 2) {
230         return report_bad_resultstr("-info");
231     }
232     return 0;
233 }
234
235
236 /* ---------------------------- */
237
238 /*
239  * This function first uses searchlabel and changer_search, if
240  * the library is able to find a tape itself. If it is not, or if 
241  * the tape could not be found, then the normal scan is done.
242  *
243  * See interface documentation in changer.h.
244  */
245
246 void
247 changer_find(
248      void *     user_data,
249      int        (*user_init)(void *, int, int, int, int),
250      int        (*user_slot)(void *, int, char *, char *),
251      char *     searchlabel)
252 {
253     char *slotstr, *device = NULL, *curslotstr = NULL;
254     int nslots, checked, backwards, rc, done, searchable;
255
256     rc = changer_query(&nslots, &curslotstr, &backwards, &searchable);
257
258     if (rc != 0) {
259         /* Problem with the changer script. Bail. */
260         g_fprintf(stderr, _("Changer problem: %s\n"), changer_resultstr);
261         return;
262     }
263
264     done = user_init(user_data, rc, nslots, backwards, searchable);
265     amfree(curslotstr);
266    
267     if (searchlabel != NULL)
268     {
269       dbprintf(_("changer_find: looking for %s changer is searchable = %d\n"),
270                 searchlabel, searchable);
271     } else {
272       dbprintf(_("changer_find: looking for NULL changer is searchable = %d\n"),
273                 searchable);
274     }
275
276     if ((searchlabel!=NULL) && searchable && !done){
277       rc=changer_search(searchlabel, &curslotstr, &device);
278       if(rc == 0)
279         done = user_slot(user_data, rc, curslotstr, device);
280     }
281  
282     slotstr = "current";
283     checked = 0;
284
285     while(!done && checked < nslots) {
286         rc = changer_loadslot(slotstr, &curslotstr, &device);
287         if(rc > 0)
288             done = user_slot(user_data, rc, curslotstr, device);
289         else if(!done)
290             done = user_slot(user_data, 0,  curslotstr, device);
291         amfree(curslotstr);
292         amfree(device);
293
294         checked += 1;
295         slotstr = "next";
296     }
297 }
298
299 /* ---------------------------- */
300
301 void
302 changer_current(
303     void *      user_data,
304     int         (*user_init)(void *, int, int, int, int),
305     int         (*user_slot)(void *, int, char *, char *))
306 {
307     char *device = NULL, *curslotstr = NULL;
308     int nslots, backwards, rc, done, searchable;
309
310     rc = changer_query(&nslots, &curslotstr, &backwards, &searchable);
311     done = user_init(user_data, rc, nslots, backwards, searchable);
312     amfree(curslotstr);
313
314     rc = changer_loadslot("current", &curslotstr, &device);
315     if(rc > 0) {
316         done = user_slot(user_data, rc, curslotstr, device);
317     } else if(!done) {
318         done = user_slot(user_data, 0,  curslotstr, device);
319     }
320     amfree(curslotstr);
321     amfree(device);
322 }
323
324 /* ---------------------------- */
325
326 static int
327 changer_command(
328      char *cmd,
329      char *arg)
330 {
331     int fd[2];
332     amwait_t wait_exitcode = 1;
333     int exitcode;
334     char *cmdstr;
335     pid_t pid, changer_pid = 0;
336     int fd_to_close[4], *pfd_to_close = fd_to_close;
337
338     cmdstr = vstralloc(tapechanger, " ",
339                        cmd, arg ? " " : "", 
340                        arg ? arg : "",
341                        NULL);
342
343     if(changer_debug) {
344         g_fprintf(stderr, _("changer: opening pipe to: %s\n"), cmdstr);
345         fflush(stderr);
346     }
347
348     amfree(changer_resultstr);
349
350     if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) == -1) {
351         changer_resultstr = vstrallocf(
352                                 _("<error> could not create pipe for \"%s\": %s"),
353                                 cmdstr, strerror(errno));
354         exitcode = 2;
355         goto failed;
356     }
357
358     /* make sure fd[0] > 2  && fd[1] > 2 */
359     pfd_to_close = fd_to_close;
360     while(fd[0] <= 2) {
361         int a = dup(fd[0]);
362         *pfd_to_close++ = fd[0];
363         fd[0] = a;
364     }
365     while(fd[1] <= 2) {
366         int a = dup(fd[1]);
367         *pfd_to_close++ = fd[1];
368         fd[1] = a;
369     }
370     while (pfd_to_close > fd_to_close) {
371         close(*--pfd_to_close);
372     }
373
374     if(fd[0] < 0 || fd[0] >= (int)FD_SETSIZE) {
375         changer_resultstr = vstrallocf(
376                         _("<error> could not create pipe for \"%s\":"
377                         "socketpair 0: descriptor %d out of range ( 0 .. %d)"),
378                         cmdstr, fd[0], (int)FD_SETSIZE-1);
379         exitcode = 2;
380         goto done;
381     }
382     if(fd[1] < 0 || fd[1] >= (int)FD_SETSIZE) {
383         changer_resultstr = vstrallocf(
384                         _("<error> could not create pipe for \"%s\":"
385                         "socketpair 1: descriptor %d out of range ( 0 .. %d)"),
386                         cmdstr, fd[1], (int)FD_SETSIZE-1);
387         exitcode = 2;
388         goto done;
389     }
390
391     switch(changer_pid = fork()) {
392     case -1:
393         changer_resultstr = vstrallocf(
394                         _("<error> could not fork for \"%s\": %s"),
395                         cmdstr, strerror(errno));
396         exitcode = 2;
397         goto done;
398     case 0:
399         debug_dup_stderr_to_debug();
400         if(dup2(fd[1], 1) == -1) {
401             changer_resultstr = vstrallocf(
402                         _("<error> could not open pipe to \"%s\": %s"),
403                         cmdstr, strerror(errno));
404             (void)fullwrite(fd[1], changer_resultstr, strlen(changer_resultstr));
405             exit(1);
406         }
407         aclose(fd[0]);
408         aclose(fd[1]);
409         if(config_dir && chdir(config_dir) == -1) {
410             changer_resultstr = vstrallocf(
411                         _("<error> could not cd to \"%s\": %s"),
412                         config_dir, strerror(errno));
413             (void)fullwrite(STDOUT_FILENO, changer_resultstr, strlen(changer_resultstr));
414             exit(1);
415         }
416         safe_fd(-1, 0);
417         if(arg) {
418             execle(tapechanger, tapechanger, cmd, arg, (char *)NULL,
419                    safe_env());
420         } else {
421             execle(tapechanger, tapechanger, cmd, (char *)NULL, safe_env());
422         }
423         changer_resultstr = vstrallocf(
424                         _("<error> could not exec \"%s\": %s"),
425                         tapechanger, strerror(errno));
426         (void)fullwrite(STDOUT_FILENO, changer_resultstr, strlen(changer_resultstr));
427         exit(1);
428     default:
429         aclose(fd[1]);
430     }
431
432     if((changer_resultstr = areads(fd[0])) == NULL) {
433         if (errno == 0) {
434             changer_resultstr = vstrallocf(
435                         _("<error> could not read result from \"%s\": Premature end of file, see %s"),
436                         tapechanger, dbfn());
437         } else {
438             changer_resultstr = vstrallocf(
439                         _("<error> could not read result from \"%s\": %s"),
440                         tapechanger, strerror(errno));
441         }
442     }
443
444     while(1) {
445         if ((pid = wait(&wait_exitcode)) == -1) {
446             if(errno == EINTR) {
447                 continue;
448             } else {
449                 changer_resultstr = vstrallocf(
450                         _("<error> wait for \"%s\" failed: %s"),
451                         tapechanger, strerror(errno));
452                 exitcode = 2;
453                 goto done;
454             }
455         } else if (pid != changer_pid) {
456             changer_resultstr = vstrallocf(
457                         _("<error> wait for \"%s\" returned unexpected pid %ld"),
458                         tapechanger, (long)pid);
459             exitcode = 2;
460             goto done;
461         } else {
462             break;
463         }
464     }
465
466     /* mark out-of-control changers as fatal error */
467     if(WIFSIGNALED(wait_exitcode)) {
468         changer_resultstr = newvstrallocf(changer_resultstr,
469                         _("<error> %s (got signal %d)"),
470                         changer_resultstr, WTERMSIG(wait_exitcode));
471         exitcode = 2;
472     } else {
473         exitcode = WEXITSTATUS(wait_exitcode);
474     }
475
476 done:
477     aclose(fd[0]);
478     aclose(fd[1]);
479
480 failed:
481     if (exitcode != 0) {
482         dbprintf(_("changer: got exit: %d str: %s\n"), exitcode, changer_resultstr); 
483     }
484
485     amfree(cmdstr);
486
487     return exitcode;
488 }
489
490
491 /*
492  * This function commands the changerscript to look for a tape named
493  * searchlabel. If is found, the changerscript answers with the device,
494  * in which the tape can be accessed.
495  */
496
497 int
498 changer_search(
499     char *      searchlabel,
500     char **     outslotstr,
501     char **     devicename)
502 {
503     char *rest;
504     int rc;
505
506     dbprintf("changer_search: %s\n",searchlabel);
507     rc = run_changer_command("-search", searchlabel, outslotstr, &rest);
508     if(rc) return rc;
509
510     if(*rest == '\0') return report_bad_resultstr("-search");
511
512     *devicename = newstralloc(*devicename, rest);
513     return 0;
514 }
515
516
517 /*
518  * Because barcodelabel are short, and may not be the same as the 
519  * amandalabels, the changerscript should be informed, which tapelabel
520  * is associated with a tape. This function should be called after 
521  * giving a label for a tape. (Maybe also, when the label and the associated
522  * slot is known. e.g. during library scan.
523  */
524
525 int
526 changer_label(
527     char *      slotsp, 
528     char *      labelstr)
529 {
530     int rc;
531     char *rest=NULL;
532     char *slotstr;
533     char *curslotstr = NULL;
534     int nslots, backwards, searchable;
535
536     dbprintf(_("changer_label: %s for slot %s\n"),labelstr,slotsp);
537     rc = changer_query(&nslots, &curslotstr, &backwards,&searchable);
538     amfree(curslotstr);
539
540     if ((rc == 0) && (searchable == 1)){
541         dbprintf(_("changer_label: calling changer -label %s\n"),labelstr);
542         rc = run_changer_command("-label", labelstr, &slotstr, &rest);
543         amfree(slotstr);
544     }
545
546     if(rc) return rc;
547
548     return 0;
549 }