examples: Add virt-dhcp-address program.
[libguestfs.git] / examples / virt-dhcp-address.c
1 /* This is a more significant example of a tool which can grab the
2  * DHCP address from some types of virtual machine.  Since there are
3  * so many possible ways to do this, without clarity on which is the
4  * best way, I don't want to make this into an official virt tool.
5  *
6  * For more information, see:
7  *
8  * https://rwmj.wordpress.com/2010/10/26/tip-find-the-ip-address-of-a-virtual-machine/
9  * https://rwmj.wordpress.com/2011/03/30/tip-another-way-to-get-the-ip-address-of-a-virtual-machine/
10  */
11
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <string.h>
15 #include <errno.h>
16 #include <unistd.h>
17 #include <assert.h>
18
19 #include <guestfs.h>
20 #include <hivex.h>
21
22 static int compare_keys_len (const void *p1, const void *p2);
23 static size_t count_strings (char *const *argv);
24 static void free_strings (char **argv);
25 static void mount_disks (guestfs_h *g, char *root);
26 static void print_dhcp_address (guestfs_h *g, char *root);
27 static void print_dhcp_address_linux (guestfs_h *g, char *root, const char *logfile);
28 static void print_dhcp_address_windows (guestfs_h *g, char *root);
29
30 int
31 main (int argc, char *argv[])
32 {
33   guestfs_h *g;
34   size_t i;
35   char **roots, *root;
36
37   if (argc < 2) {
38     fprintf (stderr,
39              "Usage: virt-dhcp-address disk.img [disk.img [...]]\n"
40              "Note that all disks must come from a single virtual machine.\n");
41     exit (EXIT_FAILURE);
42   }
43
44   g = guestfs_create ();
45   if (g == NULL) {
46     perror ("failed to create libguestfs handle");
47     exit (EXIT_FAILURE);
48   }
49
50   for (i = 1; i < (size_t) argc; ++i) {
51     /* Attach the disk image(s) read-only to libguestfs. */
52     if (guestfs_add_drive_opts (g, argv[i],
53                                 /* GUESTFS_ADD_DRIVE_OPTS_FORMAT, "raw", */
54                                 GUESTFS_ADD_DRIVE_OPTS_READONLY, 1,
55                                 -1) /* this marks end of optional arguments */
56         == -1)
57       exit (EXIT_FAILURE);
58   }
59
60   /* Run the libguestfs back-end. */
61   if (guestfs_launch (g) == -1)
62     exit (EXIT_FAILURE);
63
64   /* Ask libguestfs to inspect for operating systems. */
65   roots = guestfs_inspect_os (g);
66   if (roots == NULL)
67     exit (EXIT_FAILURE);
68   if (roots[0] == NULL) {
69     fprintf (stderr, "virt-dhcp-address: no operating systems found\n");
70     exit (EXIT_FAILURE);
71   }
72   if (count_strings (roots) > 1) {
73     fprintf (stderr, "virt-dhcp-address: multi-boot operating system\n");
74     exit (EXIT_FAILURE);
75   }
76
77   root = roots[0];
78
79   /* Mount up the guest's disks. */
80   mount_disks (g, root);
81
82   /* Print DHCP address. */
83   print_dhcp_address (g, root);
84
85   /* Close handle and exit. */
86   guestfs_close (g);
87   free_strings (roots);
88
89   exit (EXIT_SUCCESS);
90 }
91
92 static void
93 mount_disks (guestfs_h *g, char *root)
94 {
95   char **mountpoints;
96   size_t i;
97
98   /* Mount up the disks, like guestfish -i.
99    *
100    * Sort keys by length, shortest first, so that we end up
101    * mounting the filesystems in the correct order.
102    */
103   mountpoints = guestfs_inspect_get_mountpoints (g, root);
104   if (mountpoints == NULL)
105     exit (EXIT_FAILURE);
106
107   qsort (mountpoints, count_strings (mountpoints) / 2, 2 * sizeof (char *),
108          compare_keys_len);
109
110   for (i = 0; mountpoints[i] != NULL; i += 2) {
111     /* Ignore failures from this call, since bogus entries can
112      * appear in the guest's /etc/fstab.
113      */
114     guestfs_mount_ro (g, mountpoints[i+1], mountpoints[i]);
115   }
116
117   free_strings (mountpoints);
118 }
119
120 static void
121 print_dhcp_address (guestfs_h *g, char *root)
122 {
123   char *guest_type, *guest_distro;
124
125   /* Depending on the guest type, try to get the DHCP address. */
126   guest_type = guestfs_inspect_get_type (g, root);
127
128   if (strcmp (guest_type, "linux") == 0) {
129     guest_distro = guestfs_inspect_get_distro (g, root);
130
131     if (strcmp (guest_distro, "fedora") == 0 ||
132         strcmp (guest_distro, "rhel") == 0 ||
133         strcmp (guest_distro, "redhat-based") == 0) {
134       print_dhcp_address_linux (g, root, "/var/log/messages");
135     }
136     else if (strcmp (guest_distro, "debian") == 0 ||
137              strcmp (guest_distro, "ubuntu") == 0) {
138       print_dhcp_address_linux (g, root, "/var/log/syslog");
139     }
140     else {
141       fprintf (stderr, "virt-dhcp-address: don't know how to get DHCP address from '%s'\n",
142                guest_distro);
143       exit (EXIT_FAILURE);
144     }
145
146     free (guest_distro);
147   }
148   else if (strcmp (guest_type, "windows") == 0) {
149     print_dhcp_address_windows (g, root);
150   }
151   else {
152     fprintf (stderr, "virt-dhcp-address: don't know how to get DHCP address from '%s'\n",
153              guest_type);
154     exit (EXIT_FAILURE);
155   }
156
157   free (guest_type);
158 }
159
160 /* Look for dhclient messages in logfile.
161  */
162 static void
163 print_dhcp_address_linux (guestfs_h *g, char *root, const char *logfile)
164 {
165   char **lines, *p;
166   size_t len;
167
168   lines = guestfs_egrep (g, "dhclient.*: bound to ", logfile);
169   if (lines == NULL)
170     exit (EXIT_FAILURE);
171
172   len = count_strings (lines);
173   if (len == 0) {
174     fprintf (stderr, "virt-dhcp-address: cannot find DHCP address for this guest.\n");
175     exit (EXIT_FAILURE);
176   }
177
178   /* Only want the last message. */
179   p = strstr (lines[len-1], "bound to ");
180   assert (p);
181   p += 9;
182   len = strcspn (p, " ");
183   p[len] = '\0';
184
185   printf ("%s\n", p);
186
187   free_strings (lines);
188 }
189
190 /* Download the Windows SYSTEM hive and find DHCP configuration in there. */
191 static void
192 print_dhcp_address_windows (guestfs_h *g, char *root_unused)
193 {
194   char *system_path;
195   char tmpfile[] = "/tmp/systemXXXXXX";
196   int fd, err;
197   hive_h *h;
198   hive_node_h root, node, *nodes;
199   hive_value_h value;
200   int32_t dword;
201   char controlset[] = "ControlSetXXX";
202   size_t i;
203   char *p;
204
205   /* Locate the SYSTEM hive case-sensitive path. */
206   system_path =
207     guestfs_case_sensitive_path (g, "/windows/system32/config/system");
208   if (!system_path) {
209     fprintf (stderr, "virt-dhcp-address: HKLM\\System not found in this guest.");
210     exit (EXIT_FAILURE);
211   }
212
213   fd = mkstemp (tmpfile);
214   if (fd == -1) {
215     perror ("mkstemp");
216     exit (EXIT_FAILURE);
217   }
218
219   /* Download the SYSTEM hive. */
220   if (guestfs_download (g, system_path, tmpfile) == -1)
221     exit (EXIT_FAILURE);
222
223   free (system_path);
224
225   /* Open the hive to parse it. */
226   h = hivex_open (tmpfile, 0);
227   err = errno;
228   close (fd);
229   unlink (tmpfile);
230
231   if (h == NULL) {
232     errno = err;
233     perror ("hivex_open");
234     exit (EXIT_FAILURE);
235   }
236
237   /* Navigate to the Select key so we know which ControlSet is in use. */
238   root = hivex_root (h);
239   if (root == 0) {
240     perror ("hivex_root");
241     exit (EXIT_FAILURE);
242   }
243   node = hivex_node_get_child (h, root, "Select");
244   if (node == 0) {
245     if (errno != 0)
246       perror ("hivex_node_get_child");
247     else
248       fprintf (stderr, "virt-dhcp-address: HKLM\\System\\Select key not found.");
249     exit (EXIT_FAILURE);
250   }
251   value = hivex_node_get_value (h, node, "Current");
252   if (value == 0) {
253     if (errno != 0)
254       perror ("hivex_node_get_value");
255     else
256       fprintf (stderr, "virt-dhcp-address: HKLM\\System\\Select Default entry not found.");
257     exit (EXIT_FAILURE);
258   }
259   /* XXX Should check the type. */
260   dword = hivex_value_dword (h, value);
261   snprintf (controlset, sizeof controlset, "ControlSet%03d", dword);
262
263   /* Get ControlSetXXX\Services\Tcpip\Parameters\Interfaces. */
264   const char *path[] = { controlset, "Services", "Tcpip", "Parameters",
265                          "Interfaces" };
266   node = root;
267   errno = 0;
268   for (i = 0; node != 0 && i < sizeof path / sizeof path[0]; ++i)
269     node = hivex_node_get_child (h, node, path[i]);
270
271   if (node == 0) {
272     if (errno != 0)
273       perror ("hivex_node_get_child");
274     else
275       fprintf (stderr, "virt-dhcp-address: HKLM\\System\\%s\\Services\\Tcpip\\Parameters\\Interfaces not found.", controlset);
276     exit (EXIT_FAILURE);
277   }
278
279   /* Look for a node under here which has a "DhcpIPAddress" entry in it. */
280   nodes = hivex_node_children (h, node);
281   if (nodes == NULL) {
282     perror ("hivex_node_children");
283     exit (EXIT_FAILURE);
284   }
285
286   value = 0;
287   for (i = 0; value == 0 && nodes[i] != 0; ++i) {
288     errno = 0;
289     value = hivex_node_get_value (h, nodes[i], "DhcpIPAddress");
290     if (value == 0 && errno != 0) {
291       perror ("hivex_node_get_value");
292       exit (EXIT_FAILURE);
293     }
294   }
295
296   if (value == 0) {
297     fprintf (stderr, "virt-dhcp-address: cannot find DHCP address for this guest.\n");
298     exit (EXIT_FAILURE);
299   }
300
301   /* Get the string and use hivex's auto-conversion to convert it to UTF-8
302    * for output.
303    */
304   p = hivex_value_string (h, value);
305   if (!p) {
306     perror ("hivex_value_string");
307     exit (EXIT_FAILURE);
308   }
309
310   printf ("%s\n", p);
311
312   /* Close the hive handle. */
313   hivex_close (h);
314 }
315
316 static int
317 compare_keys_len (const void *p1, const void *p2)
318 {
319   const char *key1 = * (char * const *) p1;
320   const char *key2 = * (char * const *) p2;
321   return strlen (key1) - strlen (key2);
322 }
323
324 static size_t
325 count_strings (char *const *argv)
326 {
327   size_t c;
328
329   for (c = 0; argv[c]; ++c)
330     ;
331   return c;
332 }
333
334 static void
335 free_strings (char **argv)
336 {
337   size_t i;
338
339   for (i = 0; argv[i]; ++i)
340     free (argv[i]);
341   free (argv);
342 }