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