Imported Upstream version 2.5.1
[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(void);
61 static int run_changer_command(char *cmd, char *arg, char **slotstr, char **rest);
62
63 int
64 changer_init(void)
65 {
66     tapechanger = getconf_str(CNF_TPCHANGER);
67     return strcmp(tapechanger, "") != 0;
68 }
69
70
71 static int
72 report_bad_resultstr(void)
73 {
74     char *s;
75
76     s = vstralloc("badly formed result from changer: ",
77                   "\"", changer_resultstr, "\"",
78                   NULL);
79     amfree(changer_resultstr);
80     changer_resultstr = s;
81     return 2;
82 }
83
84 static int
85 run_changer_command(
86     char *      cmd,
87     char *      arg,
88     char **     slotstr,
89     char **     rest)
90 {
91     int exitcode;
92     char *result_copy;
93     char *slot;
94     char *s;
95     int ch;
96
97     if (slotstr) {
98         *slotstr = NULL;
99     }
100     if (rest) {
101         *rest = NULL;
102     }
103     exitcode = changer_command(cmd, arg);
104     s = changer_resultstr;
105     ch = *s++;
106
107     skip_whitespace(s, ch);
108     if(ch == '\0') return report_bad_resultstr();
109     slot = s - 1;
110     skip_non_whitespace(s, ch);
111     s[-1] = '\0';
112     if (slotstr) {
113         *slotstr = newstralloc(*slotstr, slot);
114     }
115     s[-1] = (char)ch;
116
117     skip_whitespace(s, ch);
118     if (rest) {
119         *rest = s - 1;
120     }
121
122     if(exitcode) {
123         if(ch == '\0') return report_bad_resultstr();
124         result_copy = stralloc(s - 1);
125         amfree(changer_resultstr);
126         changer_resultstr = result_copy;
127         return exitcode;
128     }
129     return 0;
130 }
131
132 int
133 changer_reset(
134     char **     slotstr)
135 {
136     char *rest;
137
138     return run_changer_command("-reset", (char *) NULL, slotstr, &rest);
139 }
140
141 int
142 changer_clean(
143     char **     slotstr)
144 {
145     char *rest;
146
147     return run_changer_command("-clean", (char *) NULL, slotstr, &rest);
148 }
149
150 int
151 changer_eject(
152     char **     slotstr)
153 {
154     char *rest;
155
156     return run_changer_command("-eject", (char *) NULL, slotstr, &rest);
157 }
158
159 int
160 changer_loadslot(
161     char *inslotstr,
162     char **outslotstr,
163     char **devicename)
164 {
165     char *rest;
166     int rc;
167
168     rc = run_changer_command("-slot", inslotstr, outslotstr, &rest);
169
170     if(rc) return rc;
171     if(*rest == '\0') return report_bad_resultstr();
172
173     *devicename = newstralloc(*devicename, rest);
174     return 0;
175 }
176
177
178 /*
179  * This function is somewhat equal to changer_info with one additional
180  * parameter, to get information, if the changer is able to search for
181  * tapelabels himself. E.g. Barcodereader
182  * The changer_script answers with an additional parameter, if it is able
183  * to search. This one should be 1, if it is able to search, and 0 if it
184  * knows about the extension. If the additional answer is omitted, the
185  * changer is not able to search for a tape. 
186  */
187
188 int
189 changer_query(
190     int *       nslotsp,
191     char **     curslotstr,
192     int *       backwardsp,
193     int *       searchable)
194 {
195     char *rest;
196     int rc;
197
198     rc = run_changer_command("-info", (char *) NULL, curslotstr, &rest);
199     if(rc) return rc;
200
201     dbprintf(("changer_query: changer return was %s\n",rest));
202     if (sscanf(rest, "%d %d %d", nslotsp, backwardsp, searchable) != 3) {
203       if (sscanf(rest, "%d %d", nslotsp, backwardsp) != 2) {
204         return report_bad_resultstr();
205       } else {
206         *searchable = 0;
207       }
208     }
209     dbprintf(("changer_query: searchable = %d\n",*searchable));
210     return 0;
211 }
212
213 int
214 changer_info(
215     int *       nslotsp,
216     char **     curslotstr,
217     int *       backwardsp)
218 {
219     char *rest;
220     int rc;
221
222     rc = run_changer_command("-info", (char *) NULL, curslotstr, &rest);
223     if(rc) return rc;
224
225     if (sscanf(rest, "%d %d", nslotsp, backwardsp) != 2) {
226         return report_bad_resultstr();
227     }
228     return 0;
229 }
230
231
232 /* ---------------------------- */
233
234 /*
235  * This function first uses searchlabel and changer_search, if
236  * the library is able to find a tape itself. If it is not, or if 
237  * the tape could not be found, then the normal scan is done.
238  *
239  * See interface documentation in changer.h.
240  */
241
242 void
243 changer_find(
244      void *     user_data,
245      int        (*user_init)(void *, int, int, int, int),
246      int        (*user_slot)(void *, int, char *, char *),
247      char *     searchlabel)
248 {
249     char *slotstr, *device = NULL, *curslotstr = NULL;
250     int nslots, checked, backwards, rc, done, searchable;
251
252     rc = changer_query(&nslots, &curslotstr, &backwards, &searchable);
253
254     if (rc != 0) {
255         /* Problem with the changer script. Bail. */
256         fprintf(stderr, "Changer problem: %s\n", changer_resultstr);
257         return;
258     }
259
260     done = user_init(user_data, rc, nslots, backwards, searchable);
261     amfree(curslotstr);
262    
263     if (searchlabel != NULL)
264     {
265       dbprintf(("changer_find: looking for %s changer is searchable = %d\n",
266                 searchlabel, searchable));
267     } else {
268       dbprintf(("changer_find: looking for NULL changer is searchable = %d\n",
269                 searchable));
270     }
271
272     if ((searchlabel!=NULL) && searchable && !done){
273       rc=changer_search(searchlabel, &curslotstr, &device);
274       if(rc == 0)
275         done = user_slot(user_data, rc, curslotstr, device);
276     }
277  
278     slotstr = "current";
279     checked = 0;
280
281     while(!done && checked < nslots) {
282         rc = changer_loadslot(slotstr, &curslotstr, &device);
283         if(rc > 0)
284             done = user_slot(user_data, rc, curslotstr, device);
285         else if(!done)
286             done = user_slot(user_data, 0,  curslotstr, device);
287         amfree(curslotstr);
288         amfree(device);
289
290         checked += 1;
291         slotstr = "next";
292     }
293 }
294
295 /* ---------------------------- */
296
297 void
298 changer_current(
299     void *      user_data,
300     int         (*user_init)(void *, int, int, int, int),
301     int         (*user_slot)(void *, int, char *, char *))
302 {
303     char *device = NULL, *curslotstr = NULL;
304     int nslots, backwards, rc, done, searchable;
305
306     rc = changer_query(&nslots, &curslotstr, &backwards, &searchable);
307     done = user_init(user_data, rc, nslots, backwards, searchable);
308     amfree(curslotstr);
309
310     rc = changer_loadslot("current", &curslotstr, &device);
311     if(rc > 0) {
312         done = user_slot(user_data, rc, curslotstr, device);
313     } else if(!done) {
314         done = user_slot(user_data, 0,  curslotstr, device);
315     }
316     amfree(curslotstr);
317     amfree(device);
318 }
319
320 /* ---------------------------- */
321
322 static int
323 changer_command(
324      char *cmd,
325      char *arg)
326 {
327     int fd[2];
328     amwait_t wait_exitcode = 1;
329     int exitcode;
330     char num1[NUM_STR_SIZE];
331     char num2[NUM_STR_SIZE];
332     char *cmdstr;
333     pid_t pid, changer_pid = 0;
334
335     if (*tapechanger != '/') {
336         tapechanger = vstralloc(libexecdir, "/", tapechanger, versionsuffix(),
337                                 NULL);
338         malloc_mark(tapechanger);
339     }
340     cmdstr = vstralloc(tapechanger, " ",
341                        cmd, arg ? " " : "", 
342                        arg ? arg : "",
343                        NULL);
344
345     if(changer_debug) {
346         fprintf(stderr, "changer: opening pipe to: %s\n", cmdstr);
347         fflush(stderr);
348     }
349
350     amfree(changer_resultstr);
351
352     if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) == -1) {
353         changer_resultstr = vstralloc ("<error> ",
354                                        "could not create pipe for \"",
355                                        cmdstr,
356                                        "\": ",
357                                        strerror(errno),
358                                        NULL);
359         exitcode = 2;
360         goto failed;
361     }
362
363     /* make sure fd[0] != 1 */
364     if(fd[0] == 1) {
365         int a = dup(fd[0]);
366         close(fd[0]);
367         fd[0] = a;
368     }
369
370     if(fd[0] < 0 || fd[0] >= (int)FD_SETSIZE) {
371         snprintf(num1, SIZEOF(num1), "%d", fd[0]);
372         snprintf(num2, SIZEOF(num2), "%d", FD_SETSIZE-1);
373         changer_resultstr = vstralloc ("<error> ",
374                                        "could not create pipe for \"",
375                                        cmdstr,
376                                        "\": ",
377                                        "socketpair 0: descriptor ",
378                                        num1,
379                                        " out of range ( .. ",
380                                        num2,
381                                        ")",
382                                        NULL);
383         exitcode = 2;
384         goto done;
385     }
386     if(fd[1] < 0 || fd[1] >= (int)FD_SETSIZE) {
387         snprintf(num1, SIZEOF(num1), "%d", fd[1]);
388         snprintf(num2, SIZEOF(num2), "%d", FD_SETSIZE-1);
389         changer_resultstr = vstralloc ("<error> ",
390                                        "could not create pipe for \"",
391                                        cmdstr,
392                                        "\": ",
393                                        "socketpair 1: descriptor ",
394                                        num1,
395                                        " out of range ( .. ",
396                                        num2,
397                                        ")",
398                                        NULL);
399         exitcode = 2;
400         goto done;
401     }
402
403     switch(changer_pid = fork()) {
404     case -1:
405         changer_resultstr = vstralloc ("<error> ",
406                                        "could not fork for \"",
407                                        cmdstr,
408                                        "\": ",
409                                        strerror(errno),
410                                        NULL);
411         exitcode = 2;
412         goto done;
413     case 0:
414         if(dup2(fd[1], 1) == -1 || dup2(fd[1], 2) == -1) {
415             changer_resultstr = vstralloc ("<error> ",
416                                            "could not open pipe to \"",
417                                            cmdstr,
418                                            "\": ",
419                                            strerror(errno),
420                                            NULL);
421             (void)fullwrite(fd[1], changer_resultstr, strlen(changer_resultstr));
422             exit(1);
423         }
424         aclose(fd[0]);
425         aclose(fd[1]);
426         if(config_dir && chdir(config_dir) == -1) {
427             changer_resultstr = vstralloc ("<error> ",
428                                            "could not cd to \"",
429                                            config_dir,
430                                            "\": ",
431                                            strerror(errno),
432                                            NULL);
433             (void)fullwrite(2, changer_resultstr, strlen(changer_resultstr));
434             exit(1);
435         }
436         if(arg) {
437             execle(tapechanger, tapechanger, cmd, arg, (char *)NULL,
438                    safe_env());
439         } else {
440             execle(tapechanger, tapechanger, cmd, (char *)NULL, safe_env());
441         }
442         changer_resultstr = vstralloc ("<error> ",
443                                        "could not exec \"",
444                                        tapechanger,
445                                        "\": ",
446                                        strerror(errno),
447                                        NULL);
448         (void)fullwrite(2, changer_resultstr, strlen(changer_resultstr));
449         exit(1);
450     default:
451         aclose(fd[1]);
452     }
453
454     if((changer_resultstr = areads(fd[0])) == NULL) {
455         changer_resultstr = vstralloc ("<error> ",
456                                        "could not read result from \"",
457                                        tapechanger,
458                                        errno ? "\": " : "\"",
459                                        errno ? strerror(errno) : "",
460                                        NULL);
461     }
462
463     while(1) {
464         if ((pid = wait(&wait_exitcode)) == -1) {
465             if(errno == EINTR) {
466                 continue;
467             } else {
468                 changer_resultstr = vstralloc ("<error> ",
469                                                "wait for \"",
470                                                tapechanger,
471                                                "\" failed: ",
472                                                strerror(errno),
473                                                NULL);
474                 exitcode = 2;
475                 goto done;
476             }
477         } else if (pid != changer_pid) {
478             snprintf(num1, SIZEOF(num1), "%ld", (long)pid);
479             changer_resultstr = vstralloc ("<error> ",
480                                            "wait for \"",
481                                            tapechanger,
482                                            "\" returned unexpected pid ",
483                                            num1,
484                                            NULL);
485             exitcode = 2;
486             goto done;
487         } else {
488             break;
489         }
490     }
491
492     /* mark out-of-control changers as fatal error */
493     if(WIFSIGNALED(wait_exitcode)) {
494         snprintf(num1, SIZEOF(num1), "%d", WTERMSIG(wait_exitcode));
495         changer_resultstr = newvstralloc (changer_resultstr,
496                                           "<error> ",
497                                           changer_resultstr,
498                                           " (got signal ", num1, ")",
499                                           NULL);
500         exitcode = 2;
501     } else {
502         exitcode = WEXITSTATUS(wait_exitcode);
503     }
504
505 done:
506     aclose(fd[0]);
507     aclose(fd[1]);
508
509 failed:
510     if (exitcode != 0) {
511         dbprintf(("changer: got exit: %d str: %s\n", exitcode, changer_resultstr)); 
512     }
513
514     amfree(cmdstr);
515
516     return exitcode;
517 }
518
519
520 /*
521  * This function commands the changerscript to look for a tape named
522  * searchlabel. If is found, the changerscript answers with the device,
523  * in which the tape can be accessed.
524  */
525
526 int
527 changer_search(
528     char *      searchlabel,
529     char **     outslotstr,
530     char **     devicename)
531 {
532     char *rest;
533     int rc;
534
535     dbprintf(("changer_search: %s\n",searchlabel));
536     rc = run_changer_command("-search", searchlabel, outslotstr, &rest);
537     if(rc) return rc;
538
539     if(*rest == '\0') return report_bad_resultstr();
540
541     *devicename = newstralloc(*devicename, rest);
542     return 0;
543 }
544
545
546 /*
547  * Because barcodelabel are short, and may not be the same as the 
548  * amandalabels, the changerscript should be informed, which tapelabel
549  * is associated with a tape. This function should be called after 
550  * giving a label for a tape. (Maybe also, when the label and the associated
551  * slot is known. e.g. during library scan.
552  */
553
554 int
555 changer_label(
556     char *      slotsp, 
557     char *      labelstr)
558 {
559     int rc;
560     char *rest=NULL;
561     char *slotstr;
562     char *curslotstr = NULL;
563     int nslots, backwards, searchable;
564
565     dbprintf(("changer_label: %s for slot %s\n",labelstr,slotsp));
566     rc = changer_query(&nslots, &curslotstr, &backwards,&searchable);
567     amfree(curslotstr);
568
569     if ((rc == 0) && (searchable == 1)){
570         dbprintf(("changer_label: calling changer -label %s\n",labelstr));
571         rc = run_changer_command("-label", labelstr, &slotstr, &rest);
572         amfree(slotstr);
573     }
574
575     if(rc) return rc;
576
577     return 0;
578 }