6ae08c3e6a4e12e1a92c1f3dfd600eb82e891b9e
[libguestfs.git] / src / inspect_fs_cd.c
1 /* libguestfs
2  * Copyright (C) 2010-2011 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 <fcntl.h>
27 #include <string.h>
28 #include <sys/stat.h>
29 #include <errno.h>
30 #include <endian.h>
31
32 #ifdef HAVE_PCRE
33 #include <pcre.h>
34 #endif
35
36 #ifdef HAVE_HIVEX
37 #include <hivex.h>
38 #endif
39
40 #include "c-ctype.h"
41 #include "ignore-value.h"
42 #include "xstrtol.h"
43
44 #include "guestfs.h"
45 #include "guestfs-internal.h"
46 #include "guestfs-internal-actions.h"
47 #include "guestfs_protocol.h"
48
49 #if defined(HAVE_PCRE) && defined(HAVE_HIVEX)
50
51 /* Debian/Ubuntu install disks are easy ...
52  *
53  * These files are added by the debian-cd program, and it is worth
54  * looking at the source code to determine exact values, in
55  * particular '/usr/share/debian-cd/tools/start_new_disc'
56  *
57  * XXX Architecture?  We could parse it out of the product name
58  * string, but that seems quite hairy.  We could look for the names
59  * of packages.  Also note that some Debian install disks are
60  * multiarch.
61  */
62 static int
63 check_debian_installer_root (guestfs_h *g, struct inspect_fs *fs)
64 {
65   fs->product_name = guestfs___first_line_of_file (g, "/.disk/info");
66   if (!fs->product_name)
67     return -1;
68
69   fs->type = OS_TYPE_LINUX;
70   if (STRPREFIX (fs->product_name, "Ubuntu"))
71     fs->distro = OS_DISTRO_UBUNTU;
72   else if (STRPREFIX (fs->product_name, "Debian"))
73     fs->distro = OS_DISTRO_DEBIAN;
74
75   (void) guestfs___parse_major_minor (g, fs);
76
77   if (guestfs_is_file (g, "/.disk/cd_type") > 0) {
78     char *cd_type = guestfs___first_line_of_file (g, "/.disk/cd_type");
79     if (!cd_type)
80       return -1;
81
82     if (STRPREFIX (cd_type, "dvd/single") ||
83         STRPREFIX (cd_type, "full_cd/single")) {
84       fs->is_multipart_disk = 0;
85       fs->is_netinst_disk = 0;
86     }
87     else if (STRPREFIX (cd_type, "dvd") ||
88              STRPREFIX (cd_type, "full_cd")) {
89       fs->is_multipart_disk = 1;
90       fs->is_netinst_disk = 0;
91     }
92     else if (STRPREFIX (cd_type, "not_complete")) {
93       fs->is_multipart_disk = 0;
94       fs->is_netinst_disk = 1;
95     }
96
97     free (cd_type);
98   }
99
100   return 0;
101 }
102
103 /* Take string which must look like "key = value" and find the value.
104  * There may or may not be spaces before and after the equals sign.
105  * This function is used by both check_fedora_installer_root and
106  * check_w2k3_installer_root.
107  */
108 static const char *
109 find_value (const char *kv)
110 {
111   const char *p;
112
113   p = strchr (kv, '=');
114   if (!p)
115     abort ();
116
117   do {
118     ++p;
119   } while (c_isspace (*p));
120
121   return p;
122 }
123
124 /* Fedora CDs and DVD (not netinst).  The /.treeinfo file contains
125  * an initial section somewhat like this:
126  *
127  * [general]
128  * version = 14
129  * arch = x86_64
130  * family = Fedora
131  * variant = Fedora
132  * discnum = 1
133  * totaldiscs = 1
134  */
135 static int
136 check_fedora_installer_root (guestfs_h *g, struct inspect_fs *fs)
137 {
138   char *str;
139   const char *v;
140   int r;
141   int discnum = 0, totaldiscs = 0;
142
143   fs->type = OS_TYPE_LINUX;
144
145   r = guestfs___first_egrep_of_file (g, "/.treeinfo",
146                                      "^family = Fedora$", 0, &str);
147   if (r == -1)
148     return -1;
149   if (r > 0) {
150     fs->distro = OS_DISTRO_FEDORA;
151     free (str);
152   }
153
154   r = guestfs___first_egrep_of_file (g, "/.treeinfo",
155                                      "^family = Red Hat Enterprise Linux$",
156                                      0, &str);
157   if (r == -1)
158     return -1;
159   if (r > 0) {
160     fs->distro = OS_DISTRO_RHEL;
161     free (str);
162   }
163
164   /* XXX should do major.minor before this */
165   r = guestfs___first_egrep_of_file (g, "/.treeinfo",
166                                      "^version = [[:digit:]]+", 0, &str);
167   if (r == -1)
168     return -1;
169   if (r > 0) {
170     v = find_value (str);
171     fs->major_version = guestfs___parse_unsigned_int_ignore_trailing (g, v);
172     free (str);
173     if (fs->major_version == -1)
174       return -1;
175   }
176
177   r = guestfs___first_egrep_of_file (g, "/.treeinfo",
178                                      "^arch = [-_[:alnum:]]+$", 0, &str);
179   if (r == -1)
180     return -1;
181   if (r > 0) {
182     v = find_value (str);
183     fs->arch = safe_strdup (g, v);
184     free (str);
185   }
186
187   r = guestfs___first_egrep_of_file (g, "/.treeinfo",
188                                      "^discnum = [[:digit:]]+$", 0, &str);
189   if (r == -1)
190     return -1;
191   if (r > 0) {
192     v = find_value (str);
193     discnum = guestfs___parse_unsigned_int (g, v);
194     free (str);
195     if (discnum == -1)
196       return -1;
197   }
198
199   r = guestfs___first_egrep_of_file (g, "/.treeinfo",
200                                      "^totaldiscs = [[:digit:]]+$", 0, &str);
201   if (r == -1)
202     return -1;
203   if (r > 0) {
204     v = find_value (str);
205     totaldiscs = guestfs___parse_unsigned_int (g, v);
206     free (str);
207     if (totaldiscs == -1)
208       return -1;
209   }
210
211   fs->is_multipart_disk = totaldiscs > 0;
212   /* and what about discnum? */
213
214   return 0;
215 }
216
217 /* Linux with /isolinux/isolinux.cfg.
218  *
219  * This file is not easily parsable so we have to do our best.
220  * Look for the "menu title" line which contains:
221  *   menu title Welcome to Fedora 14!   # since at least Fedora 10
222  *   menu title Welcome to Red Hat Enterprise Linux 6.0!
223  */
224 static int
225 check_isolinux_installer_root (guestfs_h *g, struct inspect_fs *fs)
226 {
227   char *str;
228   int r;
229
230   fs->type = OS_TYPE_LINUX;
231
232   r = guestfs___first_egrep_of_file (g, "/isolinux/isolinux.cfg",
233                                      "^menu title Welcome to Fedora [[:digit:]]+",
234                                      0, &str);
235   if (r == -1)
236     return -1;
237   if (r > 0) {
238     fs->distro = OS_DISTRO_FEDORA;
239     fs->major_version =
240       guestfs___parse_unsigned_int_ignore_trailing (g, &str[29]);
241     free (str);
242     if (fs->major_version == -1)
243       return -1;
244   }
245
246   /* XXX parse major.minor */
247   r = guestfs___first_egrep_of_file (g, "/isolinux/isolinux.cfg",
248                                      "^menu title Welcome to Red Hat Enterprise Linux [[:digit:]]+",
249                            0, &str);
250   if (r == -1)
251     return -1;
252   if (r > 0) {
253     fs->distro = OS_DISTRO_RHEL;
254     fs->major_version =
255       guestfs___parse_unsigned_int_ignore_trailing (g, &str[47]);
256     free (str);
257     if (fs->major_version == -1)
258       return -1;
259   }
260
261   return 0;
262 }
263
264 /* Windows 2003 and similar versions.
265  *
266  * NB: txtsetup file contains Windows \r\n line endings, which guestfs_grep
267  * does not remove.  We have to remove them by hand here.
268  */
269 static void
270 trim_cr (char *str)
271 {
272   size_t n = strlen (str);
273   if (n > 0 && str[n-1] == '\r')
274     str[n-1] = '\0';
275 }
276
277 static void
278 trim_quot (char *str)
279 {
280   size_t n = strlen (str);
281   if (n > 0 && str[n-1] == '"')
282     str[n-1] = '\0';
283 }
284
285 static int
286 check_w2k3_installer_root (guestfs_h *g, struct inspect_fs *fs,
287                            const char *txtsetup)
288 {
289   char *str;
290   const char *v;
291   int r;
292
293   fs->type = OS_TYPE_WINDOWS;
294   fs->distro = OS_DISTRO_WINDOWS;
295
296   r = guestfs___first_egrep_of_file (g, txtsetup,
297                                      "^productname[[:space:]]*=[[:space:]]*\"", 1, &str);
298   if (r == -1)
299     return -1;
300   if (r > 0) {
301     trim_cr (str);
302     trim_quot (str);
303     v = find_value (str);
304     fs->product_name = safe_strdup (g, v+1);
305     free (str);
306   }
307
308   r = guestfs___first_egrep_of_file (g, txtsetup,
309                                      "^majorversion[[:space:]]*=[[:space:]]*[[:digit:]]+",
310                                      1, &str);
311   if (r == -1)
312     return -1;
313   if (r > 0) {
314     trim_cr (str);
315     v = find_value (str);
316     fs->major_version = guestfs___parse_unsigned_int_ignore_trailing (g, v);
317     free (str);
318     if (fs->major_version == -1)
319       return -1;
320   }
321
322   r = guestfs___first_egrep_of_file (g, txtsetup,
323                                      "^minorversion[[:space:]]*=[[:space:]]*[[:digit:]]+",
324                                      1, &str);
325   if (r == -1)
326     return -1;
327   if (r > 0) {
328     trim_cr (str);
329     v = find_value (str);
330     fs->minor_version = guestfs___parse_unsigned_int_ignore_trailing (g, v);
331     free (str);
332     if (fs->minor_version == -1)
333       return -1;
334   }
335
336   /* This is the windows systemroot that would be chosen on
337    * installation by default, although not necessarily the one that
338    * the user will finally choose.
339    */
340   r = guestfs___first_egrep_of_file (g, txtsetup,
341                                      "^defaultpath[[:space:]]*=[[:space:]]*",
342                                      1, &str);
343   if (r == -1)
344     return -1;
345   if (r > 0) {
346     trim_cr (str);
347     v = find_value (str);
348     fs->windows_systemroot = safe_strdup (g, v);
349     free (str);
350   }
351
352   return 0;
353 }
354
355 /* The currently mounted device is very likely to be an installer. */
356 int
357 guestfs___check_installer_root (guestfs_h *g, struct inspect_fs *fs)
358 {
359   /* The presence of certain files indicates a live CD.
360    *
361    * XXX Fedora netinst contains a ~120MB squashfs called
362    * /images/install.img.  However this is not a live CD (unlike the
363    * Fedora live CDs which contain the same, but larger file).  We
364    * need to unpack this and look inside to tell the difference.
365    */
366   if (guestfs_is_file (g, "/casper/filesystem.squashfs") > 0)
367     fs->is_live_disk = 1;
368
369   /* Debian/Ubuntu. */
370   if (guestfs_is_file (g, "/.disk/info") > 0) {
371     if (check_debian_installer_root (g, fs) == -1)
372       return -1;
373   }
374
375   /* Fedora CDs and DVD (not netinst). */
376   else if (guestfs_is_file (g, "/.treeinfo") > 0) {
377     if (check_fedora_installer_root (g, fs) == -1)
378       return -1;
379   }
380
381   /* Linux with /isolinux/isolinux.cfg. */
382   else if (guestfs_is_file (g, "/isolinux/isolinux.cfg") > 0) {
383     if (check_isolinux_installer_root (g, fs) == -1)
384       return -1;
385   }
386
387   /* Windows 2003 64 bit */
388   else if (guestfs_is_file (g, "/amd64/txtsetup.sif") > 0) {
389     fs->arch = safe_strdup (g, "x86_64");
390     if (check_w2k3_installer_root (g, fs, "/amd64/txtsetup.sif") == -1)
391       return -1;
392   }
393
394   /* Windows 2003 32 bit */
395   else if (guestfs_is_file (g, "/i386/txtsetup.sif") > 0) {
396     fs->arch = safe_strdup (g, "i386");
397     if (check_w2k3_installer_root (g, fs, "/i386/txtsetup.sif") == -1)
398       return -1;
399   }
400
401   return 0;
402 }
403
404 #endif /* defined(HAVE_PCRE) && defined(HAVE_HIVEX) */