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