Add section "Notes on disk format" to the manual.
[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   uint32_t count;
167   uint64_t offset;
168   int priority;
169   uint64_t start;
170   uint64_t end;
171   const char *object;
172 };
173
174 /* The per-connection handle. */
175 struct handle {
176   int fd;
177
178   /* See log_operation below. */
179   struct operation last;
180   struct operation current;
181 };
182
183 /* Create the per-connection handle. */
184 static void *
185 logger_open (int readonly)
186 {
187   struct handle *h;
188   int flags;
189
190   h = malloc (sizeof *h);
191   if (h == NULL) {
192     nbdkit_error ("malloc: %m");
193     return NULL;
194   }
195
196   memset (&h->last, 0, sizeof h->last);
197   memset (&h->current, 0, sizeof h->current);
198
199   flags = O_CLOEXEC|O_NOCTTY;
200   if (readonly)
201     flags |= O_RDONLY;
202   else
203     flags |= O_RDWR;
204
205   h->fd = open (file, flags);
206   if (h->fd == -1) {
207     nbdkit_error ("open: %s: %m", file);
208     free (h);
209     return NULL;
210   }
211
212   return h;
213 }
214
215 /* Free up the per-connection handle. */
216 static void
217 logger_close (void *handle)
218 {
219   struct handle *h = handle;
220
221   close (h->fd);
222   free (h);
223 }
224
225 #define THREAD_MODEL NBDKIT_THREAD_MODEL_SERIALIZE_REQUESTS
226
227 /* Get the file size. */
228 static int64_t
229 logger_get_size (void *handle)
230 {
231   struct handle *h = handle;
232   struct stat statbuf;
233
234   if (fstat (h->fd, &statbuf) == -1) {
235     nbdkit_error ("stat: %m");
236     return -1;
237   }
238
239   return statbuf.st_size;
240 }
241
242 static int
243 priority_of_object (const char *object)
244 {
245   switch (object[0]) {
246   case 'v': return 1;           /* whole device (least important) */
247   case 'p': return 2;
248   case 'l': return 3;
249   case 'd': return 4;
250   case 'f': return 5;           /* file (most important) */
251   default: return 6;
252   }
253 }
254
255 /* Callback from find_range.  Save the highest priority object into
256  * the handle for later printing.
257  */
258 static void
259 log_callback (uint64_t start, uint64_t end, const char *object, void *opaque)
260 {
261   struct handle *h = opaque;
262   int priority = priority_of_object (object);
263
264   if (priority > h->current.priority) {
265     h->current.priority = priority;
266     h->current.start = start;
267     h->current.end = end;
268     h->current.object = object;
269   }
270 }
271
272 static void
273 log_operation (struct handle *h, uint64_t offset, uint32_t count, int is_read)
274 {
275   /* Because Boost interval_map is really bloody slow, implement a
276    * shortcut here.  We can remove this once Boost performance
277    * problems have been fixed.
278    */
279   if (h->current.is_read == is_read &&
280       h->current.count == count &&
281       h->current.offset == offset)
282     goto skip_find_range;
283
284   h->current.is_read = is_read;
285   h->current.count = count;
286   h->current.offset = offset;
287   h->current.priority = 0;
288   find_range (ranges, offset, offset+count, log_callback, h);
289  skip_find_range:
290
291   if (h->current.priority > 0) {
292     FILE *fp = logfp ? logfp : stdout;
293
294     if (h->current.priority != h->last.priority ||
295         h->current.is_read != h->last.is_read ||
296         h->last.object == NULL ||
297         strcmp (h->current.object, h->last.object) != 0) {
298       fprintf (fp,
299                "\n"
300                "%s %s\n",
301                is_read ? "read" : "write", h->current.object);
302
303       h->last = h->current;
304     }
305
306     /* It would be nice to print an offset relative to the current
307      * object here, but that's not possible since we don't have the
308      * information about precisely what file offsets map to what
309      * blocks.
310      */
311     fprintf (fp, " %" PRIx64 "-%" PRIx64, offset, offset+count);
312     fflush (fp);
313   }
314 }
315
316 /* Read data from the file. */
317 static int
318 logger_pread (void *handle, void *buf, uint32_t count, uint64_t offset)
319 {
320   struct handle *h = handle;
321
322   log_operation (h, offset, count, 1);
323
324   while (count > 0) {
325     ssize_t r = pread (h->fd, buf, count, offset);
326     if (r == -1) {
327       nbdkit_error ("pread: %m");
328       return -1;
329     }
330     if (r == 0) {
331       nbdkit_error ("pread: unexpected end of file");
332       return -1;
333     }
334     buf += r;
335     count -= r;
336     offset += r;
337   }
338
339   return 0;
340 }
341
342 /* Write data to the file. */
343 static int
344 logger_pwrite (void *handle, const void *buf, uint32_t count, uint64_t offset)
345 {
346   struct handle *h = handle;
347
348   log_operation (h, offset, count, 0);
349
350   while (count > 0) {
351     ssize_t r = pwrite (h->fd, buf, count, offset);
352     if (r == -1) {
353       nbdkit_error ("pwrite: %m");
354       return -1;
355     }
356     buf += r;
357     count -= r;
358     offset += r;
359   }
360
361   return 0;
362 }
363
364 /* Flush the file to disk. */
365 static int
366 logger_flush (void *handle)
367 {
368   struct handle *h = handle;
369
370   if (fdatasync (h->fd) == -1) {
371     nbdkit_error ("fdatasync: %m");
372     return -1;
373   }
374
375   return 0;
376 }
377
378 /* Register the nbdkit plugin. */
379
380 static struct nbdkit_plugin plugin = {
381   .name              = "bmaplogger",
382   .version           = PACKAGE_VERSION,
383   .config            = logger_config,
384   .config_help       = logger_config_help,
385   .config_complete   = logger_config_complete,
386   .open              = logger_open,
387   .close             = logger_close,
388   .get_size          = logger_get_size,
389   .pread             = logger_pread,
390   .unload            = logger_unload,
391 };
392
393 NBDKIT_REGISTER_PLUGIN(plugin)