1 /* nbdkit
2  * Copyright (C) 2013 Red Hat Inc.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  * * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *
11  * * Redistributions in binary form must reproduce the above copyright
12  * notice, this list of conditions and the following disclaimer in the
13  * documentation and/or other materials provided with the distribution.
14  *
15  * * Neither the name of Red Hat nor the names of its contributors may be
16  * used to endorse or promote products derived from this software without
17  * specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
21  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
22  * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR
23  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
26  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
27  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
29  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  */
32 
33 #include <config.h>
34 
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <stdarg.h>
38 #include <string.h>
39 #include <unistd.h>
40 #include <sys/types.h>
41 #include <sys/wait.h>
42 #include <signal.h>
43 #include <errno.h>
44 #include <assert.h>
45 
46 #include "test.h"
47 
48 /* 'test_start_nbdkit' below makes assumptions about the format of
49  * these strings.
50  */
51 #define TEST_NBDKIT_TEMPLATE "/tmp/nbdkitXXXXXX"
52 struct test_nbdkit {
53   char tmpdir[          17     + 1]; /*          template,          NUL */
54   char sockpath[        17 + 5 + 1]; /*          template, "/sock", NUL */
55   char unixsockpath[5 + 17 + 5 + 1]; /* "unix:", template, "/sock", NUL */
56   char pidpath[         17 + 4 + 1]; /*          template, "/pid",  NUL */
57   pid_t pid;
58   struct test_nbdkit *next;
59 };
60 const struct test_nbdkit template = {
61   .tmpdir =               TEST_NBDKIT_TEMPLATE,
62   .sockpath =             TEST_NBDKIT_TEMPLATE "/sock",
63   .unixsockpath = "unix:" TEST_NBDKIT_TEMPLATE "/sock",
64   .pidpath =              TEST_NBDKIT_TEMPLATE "/pid",
65 };
66 
67 static struct test_nbdkit *head;
68 
69 pid_t pid = 0;
70 const char *sock = NULL;
71 const char *server[2] = { NULL, NULL };
72 
73 static void
cleanup(void)74 cleanup (void)
75 {
76   int status;
77   struct test_nbdkit *next;
78   const char *s;
79 
80   while (head) {
81     if (head->pid > 0) {
82       assert (!pid || pid == head->pid);
83       pid = 0;
84 
85       /* This improves the stability when running the tests under
86        * valgrind.  We have to wait a little for nbdkit close
87        * callbacks to run.
88        */
89       s = getenv ("NBDKIT_VALGRIND");
90       if (s && strcmp (s, "1") == 0)
91         sleep (5);
92 
93       kill (head->pid, SIGTERM);
94 
95       /* Check the status of nbdkit is normal on exit. */
96       if (waitpid (head->pid, &status, 0) == -1) {
97         perror ("waitpid");
98         _exit (EXIT_FAILURE);
99       }
100       if (WIFEXITED (status) && WEXITSTATUS (status) != 0) {
101         _exit (WEXITSTATUS (status));
102       }
103       if (WIFSIGNALED (status)) {
104         /* Note that nbdkit is supposed to catch the signal we send and
105          * exit cleanly, so the following shouldn't happen.
106          */
107         fprintf (stderr, "nbdkit terminated by signal %d\n", WTERMSIG (status));
108         _exit (EXIT_FAILURE);
109       }
110       if (WIFSTOPPED (status)) {
111         fprintf (stderr, "nbdkit stopped by signal %d\n", WSTOPSIG (status));
112         _exit (EXIT_FAILURE);
113       }
114     }
115 
116     unlink (head->pidpath);
117     unlink (head->sockpath);
118     rmdir (head->tmpdir);
119 
120     next = head->next;
121     free (head);
122     head = next;
123   }
124 }
125 
126 int
test_start_nbdkit(const char * arg,...)127 test_start_nbdkit (const char *arg, ...)
128 {
129   size_t i, len;
130   struct test_nbdkit *kit = malloc (sizeof *kit);
131 
132   if (!kit) {
133     perror ("malloc");
134     return -1;
135   }
136   *kit = template;
137   if (mkdtemp (kit->tmpdir) == NULL) {
138     perror ("mkdtemp");
139     free (kit);
140     return -1;
141   }
142   len = strlen (kit->tmpdir);
143   memcpy (kit->sockpath, kit->tmpdir, len);
144   memcpy (kit->unixsockpath+5, kit->tmpdir, len);
145   memcpy (kit->pidpath, kit->tmpdir, len);
146 
147   kit->pid = fork ();
148   if (kit->pid == 0) {               /* Child (nbdkit). */
149     const char *p;
150 #define MAX_ARGS 64
151     const char *argv[MAX_ARGS+1];
152     va_list args;
153 
154     argv[0] = "nbdkit";
155     argv[1] = "-U";
156     argv[2] = kit->sockpath;
157     argv[3] = "-P";
158     argv[4] = kit->pidpath;
159     argv[5] = "-f";
160     argv[6] = "-v";
161     argv[7] = arg;
162     i = 8;
163 
164     va_start (args, arg);
165     while ((p = va_arg (args, const char *)) != NULL) {
166       if (i >= MAX_ARGS)
167         abort ();
168       argv[i] = p;
169       ++i;
170     }
171     va_end (args);
172     argv[i] = NULL;
173 
174     execvp ("nbdkit", (char **) argv);
175     perror ("exec: nbdkit");
176     _exit (EXIT_FAILURE);
177   }
178 
179   /* Ensure nbdkit is killed and temporary files are deleted when the
180    * main program exits.
181    */
182   if (head)
183     kit->next = head;
184   else
185     atexit (cleanup);
186   head = kit;
187   pid = kit->pid;
188   sock = kit->sockpath;
189   server[0] = kit->unixsockpath;
190 
191   /* Wait for the pidfile to turn up, which indicates that nbdkit has
192    * started up successfully and is ready to serve requests.  However
193    * if 'pid' exits in this time it indicates a failure to start up.
194    * Also there is a timeout in case nbdkit hangs.
195    */
196   for (i = 0; i < NBDKIT_START_TIMEOUT; ++i) {
197     if (waitpid (pid, NULL, WNOHANG) == pid)
198       goto early_exit;
199 
200     if (kill (pid, 0) == -1) {
201       if (errno == ESRCH) {
202       early_exit:
203         fprintf (stderr,
204                  "%s FAILED: nbdkit exited before starting to serve files\n",
205                  program_name);
206         pid = 0;
207         return -1;
208       }
209       perror ("kill");
210     }
211 
212     if (access (kit->pidpath, F_OK) == 0)
213       break;
214 
215     sleep (1);
216   }
217 
218   return 0;
219 }
220