New API: file-architecture
[libguestfs.git] / src / inspect.c
1 /* libguestfs
2  * Copyright (C) 2010 Red Hat Inc.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library 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 GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17  */
18
19 #include <config.h>
20
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <stdint.h>
24 #include <inttypes.h>
25 #include <unistd.h>
26 #include <string.h>
27 #include <sys/stat.h>
28
29 #include <pcre.h>
30 #include <magic.h>
31
32 #include "ignore-value.h"
33
34 #include "guestfs.h"
35 #include "guestfs-internal.h"
36 #include "guestfs-internal-actions.h"
37 #include "guestfs_protocol.h"
38
39 /* Compile all the regular expressions once when the shared library is
40  * loaded.  PCRE is thread safe so we're supposedly OK here if
41  * multiple threads call into the libguestfs API functions below
42  * simultaneously.
43  */
44 static pcre *re_file_elf;
45 static pcre *re_file_win64;
46 static pcre *re_elf_ppc64;
47
48 static void compile_regexps (void) __attribute__((constructor));
49 static void
50 compile_regexps (void)
51 {
52   const char *err;
53   int offset;
54
55 #define COMPILE(re,pattern,options)                                     \
56   do {                                                                  \
57     re = pcre_compile ((pattern), (options), &err, &offset, NULL);      \
58     if (re == NULL) {                                                   \
59       ignore_value (write (2, err, strlen (err)));                      \
60       abort ();                                                         \
61     }                                                                   \
62   } while (0)
63
64   COMPILE (re_file_elf,
65            "ELF.*(?:executable|shared object|relocatable), (.+?),", 0);
66   COMPILE (re_elf_ppc64, "64.*PowerPC", 0);
67 }
68
69 /* Match a regular expression which contains no captures.  Returns
70  * true if it matches or false if it doesn't.
71  */
72 static int
73 match (guestfs_h *g, const char *str, const pcre *re)
74 {
75   size_t len = strlen (str);
76   int vec[30], r;
77
78   r = pcre_exec (re, NULL, str, len, 0, 0, vec, sizeof vec / sizeof vec[0]);
79   if (r == PCRE_ERROR_NOMATCH)
80     return 0;
81   if (r != 1) {
82     /* Internal error -- should not happen. */
83     fprintf (stderr, "libguestfs: %s: %s: internal error: pcre_exec returned unexpected error code %d when matching against the string \"%s\"\n",
84              __FILE__, __func__, r, str);
85     return 0;
86   }
87
88   return 1;
89 }
90
91 /* Match a regular expression which contains exactly one capture.  If
92  * the string matches, return the capture, otherwise return NULL.  The
93  * caller must free the result.
94  */
95 static char *
96 match1 (guestfs_h *g, const char *str, const pcre *re)
97 {
98   size_t len = strlen (str);
99   int vec[30], r;
100
101   r = pcre_exec (re, NULL, str, len, 0, 0, vec, sizeof vec / sizeof vec[0]);
102   if (r == PCRE_ERROR_NOMATCH)
103     return NULL;
104   if (r != 2) {
105     /* Internal error -- should not happen. */
106     fprintf (stderr, "libguestfs: %s: %s: internal error: pcre_exec returned unexpected error code %d when matching against the string \"%s\"\n",
107              __FILE__, __func__, r, str);
108     return NULL;
109   }
110
111   return safe_strndup (g, &str[vec[2]], vec[3]-vec[2]);
112 }
113
114 /* Convert output from 'file' command on ELF files to the canonical
115  * architecture string.  Caller must free the result.
116  */
117 static char *
118 canonical_elf_arch (guestfs_h *g, const char *elf_arch)
119 {
120   const char *r;
121
122   if (strstr (elf_arch, "Intel 80386"))
123     r = "i386";
124   else if (strstr (elf_arch, "Intel 80486"))
125     r = "i486";
126   else if (strstr (elf_arch, "x86-64"))
127     r = "x86_64";
128   else if (strstr (elf_arch, "AMD x86-64"))
129     r = "x86_64";
130   else if (strstr (elf_arch, "SPARC32"))
131     r = "sparc";
132   else if (strstr (elf_arch, "SPARC V9"))
133     r = "sparc64";
134   else if (strstr (elf_arch, "IA-64"))
135     r = "ia64";
136   else if (match (g, elf_arch, re_elf_ppc64))
137     r = "ppc64";
138   else if (strstr (elf_arch, "PowerPC"))
139     r = "ppc";
140   else
141     r = elf_arch;
142
143   char *ret = safe_strdup (g, r);
144   return ret;
145 }
146
147 static int
148 is_regular_file (const char *filename)
149 {
150   struct stat statbuf;
151
152   return lstat (filename, &statbuf) == 0 && S_ISREG (statbuf.st_mode);
153 }
154
155 /* Download and uncompress the cpio file to find binaries within.
156  * Notes:
157  * (1) Two lists must be identical.
158  * (2) Implicit limit of 31 bytes for length of each element (see code
159  * below).
160  */
161 #define INITRD_BINARIES1 "bin/ls bin/rm bin/modprobe sbin/modprobe bin/sh bin/bash bin/dash bin/nash"
162 #define INITRD_BINARIES2 {"bin/ls", "bin/rm", "bin/modprobe", "sbin/modprobe", "bin/sh", "bin/bash", "bin/dash", "bin/nash"}
163
164 static char *
165 cpio_arch (guestfs_h *g, const char *file, const char *path)
166 {
167   char *ret = NULL;
168
169   const char *method;
170   if (strstr (file, "gzip"))
171     method = "zcat";
172   else if (strstr (file, "bzip2"))
173     method = "bzcat";
174   else
175     method = "cat";
176
177   char dir[] = "/tmp/initrd.XXXXXX";
178 #define dir_len (sizeof dir)
179   if (mkdtemp (dir) == NULL) {
180     perrorf (g, "mkdtemp");
181     goto out;
182   }
183
184   char dir_initrd[dir_len + 16];
185   snprintf (dir_initrd, dir_len + 16, "%s/initrd", dir);
186   if (guestfs_download (g, path, dir_initrd) == -1)
187     goto out;
188
189   char cmd[dir_len + 256];
190   snprintf (cmd, dir_len + 256,
191             "cd %s && %s initrd | cpio --quiet -id " INITRD_BINARIES1,
192             dir, method);
193   int r = system (cmd);
194   if (r == -1 || WEXITSTATUS (r) != 0) {
195     perrorf (g, "cpio command failed");
196     goto out;
197   }
198
199   char bin[dir_len + 32];
200   const char *bins[] = INITRD_BINARIES2;
201   size_t i;
202   for (i = 0; i < sizeof bins / sizeof bins[0]; ++i) {
203     snprintf (bin, dir_len + 32, "%s/%s", dir, bins[i]);
204
205     if (is_regular_file (bin)) {
206       int flags = g->verbose ? MAGIC_DEBUG : 0;
207       flags |= MAGIC_ERROR | MAGIC_RAW;
208
209       magic_t m = magic_open (flags);
210       if (m == NULL) {
211         perrorf (g, "magic_open");
212         goto out;
213       }
214
215       if (magic_load (m, NULL) == -1) {
216         perrorf (g, "magic_load: default magic database file");
217         magic_close (m);
218         goto out;
219       }
220
221       const char *line = magic_file (m, bin);
222       if (line == NULL) {
223         perrorf (g, "magic_file: %s", bin);
224         magic_close (m);
225         goto out;
226       }
227
228       char *elf_arch;
229       if ((elf_arch = match1 (g, line, re_file_elf)) != NULL) {
230         ret = canonical_elf_arch (g, elf_arch);
231         free (elf_arch);
232         magic_close (m);
233         goto out;
234       }
235       magic_close (m);
236     }
237   }
238   error (g, "file_architecture: could not determine architecture of cpio archive");
239
240  out:
241   /* Free up the temporary directory.  Note the directory name cannot
242    * contain shell meta-characters because of the way it was
243    * constructed above.
244    */
245   snprintf (cmd, dir_len + 256, "rm -rf %s", dir);
246   ignore_value (system (cmd));
247
248   return ret;
249 #undef dir_len
250 }
251
252 char *
253 guestfs__file_architecture (guestfs_h *g, const char *path)
254 {
255   char *file = NULL;
256   char *elf_arch = NULL;
257   char *ret = NULL;
258
259   /* Get the output of the "file" command.  Note that because this
260    * runs in the daemon, LANG=C so it's in English.
261    */
262   file = guestfs_file (g, path);
263   if (file == NULL)
264     return NULL;
265
266   if ((elf_arch = match1 (g, file, re_file_elf)) != NULL)
267     ret = canonical_elf_arch (g, elf_arch);
268   else if (strstr (file, "PE32 executable"))
269     ret = safe_strdup (g, "i386");
270   else if (strstr (file, "PE32+ executable"))
271     ret = safe_strdup (g, "x86_64");
272   else if (strstr (file, "cpio archive"))
273     ret = cpio_arch (g, file, path);
274   else
275     error (g, "file_architecture: unknown architecture: %s", path);
276
277   free (file);
278   free (elf_arch);
279   return ret;                   /* caller frees */
280 }