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