Add nbdkit bmaplogger plugin.
[virt-bmap.git] / logger.c
1 /* virt-bmap logger plugin
2  * Copyright (C) 2014 Red Hat Inc.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17  */
18
19 /* This is an nbdkit plugin which observes guest activity and displays
20  * what files are being accessed.
21  */
22
23 #include <config.h>
24
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <stdint.h>
28 #include <inttypes.h>
29 #include <string.h>
30 #include <fcntl.h>
31 #include <unistd.h>
32 #include <errno.h>
33 #include <sys/types.h>
34 #include <sys/stat.h>
35 #include <assert.h>
36
37 #include <nbdkit-plugin.h>
38
39 #include "cleanups.h"
40 #include "ranges.h"
41
42 static char *file = NULL;
43 static char *bmap = NULL;
44 static char *logfile = NULL;
45 static void *ranges = NULL;
46 static FILE *logfp = NULL;
47
48 static int
49 logger_config (const char *key, const char *value)
50 {
51   if (strcmp (key, "file") == 0) {
52     free (file);
53     file = nbdkit_absolute_path (value);
54     if (file == NULL)
55       return -1;
56   }
57   else if (strcmp (key, "bmap") == 0) {
58     free (bmap);
59     bmap = nbdkit_absolute_path (value);
60     if (bmap == NULL)
61       return -1;
62   }
63   else if (strcmp (key, "logfile") == 0) {
64     free (logfile);
65     logfile = nbdkit_absolute_path (value);
66     if (logfile == NULL)
67       return -1;
68   }
69   else {
70     nbdkit_error ("unknown parameter '%s'", key);
71     return -1;
72   }
73
74   return 0;
75 }
76
77 static int
78 logger_config_complete (void)
79 {
80   FILE *fp;
81   const char *bmap_file = bmap ? bmap : "bmap";
82   CLEANUP_FREE char *line = NULL;
83   size_t alloc;
84   ssize_t len;
85   size_t count;
86
87   if (!file) {
88     nbdkit_error ("missing 'file=...' parameter, see virt-bmap(1)");
89     return -1;
90   }
91
92   ranges = new_ranges ();
93
94   /* Load ranges from bmap file. */
95   fp = fopen (bmap_file, "r");
96   if (fp == NULL) {
97     nbdkit_error ("open: %s: %m", bmap_file);
98     return -1;
99   }
100
101   count = 0;
102   while (errno = 0, (len = getline (&line, &alloc, fp)) != -1) {
103     uint64_t start, end;
104     int object_offset;
105     const char *object;
106
107     if (len > 0 && line[len-1] == '\n')
108       line[--len] = '\0';
109
110     if (sscanf (line, "1 %" SCNx64 " %" SCNx64 " %n",
111                 &start, &end, &object_offset) >= 2) {
112       count++;
113       object = line + object_offset;
114       insert_range (ranges, start, end, object);
115     }
116   }
117
118   if (errno) {
119     nbdkit_error ("getline: %s: %m", bmap_file);
120     fclose (fp);
121     return -1;
122   }
123
124   if (count == 0) {
125     nbdkit_error ("no ranges were read from block map file: %s", bmap_file);
126     return -1;
127   }
128
129   if (fclose (fp) == -1) {
130     nbdkit_error ("fclose: %s: %m", bmap_file);
131     return -1;
132   }
133
134   /* Set up log file. */
135   if (logfile) {
136     logfp = fopen (logfile, "w");
137     if (logfp == NULL) {
138       nbdkit_error ("cannot open log file: %s: %m", logfile);
139       return -1;
140     }
141   }
142
143   return 0;
144 }
145
146 static void
147 logger_unload (void)
148 {
149   if (logfp)
150     fclose (logfp);
151
152   free_ranges (ranges);
153   free (logfile);
154   free (bmap);
155   free (file);
156 }
157
158 #define logger_config_help                                        \
159   "file=<DISK>         Input disk filename\n"                     \
160   "logfile=<OUTPUT>    Log file (default: stdout)\n"              \
161   "bmap=<BMAP>         Block map (default: \"bmap\")"             \
162
163 /* See log_operation below. */
164 struct operation {
165   int is_read;
166   int priority;
167   uint64_t start;
168   uint64_t end;
169   const char *object;
170 };
171
172 /* The per-connection handle. */
173 struct handle {
174   int fd;
175
176   /* See log_operation below. */
177   struct operation last;
178   struct operation current;
179 };
180
181 /* Create the per-connection handle. */
182 static void *
183 logger_open (int readonly)
184 {
185   struct handle *h;
186   int flags;
187
188   h = malloc (sizeof *h);
189   if (h == NULL) {
190     nbdkit_error ("malloc: %m");
191     return NULL;
192   }
193
194   memset (&h->last, 0, sizeof h->last);
195   memset (&h->current, 0, sizeof h->current);
196
197   flags = O_CLOEXEC|O_NOCTTY;
198   if (readonly)
199     flags |= O_RDONLY;
200   else
201     flags |= O_RDWR;
202
203   h->fd = open (file, flags);
204   if (h->fd == -1) {
205     nbdkit_error ("open: %s: %m", file);
206     free (h);
207     return NULL;
208   }
209
210   return h;
211 }
212
213 /* Free up the per-connection handle. */
214 static void
215 logger_close (void *handle)
216 {
217   struct handle *h = handle;
218
219   close (h->fd);
220   free (h);
221 }
222
223 #define THREAD_MODEL NBDKIT_THREAD_MODEL_SERIALIZE_REQUESTS
224
225 /* Get the file size. */
226 static int64_t
227 logger_get_size (void *handle)
228 {
229   struct handle *h = handle;
230   struct stat statbuf;
231
232   if (fstat (h->fd, &statbuf) == -1) {
233     nbdkit_error ("stat: %m");
234     return -1;
235   }
236
237   return statbuf.st_size;
238 }
239
240 static int
241 priority_of_object (const char *object)
242 {
243   switch (object[0]) {
244   case 'v': return 1;           /* whole device (least important) */
245   case 'p': return 2;
246   case 'l': return 3;
247   case 'd': return 4;
248   case 'f': return 5;           /* file (most important) */
249   default: return 6;
250   }
251 }
252
253 /* Callback from find_range.  Save the highest priority object into
254  * the handle for later printing.
255  */
256 static void
257 log_callback (uint64_t start, uint64_t end, const char *object, void *opaque)
258 {
259   struct handle *h = opaque;
260   int priority = priority_of_object (object);
261
262   if (priority > h->current.priority) {
263     h->current.priority = priority;
264     h->current.start = start;
265     h->current.end = end;
266     h->current.object = object;
267   }
268 }
269
270 static void
271 log_operation (struct handle *h, uint64_t offset, uint32_t count, int is_read)
272 {
273   h->current.is_read = is_read;
274   h->current.priority = 0;
275   find_range (ranges, offset, offset+count, log_callback, h);
276
277   if (h->current.priority > 0) {
278     FILE *fp = logfp ? logfp : stdout;
279
280     if (h->current.priority != h->last.priority ||
281         h->current.is_read != h->last.is_read ||
282         h->last.object == NULL ||
283         strcmp (h->current.object, h->last.object) != 0) {
284       fprintf (fp,
285                "\n"
286                "%s %s\n",
287                is_read ? "read" : "write", h->current.object);
288
289       h->last = h->current;
290     }
291
292     /* It would be nice to print an offset relative to the current
293      * object here, but that's not possible since we don't have the
294      * information about precisely what file offsets map to what
295      * blocks.
296      */
297     fprintf (fp, " %" PRIx64 "-%" PRIx64, offset, offset+count);
298     fflush (fp);
299   }
300 }
301
302 /* Read data from the file. */
303 static int
304 logger_pread (void *handle, void *buf, uint32_t count, uint64_t offset)
305 {
306   struct handle *h = handle;
307
308   log_operation (h, offset, count, 1);
309
310   while (count > 0) {
311     ssize_t r = pread (h->fd, buf, count, offset);
312     if (r == -1) {
313       nbdkit_error ("pread: %m");
314       return -1;
315     }
316     if (r == 0) {
317       nbdkit_error ("pread: unexpected end of file");
318       return -1;
319     }
320     buf += r;
321     count -= r;
322     offset += r;
323   }
324
325   return 0;
326 }
327
328 /* Write data to the file. */
329 static int
330 logger_pwrite (void *handle, const void *buf, uint32_t count, uint64_t offset)
331 {
332   struct handle *h = handle;
333
334   log_operation (h, offset, count, 0);
335
336   while (count > 0) {
337     ssize_t r = pwrite (h->fd, buf, count, offset);
338     if (r == -1) {
339       nbdkit_error ("pwrite: %m");
340       return -1;
341     }
342     buf += r;
343     count -= r;
344     offset += r;
345   }
346
347   return 0;
348 }
349
350 /* Flush the file to disk. */
351 static int
352 logger_flush (void *handle)
353 {
354   struct handle *h = handle;
355
356   if (fdatasync (h->fd) == -1) {
357     nbdkit_error ("fdatasync: %m");
358     return -1;
359   }
360
361   return 0;
362 }
363
364 /* Register the nbdkit plugin. */
365
366 static struct nbdkit_plugin plugin = {
367   .name              = "bmaplogger",
368   .version           = PACKAGE_VERSION,
369   .config            = logger_config,
370   .config_help       = logger_config_help,
371   .config_complete   = logger_config_complete,
372   .open              = logger_open,
373   .close             = logger_close,
374   .get_size          = logger_get_size,
375   .pread             = logger_pread,
376   .unload            = logger_unload,
377 };
378
379 NBDKIT_REGISTER_PLUGIN(plugin)