configure: Add --with-extra flag to allow setting the extra version string.
[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_fs)
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   char *controlset;
201   size_t i;
202   char *p;
203
204   /* Locate the SYSTEM hive case-sensitive path. */
205   system_path =
206     guestfs_case_sensitive_path (g, "/windows/system32/config/system");
207   if (!system_path) {
208     fprintf (stderr, "virt-dhcp-address: HKLM\\System not found in this guest.");
209     exit (EXIT_FAILURE);
210   }
211
212   fd = mkstemp (tmpfile);
213   if (fd == -1) {
214     perror ("mkstemp");
215     exit (EXIT_FAILURE);
216   }
217
218   /* Download the SYSTEM hive. */
219   if (guestfs_download (g, system_path, tmpfile) == -1)
220     exit (EXIT_FAILURE);
221
222   free (system_path);
223
224   controlset = guestfs_inspect_get_windows_current_control_set (g, root_fs);
225   if (controlset == NULL)
226     exit (EXIT_FAILURE);
227
228   /* Open the hive to parse it. */
229   h = hivex_open (tmpfile, 0);
230   err = errno;
231   close (fd);
232   unlink (tmpfile);
233
234   if (h == NULL) {
235     errno = err;
236     perror ("hivex_open");
237     exit (EXIT_FAILURE);
238   }
239
240   root = hivex_root (h);
241   if (root == 0) {
242     perror ("hivex_root");
243     exit (EXIT_FAILURE);
244   }
245
246   /* Get ControlSetXXX\Services\Tcpip\Parameters\Interfaces. */
247   const char *path[] = { controlset, "Services", "Tcpip", "Parameters",
248                          "Interfaces" };
249   node = root;
250   errno = 0;
251   for (i = 0; node != 0 && i < sizeof path / sizeof path[0]; ++i)
252     node = hivex_node_get_child (h, node, path[i]);
253
254   if (node == 0) {
255     if (errno != 0)
256       perror ("hivex_node_get_child");
257     else
258       fprintf (stderr, "virt-dhcp-address: HKLM\\System\\%s\\Services\\Tcpip\\Parameters\\Interfaces not found.", controlset);
259     exit (EXIT_FAILURE);
260   }
261
262   /* Look for a node under here which has a "DhcpIPAddress" entry in it. */
263   nodes = hivex_node_children (h, node);
264   if (nodes == NULL) {
265     perror ("hivex_node_children");
266     exit (EXIT_FAILURE);
267   }
268
269   value = 0;
270   for (i = 0; value == 0 && nodes[i] != 0; ++i) {
271     errno = 0;
272     value = hivex_node_get_value (h, nodes[i], "DhcpIPAddress");
273     if (value == 0 && errno != 0) {
274       perror ("hivex_node_get_value");
275       exit (EXIT_FAILURE);
276     }
277   }
278
279   if (value == 0) {
280     fprintf (stderr, "virt-dhcp-address: cannot find DHCP address for this guest.\n");
281     exit (EXIT_FAILURE);
282   }
283
284   /* Get the string and use hivex's auto-conversion to convert it to UTF-8
285    * for output.
286    */
287   p = hivex_value_string (h, value);
288   if (!p) {
289     perror ("hivex_value_string");
290     exit (EXIT_FAILURE);
291   }
292
293   printf ("%s\n", p);
294
295   /* Close the hive handle. */
296   hivex_close (h);
297
298   free (controlset);
299 }
300
301 static int
302 compare_keys_len (const void *p1, const void *p2)
303 {
304   const char *key1 = * (char * const *) p1;
305   const char *key2 = * (char * const *) p2;
306   return strlen (key1) - strlen (key2);
307 }
308
309 static size_t
310 count_strings (char *const *argv)
311 {
312   size_t c;
313
314   for (c = 0; argv[c]; ++c)
315     ;
316   return c;
317 }
318
319 static void
320 free_strings (char **argv)
321 {
322   size_t i;
323
324   for (i = 0; argv[i]; ++i)
325     free (argv[i]);
326   free (argv);
327 }