Imported Upstream version 2.5.1p1
[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.2.1 2006/09/27 14:04:27 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(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     int fd_to_close[4], *pfd_to_close = fd_to_close;
335
336     if (*tapechanger != '/') {
337         tapechanger = vstralloc(libexecdir, "/", tapechanger, versionsuffix(),
338                                 NULL);
339         malloc_mark(tapechanger);
340     }
341     cmdstr = vstralloc(tapechanger, " ",
342                        cmd, arg ? " " : "", 
343                        arg ? arg : "",
344                        NULL);
345
346     if(changer_debug) {
347         fprintf(stderr, "changer: opening pipe to: %s\n", cmdstr);
348         fflush(stderr);
349     }
350
351     amfree(changer_resultstr);
352
353     if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) == -1) {
354         changer_resultstr = vstralloc ("<error> ",
355                                        "could not create pipe for \"",
356                                        cmdstr,
357                                        "\": ",
358                                        strerror(errno),
359                                        NULL);
360         exitcode = 2;
361         goto failed;
362     }
363
364     /* make sure fd[0] > 2  && fd[1] > 2 */
365     pfd_to_close = fd_to_close;
366     while(fd[0] <= 2) {
367         int a = dup(fd[0]);
368         *pfd_to_close++ = fd[0];
369         fd[0] = a;
370     }
371     while(fd[1] <= 2) {
372         int a = dup(fd[1]);
373         *pfd_to_close++ = fd[1];
374         fd[1] = a;
375     }
376     while (pfd_to_close > fd_to_close) {
377         close(*--pfd_to_close);
378     }
379
380     if(fd[0] < 0 || fd[0] >= (int)FD_SETSIZE) {
381         snprintf(num1, SIZEOF(num1), "%d", fd[0]);
382         snprintf(num2, SIZEOF(num2), "%d", FD_SETSIZE-1);
383         changer_resultstr = vstralloc ("<error> ",
384                                        "could not create pipe for \"",
385                                        cmdstr,
386                                        "\": ",
387                                        "socketpair 0: descriptor ",
388                                        num1,
389                                        " out of range ( .. ",
390                                        num2,
391                                        ")",
392                                        NULL);
393         exitcode = 2;
394         goto done;
395     }
396     if(fd[1] < 0 || fd[1] >= (int)FD_SETSIZE) {
397         snprintf(num1, SIZEOF(num1), "%d", fd[1]);
398         snprintf(num2, SIZEOF(num2), "%d", FD_SETSIZE-1);
399         changer_resultstr = vstralloc ("<error> ",
400                                        "could not create pipe for \"",
401                                        cmdstr,
402                                        "\": ",
403                                        "socketpair 1: descriptor ",
404                                        num1,
405                                        " out of range ( .. ",
406                                        num2,
407                                        ")",
408                                        NULL);
409         exitcode = 2;
410         goto done;
411     }
412
413     switch(changer_pid = fork()) {
414     case -1:
415         changer_resultstr = vstralloc ("<error> ",
416                                        "could not fork for \"",
417                                        cmdstr,
418                                        "\": ",
419                                        strerror(errno),
420                                        NULL);
421         exitcode = 2;
422         goto done;
423     case 0:
424         if(dup2(fd[1], 1) == -1 || dup2(fd[1], 2) == -1) {
425             changer_resultstr = vstralloc ("<error> ",
426                                            "could not open pipe to \"",
427                                            cmdstr,
428                                            "\": ",
429                                            strerror(errno),
430                                            NULL);
431             (void)fullwrite(fd[1], changer_resultstr, strlen(changer_resultstr));
432             exit(1);
433         }
434         aclose(fd[0]);
435         aclose(fd[1]);
436         if(config_dir && chdir(config_dir) == -1) {
437             changer_resultstr = vstralloc ("<error> ",
438                                            "could not cd to \"",
439                                            config_dir,
440                                            "\": ",
441                                            strerror(errno),
442                                            NULL);
443             (void)fullwrite(2, changer_resultstr, strlen(changer_resultstr));
444             exit(1);
445         }
446         if(arg) {
447             execle(tapechanger, tapechanger, cmd, arg, (char *)NULL,
448                    safe_env());
449         } else {
450             execle(tapechanger, tapechanger, cmd, (char *)NULL, safe_env());
451         }
452         changer_resultstr = vstralloc ("<error> ",
453                                        "could not exec \"",
454                                        tapechanger,
455                                        "\": ",
456                                        strerror(errno),
457                                        NULL);
458         (void)fullwrite(2, changer_resultstr, strlen(changer_resultstr));
459         exit(1);
460     default:
461         aclose(fd[1]);
462     }
463
464     if((changer_resultstr = areads(fd[0])) == NULL) {
465         changer_resultstr = vstralloc ("<error> ",
466                                        "could not read result from \"",
467                                        tapechanger,
468                                        errno ? "\": " : "\"",
469                                        errno ? strerror(errno) : "",
470                                        NULL);
471     }
472
473     while(1) {
474         if ((pid = wait(&wait_exitcode)) == -1) {
475             if(errno == EINTR) {
476                 continue;
477             } else {
478                 changer_resultstr = vstralloc ("<error> ",
479                                                "wait for \"",
480                                                tapechanger,
481                                                "\" failed: ",
482                                                strerror(errno),
483                                                NULL);
484                 exitcode = 2;
485                 goto done;
486             }
487         } else if (pid != changer_pid) {
488             snprintf(num1, SIZEOF(num1), "%ld", (long)pid);
489             changer_resultstr = vstralloc ("<error> ",
490                                            "wait for \"",
491                                            tapechanger,
492                                            "\" returned unexpected pid ",
493                                            num1,
494                                            NULL);
495             exitcode = 2;
496             goto done;
497         } else {
498             break;
499         }
500     }
501
502     /* mark out-of-control changers as fatal error */
503     if(WIFSIGNALED(wait_exitcode)) {
504         snprintf(num1, SIZEOF(num1), "%d", WTERMSIG(wait_exitcode));
505         changer_resultstr = newvstralloc (changer_resultstr,
506                                           "<error> ",
507                                           changer_resultstr,
508                                           " (got signal ", num1, ")",
509                                           NULL);
510         exitcode = 2;
511     } else {
512         exitcode = WEXITSTATUS(wait_exitcode);
513     }
514
515 done:
516     aclose(fd[0]);
517     aclose(fd[1]);
518
519 failed:
520     if (exitcode != 0) {
521         dbprintf(("changer: got exit: %d str: %s\n", exitcode, changer_resultstr)); 
522     }
523
524     amfree(cmdstr);
525
526     return exitcode;
527 }
528
529
530 /*
531  * This function commands the changerscript to look for a tape named
532  * searchlabel. If is found, the changerscript answers with the device,
533  * in which the tape can be accessed.
534  */
535
536 int
537 changer_search(
538     char *      searchlabel,
539     char **     outslotstr,
540     char **     devicename)
541 {
542     char *rest;
543     int rc;
544
545     dbprintf(("changer_search: %s\n",searchlabel));
546     rc = run_changer_command("-search", searchlabel, outslotstr, &rest);
547     if(rc) return rc;
548
549     if(*rest == '\0') return report_bad_resultstr();
550
551     *devicename = newstralloc(*devicename, rest);
552     return 0;
553 }
554
555
556 /*
557  * Because barcodelabel are short, and may not be the same as the 
558  * amandalabels, the changerscript should be informed, which tapelabel
559  * is associated with a tape. This function should be called after 
560  * giving a label for a tape. (Maybe also, when the label and the associated
561  * slot is known. e.g. during library scan.
562  */
563
564 int
565 changer_label(
566     char *      slotsp, 
567     char *      labelstr)
568 {
569     int rc;
570     char *rest=NULL;
571     char *slotstr;
572     char *curslotstr = NULL;
573     int nslots, backwards, searchable;
574
575     dbprintf(("changer_label: %s for slot %s\n",labelstr,slotsp));
576     rc = changer_query(&nslots, &curslotstr, &backwards,&searchable);
577     amfree(curslotstr);
578
579     if ((rc == 0) && (searchable == 1)){
580         dbprintf(("changer_label: calling changer -label %s\n",labelstr));
581         rc = run_changer_command("-label", labelstr, &slotstr, &rest);
582         amfree(slotstr);
583     }
584
585     if(rc) return rc;
586
587     return 0;
588 }