• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* Copyright (C) 2008 The Android Open Source Project
2 **
3 ** This software is licensed under the terms of the GNU General Public
4 ** License version 2, as published by the Free Software Foundation, and
5 ** may be copied, distributed, and modified under those terms.
6 **
7 ** This program is distributed in the hope that it will be useful,
8 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
9 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10 ** GNU General Public License for more details.
11 */
12 #include "android/avd/info.h"
13 #include "android/avd/util.h"
14 #include "android/avd/keys.h"
15 #include "android/config/config.h"
16 #include "android/utils/file_data.h"
17 #include "android/utils/path.h"
18 #include "android/utils/property_file.h"
19 #include "android/utils/bufprint.h"
20 #include "android/utils/filelock.h"
21 #include "android/utils/tempfile.h"
22 #include "android/utils/debug.h"
23 #include "android/utils/dirscanner.h"
24 #include <ctype.h>
25 #include <stddef.h>
26 #include <string.h>
27 #include <stdlib.h>
28 #include <stdio.h>
29 #include <errno.h>
30 
31 /* global variables - see android/globals.h */
32 AvdInfoParams   android_avdParams[1];
33 AvdInfo*        android_avdInfo;
34 
35 /* for debugging */
36 #define  D(...)   VERBOSE_PRINT(init,__VA_ARGS__)
37 #define  DD(...)  VERBOSE_PRINT(avd_config,__VA_ARGS__)
38 
39 /* technical note on how all of this is supposed to work:
40  *
41  * Each AVD corresponds to a "content directory" that is used to
42  * store persistent disk images and configuration files. Most remarkable
43  * are:
44  *
45  * - a "config.ini" file used to hold configuration information for the
46  *   AVD
47  *
48  * - mandatory user data image ("userdata-qemu.img") and cache image
49  *   ("cache.img")
50  *
51  * - optional mutable system image ("system-qemu.img"), kernel image
52  *   ("kernel-qemu") and read-only ramdisk ("ramdisk.img")
53  *
54  * When starting up an AVD, the emulator looks for relevant disk images
55  * in the content directory. If it doesn't find a given image there, it
56  * will try to search in the list of system directories listed in the
57  * 'config.ini' file through one of the following (key,value) pairs:
58  *
59  *    images.sysdir.1 = <first search path>
60  *    images.sysdir.2 = <second search path>
61  *
62  * The search paths can be absolute, or relative to the root SDK installation
63  * path (which is determined from the emulator program's location, or from the
64  * ANDROID_SDK_ROOT environment variable).
65  *
66  * Individual image disk search patch can be over-riden on the command-line
67  * with one of the usual options.
68  */
69 
70 /* the name of the .ini file that will contain the complete hardware
71  * properties for the AVD. This will be used to launch the corresponding
72  * core from the UI.
73  */
74 #define  CORE_HARDWARE_INI   "hardware-qemu.ini"
75 
76 /* certain disk image files are mounted read/write by the emulator
77  * to ensure that several emulators referencing the same files
78  * do not corrupt these files, we need to lock them and respond
79  * to collision depending on the image type.
80  *
81  * the enumeration below is used to record information about
82  * each image file path.
83  *
84  * READONLY means that the file will be mounted read-only
85  * and this doesn't need to be locked. must be first in list
86  *
87  * MUSTLOCK means that the file should be locked before
88  * being mounted by the emulator
89  *
90  * TEMPORARY means that the file has been copied to a
91  * temporary image, which can be mounted read/write
92  * but doesn't require locking.
93  */
94 typedef enum {
95     IMAGE_STATE_READONLY,     /* unlocked */
96     IMAGE_STATE_MUSTLOCK,     /* must be locked */
97     IMAGE_STATE_LOCKED,       /* locked */
98     IMAGE_STATE_LOCKED_EMPTY, /* locked and empty */
99     IMAGE_STATE_TEMPORARY,    /* copied to temp file (no lock needed) */
100 } AvdImageState;
101 
102 struct AvdInfo {
103     /* for the Android build system case */
104     char      inAndroidBuild;
105     char*     androidOut;
106     char*     androidBuildRoot;
107     char*     targetArch;
108     char*     targetAbi;
109 
110     /* for the normal virtual device case */
111     char*     deviceName;
112     char*     sdkRootPath;
113     char      sdkRootPathFromEnv;
114     char*     searchPaths[ MAX_SEARCH_PATHS ];
115     int       numSearchPaths;
116     char*     contentPath;
117     IniFile*  rootIni;      /* root <foo>.ini file, empty if missing */
118     IniFile*  configIni;    /* virtual device's config.ini, NULL if missing */
119     IniFile*  skinHardwareIni;  /* skin-specific hardware.ini */
120 
121     /* for both */
122     int       apiLevel;
123     char*     skinName;     /* skin name */
124     char*     skinDirPath;  /* skin directory */
125     char*     coreHardwareIniPath;  /* core hardware.ini path */
126 
127     FileData  buildProperties[1];  /* build.prop file */
128     FileData  bootProperties[1];   /* boot.prop file */
129 
130     /* image files */
131     char*     imagePath [ AVD_IMAGE_MAX ];
132     char      imageState[ AVD_IMAGE_MAX ];
133 };
134 
135 
136 void
avdInfo_free(AvdInfo * i)137 avdInfo_free( AvdInfo*  i )
138 {
139     if (i) {
140         int  nn;
141 
142         for (nn = 0; nn < AVD_IMAGE_MAX; nn++)
143             AFREE(i->imagePath[nn]);
144 
145         AFREE(i->skinName);
146         AFREE(i->skinDirPath);
147         AFREE(i->coreHardwareIniPath);
148 
149         fileData_done(i->buildProperties);
150         fileData_done(i->bootProperties);
151 
152         for (nn = 0; nn < i->numSearchPaths; nn++)
153             AFREE(i->searchPaths[nn]);
154 
155         i->numSearchPaths = 0;
156 
157         if (i->configIni) {
158             iniFile_free(i->configIni);
159             i->configIni = NULL;
160         }
161 
162         if (i->skinHardwareIni) {
163             iniFile_free(i->skinHardwareIni);
164             i->skinHardwareIni = NULL;
165         }
166 
167         if (i->rootIni) {
168             iniFile_free(i->rootIni);
169             i->rootIni = NULL;
170         }
171 
172         AFREE(i->contentPath);
173         AFREE(i->sdkRootPath);
174         AFREE(i->targetArch);
175         AFREE(i->targetAbi);
176 
177         if (i->inAndroidBuild) {
178             AFREE(i->androidOut);
179             AFREE(i->androidBuildRoot);
180         }
181 
182         AFREE(i->deviceName);
183         AFREE(i);
184     }
185 }
186 
187 /* list of default file names for each supported image file type */
188 static const char*  const  _imageFileNames[ AVD_IMAGE_MAX ] = {
189 #define  _AVD_IMG(x,y,z)  y,
190     AVD_IMAGE_LIST
191 #undef _AVD_IMG
192 };
193 
194 /* list of short text description for each supported image file type */
195 static const char*  const _imageFileText[ AVD_IMAGE_MAX ] = {
196 #define  _AVD_IMG(x,y,z)  z,
197     AVD_IMAGE_LIST
198 #undef _AVD_IMG
199 };
200 
201 /***************************************************************
202  ***************************************************************
203  *****
204  *****    UTILITY FUNCTIONS
205  *****
206  *****  The following functions do not depend on the AvdInfo
207  *****  structure and could easily be moved elsewhere.
208  *****
209  *****/
210 
211 /* Parse a given config.ini file and extract the list of SDK search paths
212  * from it. Returns the number of valid paths stored in 'searchPaths', or -1
213  * in case of problem.
214  *
215  * Relative search paths in the config.ini will be stored as full pathnames
216  * relative to 'sdkRootPath'.
217  *
218  * 'searchPaths' must be an array of char* pointers of at most 'maxSearchPaths'
219  * entries.
220  */
221 static int
_getSearchPaths(IniFile * configIni,const char * sdkRootPath,int maxSearchPaths,char ** searchPaths)222 _getSearchPaths( IniFile*    configIni,
223                  const char* sdkRootPath,
224                  int         maxSearchPaths,
225                  char**      searchPaths )
226 {
227     char  temp[PATH_MAX], *p = temp, *end= p+sizeof temp;
228     int   nn, count = 0;
229 
230     for (nn = 0; nn < maxSearchPaths; nn++) {
231         char*  path;
232 
233         p = bufprint(temp, end, "%s%d", SEARCH_PREFIX, nn+1 );
234         if (p >= end)
235             continue;
236 
237         path = iniFile_getString(configIni, temp, NULL);
238         if (path != NULL) {
239             DD("    found image search path: %s", path);
240             if (!path_is_absolute(path)) {
241                 p = bufprint(temp, end, "%s/%s", sdkRootPath, path);
242                 AFREE(path);
243                 path = ASTRDUP(temp);
244             }
245             searchPaths[count++] = path;
246         }
247     }
248     return count;
249 }
250 
251 /* Check that an AVD name is valid. Returns 1 on success, 0 otherwise.
252  */
253 static int
_checkAvdName(const char * name)254 _checkAvdName( const char*  name )
255 {
256     int  len  = strlen(name);
257     int  len2 = strspn(name, "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
258                              "abcdefghijklmnopqrstuvwxyz"
259                              "0123456789_.-");
260     return (len == len2);
261 }
262 
263 /* Returns the full path of a given file.
264  *
265  * If 'fileName' is an absolute path, this returns a simple copy.
266  * Otherwise, this returns a new string corresponding to <rootPath>/<fileName>
267  *
268  * This returns NULL if the paths are too long.
269  */
270 static char*
_getFullFilePath(const char * rootPath,const char * fileName)271 _getFullFilePath( const char* rootPath, const char* fileName )
272 {
273     if (path_is_absolute(fileName)) {
274         return ASTRDUP(fileName);
275     } else {
276         char temp[PATH_MAX], *p=temp, *end=p+sizeof(temp);
277 
278         p = bufprint(temp, end, "%s/%s", rootPath, fileName);
279         if (p >= end) {
280             return NULL;
281         }
282         return ASTRDUP(temp);
283     }
284 }
285 
286 /* check that a given directory contains a valid skin.
287  * returns 1 on success, 0 on failure.
288  */
289 static int
_checkSkinPath(const char * skinPath)290 _checkSkinPath( const char*  skinPath )
291 {
292     char  temp[MAX_PATH], *p=temp, *end=p+sizeof(temp);
293 
294     /* for now, if it has a 'layout' file, it is a valid skin path */
295     p = bufprint(temp, end, "%s/layout", skinPath);
296     if (p >= end || !path_exists(temp))
297         return 0;
298 
299     return 1;
300 }
301 
302 /* Check that there is a skin named 'skinName' listed from 'skinDirRoot'
303  * this returns the full path of the skin directory (after alias expansions),
304  * including the skin name, or NULL on failure.
305  */
306 static char*
_checkSkinSkinsDir(const char * skinDirRoot,const char * skinName)307 _checkSkinSkinsDir( const char*  skinDirRoot,
308                     const char*  skinName )
309 {
310     DirScanner*  scanner;
311     char*        result;
312     char         temp[MAX_PATH], *p = temp, *end = p + sizeof(temp);
313 
314     p = bufprint(temp, end, "%s/skins/%s", skinDirRoot, skinName);
315     DD("Probing skin directory: %s", temp);
316     if (p >= end || !path_exists(temp)) {
317         DD("    ignore bad skin directory %s", temp);
318         return NULL;
319     }
320 
321     /* first, is this a normal skin directory ? */
322     if (_checkSkinPath(temp)) {
323         /* yes */
324         DD("    found skin directory: %s", temp);
325         return ASTRDUP(temp);
326     }
327 
328     /* second, is it an alias to another skin ? */
329     *p      = 0;
330     result  = NULL;
331     scanner = dirScanner_new(temp);
332     if (scanner != NULL) {
333         for (;;) {
334             const char*  file = dirScanner_next(scanner);
335 
336             if (file == NULL)
337                 break;
338 
339             if (strncmp(file, "alias-", 6) || file[6] == 0)
340                 continue;
341 
342             p = bufprint(temp, end, "%s/skins/%s", skinDirRoot, file+6);
343             if (p < end && _checkSkinPath(temp)) {
344                 /* yes, it's an alias */
345                 DD("    skin alias '%s' points to skin directory: %s",
346                    file+6, temp);
347                 result = ASTRDUP(temp);
348                 break;
349             }
350         }
351         dirScanner_free(scanner);
352     }
353     return result;
354 }
355 
356 /* try to see if the skin name leads to a magic skin or skin path directly
357  * returns 1 on success, 0 on error.
358  *
359  * on success, this sets up '*pSkinName' and '*pSkinDir'
360  */
361 static int
_getSkinPathFromName(const char * skinName,const char * sdkRootPath,char ** pSkinName,char ** pSkinDir)362 _getSkinPathFromName( const char*  skinName,
363                       const char*  sdkRootPath,
364                       char**       pSkinName,
365                       char**       pSkinDir )
366 {
367     char  temp[PATH_MAX], *p=temp, *end=p+sizeof(temp);
368 
369     /* if the skin name has the format 'NNNNxNNN' where
370     * NNN is a decimal value, then this is a 'magic' skin
371     * name that doesn't require a skin directory
372     */
373     if (isdigit(skinName[0])) {
374         int  width, height;
375         if (sscanf(skinName, "%dx%d", &width, &height) == 2) {
376             D("'magic' skin format detected: %s", skinName);
377             *pSkinName = ASTRDUP(skinName);
378             *pSkinDir  = NULL;
379             return 1;
380         }
381     }
382 
383     /* is the skin name a direct path to the skin directory ? */
384     if (path_is_absolute(skinName) && _checkSkinPath(skinName)) {
385         goto FOUND_IT;
386     }
387 
388     /* is the skin name a relative path from the SDK root ? */
389     p = bufprint(temp, end, "%s/%s", sdkRootPath, skinName);
390     if (p < end && _checkSkinPath(temp)) {
391         skinName = temp;
392         goto FOUND_IT;
393     }
394 
395     /* nope */
396     return 0;
397 
398 FOUND_IT:
399     if (path_split(skinName, pSkinDir, pSkinName) < 0) {
400         derror("malformed skin name: %s", skinName);
401         exit(2);
402     }
403     D("found skin '%s' in directory: %s", *pSkinName, *pSkinDir);
404     return 1;
405 }
406 
407 /***************************************************************
408  ***************************************************************
409  *****
410  *****    NORMAL VIRTUAL DEVICE SUPPORT
411  *****
412  *****/
413 
414 /* compute path to the root SDK directory
415  * assume we are in $SDKROOT/tools/emulator[.exe]
416  */
417 static int
_avdInfo_getSdkRoot(AvdInfo * i)418 _avdInfo_getSdkRoot( AvdInfo*  i )
419 {
420 
421     i->sdkRootPath = path_getSdkRoot(&i->sdkRootPathFromEnv);
422     if (i->sdkRootPath == NULL)
423         return -1;
424 
425     return 0;
426 }
427 
428 /* parse the root config .ini file. it is located in
429  * ~/.android/avd/<name>.ini or Windows equivalent
430  */
431 static int
_avdInfo_getRootIni(AvdInfo * i)432 _avdInfo_getRootIni( AvdInfo*  i )
433 {
434     char*  iniPath = path_getRootIniPath( i->deviceName );
435 
436     if (iniPath == NULL) {
437         derror("unknown virtual device name: '%s'", i->deviceName);
438         return -1;
439     }
440 
441     D("Android virtual device file at: %s", iniPath);
442 
443     i->rootIni = iniFile_newFromFile(iniPath);
444     AFREE(iniPath);
445 
446     if (i->rootIni == NULL) {
447         derror("Corrupt virtual device config file!");
448         return -1;
449     }
450     return 0;
451 }
452 
453 /* Returns the AVD's content path, i.e. the directory that contains
454  * the AVD's content files (e.g. data partition, cache, sd card, etc...).
455  *
456  * We extract this by parsing the root config .ini file, looking for
457  * a "path" elements.
458  */
459 static int
_avdInfo_getContentPath(AvdInfo * i)460 _avdInfo_getContentPath( AvdInfo*  i )
461 {
462     char temp[PATH_MAX], *p=temp, *end=p+sizeof(temp);
463 
464     i->contentPath = iniFile_getString(i->rootIni, ROOT_ABS_PATH_KEY, NULL);
465 
466     if (i->contentPath == NULL) {
467         derror("bad config: %s",
468                "virtual device file lacks a "ROOT_ABS_PATH_KEY" entry");
469         return -1;
470     }
471 
472     if (!path_is_dir(i->contentPath)) {
473         // If the absolute path doesn't match an actual directory, try
474         // the relative path if present.
475         const char* relPath = iniFile_getString(i->rootIni, ROOT_REL_PATH_KEY, NULL);
476         if (relPath != NULL) {
477             p = bufprint_config_path(temp, end);
478             p = bufprint(p, end, PATH_SEP "%s", relPath);
479             if (p < end && path_is_dir(temp)) {
480                 AFREE(i->contentPath);
481                 i->contentPath = ASTRDUP(temp);
482             }
483         }
484     }
485 
486     D("virtual device content at %s", i->contentPath);
487     return 0;
488 }
489 
490 static int
_avdInfo_getApiLevel(AvdInfo * i)491 _avdInfo_getApiLevel( AvdInfo*  i )
492 {
493     char*       target;
494     const char* p;
495     const int   defaultLevel = 1000;
496     int         level        = defaultLevel;
497 
498 #    define ROOT_TARGET_KEY   "target"
499 
500     target = iniFile_getString(i->rootIni, ROOT_TARGET_KEY, NULL);
501     if (target == NULL) {
502         D("No target field in root AVD .ini file?");
503         D("Defaulting to API level %d", level);
504         return level;
505     }
506 
507     DD("Found target field in root AVD .ini file: '%s'", target);
508 
509     /* There are two acceptable formats for the target key.
510      *
511      * 1/  android-<level>
512      * 2/  <vendor-name>:<add-on-name>:<level>
513      *
514      * Where <level> can be either a _name_ (for experimental/preview SDK builds)
515      * or a decimal number. Note that if a _name_, it can start with a digit.
516      */
517 
518     /* First, extract the level */
519     if (!memcmp(target, "android-", 8))
520         p = target + 8;
521     else {
522         /* skip two columns */
523         p = strchr(target, ':');
524         if (p != NULL) {
525             p = strchr(p+1, ':');
526             if (p != NULL)
527                 p += 1;
528         }
529     }
530     if (p == NULL || !isdigit(*p)) {
531         goto NOT_A_NUMBER;
532     } else {
533         char* end;
534         long  val = strtol(p, &end, 10);
535         if (end == NULL || *end != '\0' || val != (int)val) {
536             goto NOT_A_NUMBER;
537         }
538         level = (int)val;
539 
540         /* Sanity check, we don't support anything prior to Android 1.5 */
541         if (level < 3)
542             level = 3;
543 
544         D("Found AVD target API level: %d", level);
545     }
546 EXIT:
547     AFREE(target);
548     return level;
549 
550 NOT_A_NUMBER:
551     if (p == NULL) {
552         D("Invalid target field in root AVD .ini file");
553     } else {
554         D("Target AVD api level is not a number");
555     }
556     D("Defaulting to API level %d", level);
557     goto EXIT;
558 }
559 
560 
561 int
avdInfo_getApiLevel(AvdInfo * i)562 avdInfo_getApiLevel(AvdInfo* i) {
563     return i->apiLevel;
564 }
565 
566 /* Look for a named file inside the AVD's content directory.
567  * Returns NULL if it doesn't exist, or a strdup() copy otherwise.
568  */
569 static char*
_avdInfo_getContentFilePath(AvdInfo * i,const char * fileName)570 _avdInfo_getContentFilePath(AvdInfo*  i, const char* fileName)
571 {
572     char temp[MAX_PATH], *p = temp, *end = p + sizeof(temp);
573 
574     p = bufprint(p, end, "%s/%s", i->contentPath, fileName);
575     if (p >= end) {
576         derror("can't access virtual device content directory");
577         return NULL;
578     }
579     if (!path_exists(temp)) {
580         return NULL;
581     }
582     return ASTRDUP(temp);
583 }
584 
585 /* find and parse the config.ini file from the content directory */
586 static int
_avdInfo_getConfigIni(AvdInfo * i)587 _avdInfo_getConfigIni(AvdInfo*  i)
588 {
589     char*  iniPath = _avdInfo_getContentFilePath(i, "config.ini");
590 
591     /* Allow non-existing config.ini */
592     if (iniPath == NULL) {
593         D("virtual device has no config file - no problem");
594         return 0;
595     }
596 
597     D("virtual device config file: %s", iniPath);
598     i->configIni = iniFile_newFromFile(iniPath);
599     AFREE(iniPath);
600 
601     if (i->configIni == NULL) {
602         derror("bad config: %s",
603                "virtual device has corrupted config.ini");
604         return -1;
605     }
606     return 0;
607 }
608 
609 /* The AVD's config.ini contains a list of search paths (all beginning
610  * with SEARCH_PREFIX) which are directory locations searched for
611  * AVD platform files.
612  */
613 static void
_avdInfo_getSearchPaths(AvdInfo * i)614 _avdInfo_getSearchPaths( AvdInfo*  i )
615 {
616     if (i->configIni == NULL)
617         return;
618 
619     i->numSearchPaths = _getSearchPaths( i->configIni,
620                                          i->sdkRootPath,
621                                          MAX_SEARCH_PATHS,
622                                          i->searchPaths );
623     if (i->numSearchPaths == 0) {
624         derror("no search paths found in this AVD's configuration.\n"
625                "Weird, the AVD's config.ini file is malformed. Try re-creating it.\n");
626         exit(2);
627     }
628     else
629         DD("found a total of %d search paths for this AVD", i->numSearchPaths);
630 }
631 
632 /* Search a file in the SDK search directories. Return NULL if not found,
633  * or a strdup() otherwise.
634  */
635 static char*
_avdInfo_getSdkFilePath(AvdInfo * i,const char * fileName)636 _avdInfo_getSdkFilePath(AvdInfo*  i, const char*  fileName)
637 {
638     char temp[MAX_PATH], *p = temp, *end = p + sizeof(temp);
639 
640     do {
641         /* try the search paths */
642         int  nn;
643 
644         for (nn = 0; nn < i->numSearchPaths; nn++) {
645             const char* searchDir = i->searchPaths[nn];
646 
647             p = bufprint(temp, end, "%s/%s", searchDir, fileName);
648             if (p < end && path_exists(temp)) {
649                 DD("found %s in search dir: %s", fileName, searchDir);
650                 goto FOUND;
651             }
652             DD("    no %s in search dir: %s", fileName, searchDir);
653         }
654 
655         return NULL;
656 
657     } while (0);
658 
659 FOUND:
660     return ASTRDUP(temp);
661 }
662 
663 /* Search for a file in the content directory, and if not found, in the
664  * SDK search directory. Returns NULL if not found.
665  */
666 static char*
_avdInfo_getContentOrSdkFilePath(AvdInfo * i,const char * fileName)667 _avdInfo_getContentOrSdkFilePath(AvdInfo*  i, const char*  fileName)
668 {
669     char*  path;
670 
671     path = _avdInfo_getContentFilePath(i, fileName);
672     if (path)
673         return path;
674 
675     path = _avdInfo_getSdkFilePath(i, fileName);
676     if (path)
677         return path;
678 
679     return NULL;
680 }
681 
682 #if 0
683 static int
684 _avdInfo_findContentOrSdkImage(AvdInfo* i, AvdImageType id)
685 {
686     const char* fileName = _imageFileNames[id];
687     char*       path     = _avdInfo_getContentOrSdkFilePath(i, fileName);
688 
689     i->imagePath[id]  = path;
690     i->imageState[id] = IMAGE_STATE_READONLY;
691 
692     if (path == NULL)
693         return -1;
694     else
695         return 0;
696 }
697 #endif
698 
699 /* Returns path to the core hardware .ini file. This contains the
700  * hardware configuration that is read by the core. The content of this
701  * file is auto-generated before launching a core, but we need to know
702  * its path before that.
703  */
704 static int
_avdInfo_getCoreHwIniPath(AvdInfo * i,const char * basePath)705 _avdInfo_getCoreHwIniPath( AvdInfo* i, const char* basePath )
706 {
707     i->coreHardwareIniPath = _getFullFilePath(basePath, CORE_HARDWARE_INI);
708     if (i->coreHardwareIniPath == NULL) {
709         DD("Path too long for %s: %s", CORE_HARDWARE_INI, basePath);
710         return -1;
711     }
712     D("using core hw config path: %s", i->coreHardwareIniPath);
713     return 0;
714 }
715 
716 
717 static void
_avdInfo_readPropertyFile(AvdInfo * i,const char * filePath,FileData * data)718 _avdInfo_readPropertyFile(AvdInfo* i,
719                           const char* filePath,
720                           FileData* data) {
721     int ret = fileData_initFromFile(data, filePath);
722     if (ret < 0) {
723         D("Error reading property file %s: %s", filePath, strerror(-ret));
724     } else {
725         D("Read property file at %s", filePath);
726     }
727 }
728 
729 
730 static void
_avdInfo_extractBuildProperties(AvdInfo * i)731 _avdInfo_extractBuildProperties(AvdInfo* i) {
732     i->targetArch = propertyFile_getTargetArch(i->buildProperties);
733     if (!i->targetArch) {
734         i->targetArch = ASTRDUP("arm");
735         D("Cannot find target CPU architecture, defaulting to '%s'",
736           i->targetArch);
737     }
738     i->targetAbi = propertyFile_getTargetAbi(i->buildProperties);
739     if (!i->targetAbi) {
740         i->targetAbi = ASTRDUP("armeabi");
741         D("Cannot find target CPU ABI, defaulting to '%s'",
742           i->targetAbi);
743     }
744     if (!i->apiLevel) {
745         // Note: for regular AVDs, the API level is already extracted
746         // from config.ini, besides, for older SDK platform images,
747         // there is no build.prop file and the following function
748         // would always return 1000, making the AVD unbootable!.
749         i->apiLevel = propertyFile_getApiLevel(i->buildProperties);
750         if (i->apiLevel < 3) {
751             i->apiLevel = 3;
752             D("Cannot find target API level, defaulting to %d",
753             i->apiLevel);
754         }
755     }
756 }
757 
758 
759 static void
_avdInfo_getPropertyFile(AvdInfo * i,const char * propFileName,FileData * data)760 _avdInfo_getPropertyFile(AvdInfo* i,
761                          const char* propFileName,
762                          FileData* data ) {
763     char* filePath = _avdInfo_getContentOrSdkFilePath(i, propFileName);
764     if (!filePath) {
765         D("No %s property file found.", propFileName);
766         return;
767     }
768 
769     _avdInfo_readPropertyFile(i, filePath, data);
770     free(filePath);
771 }
772 
773 AvdInfo*
avdInfo_new(const char * name,AvdInfoParams * params)774 avdInfo_new( const char*  name, AvdInfoParams*  params )
775 {
776     AvdInfo*  i;
777 
778     if (name == NULL)
779         return NULL;
780 
781     if (!_checkAvdName(name)) {
782         derror("virtual device name contains invalid characters");
783         exit(1);
784     }
785 
786     ANEW0(i);
787     i->deviceName = ASTRDUP(name);
788 
789     if ( _avdInfo_getSdkRoot(i) < 0     ||
790          _avdInfo_getRootIni(i) < 0     ||
791          _avdInfo_getContentPath(i) < 0 ||
792          _avdInfo_getConfigIni(i)   < 0 ||
793          _avdInfo_getCoreHwIniPath(i, i->contentPath) < 0 )
794         goto FAIL;
795 
796     i->apiLevel = _avdInfo_getApiLevel(i);
797 
798     /* look for image search paths. handle post 1.1/pre cupcake
799      * obsolete SDKs.
800      */
801     _avdInfo_getSearchPaths(i);
802 
803     // Find the build.prop and boot.prop files and read them.
804     _avdInfo_getPropertyFile(i, "build.prop", i->buildProperties);
805     _avdInfo_getPropertyFile(i, "boot.prop", i->bootProperties);
806 
807     _avdInfo_extractBuildProperties(i);
808 
809     /* don't need this anymore */
810     iniFile_free(i->rootIni);
811     i->rootIni = NULL;
812 
813     return i;
814 
815 FAIL:
816     avdInfo_free(i);
817     return NULL;
818 }
819 
820 /***************************************************************
821  ***************************************************************
822  *****
823  *****    ANDROID BUILD SUPPORT
824  *****
825  *****    The code below corresponds to the case where we're
826  *****    starting the emulator inside the Android build
827  *****    system. The main differences are that:
828  *****
829  *****    - the $ANDROID_PRODUCT_OUT directory is used as the
830  *****      content file.
831  *****
832  *****    - built images must not be modified by the emulator,
833  *****      so system.img must be copied to a temporary file
834  *****      and userdata.img must be copied to userdata-qemu.img
835  *****      if the latter doesn't exist.
836  *****
837  *****    - the kernel and default skin directory are taken from
838  *****      prebuilt
839  *****
840  *****    - there is no root .ini file, or any config.ini in
841  *****      the content directory, no SDK images search path.
842  *****/
843 
844 /* Read a hardware.ini if it is located in the skin directory */
845 static int
_avdInfo_getBuildSkinHardwareIni(AvdInfo * i)846 _avdInfo_getBuildSkinHardwareIni( AvdInfo*  i )
847 {
848     char* skinName;
849     char* skinDirPath;
850 
851     avdInfo_getSkinInfo(i, &skinName, &skinDirPath);
852     if (skinDirPath == NULL)
853         return 0;
854 
855     int result = avdInfo_getSkinHardwareIni(i, skinName, skinDirPath);
856 
857     AFREE(skinName);
858     AFREE(skinDirPath);
859 
860     return result;
861 }
862 
avdInfo_getSkinHardwareIni(AvdInfo * i,char * skinName,char * skinDirPath)863 int avdInfo_getSkinHardwareIni( AvdInfo* i, char* skinName, char* skinDirPath)
864 {
865     char  temp[PATH_MAX], *p=temp, *end=p+sizeof(temp);
866 
867     p = bufprint(temp, end, "%s/%s/hardware.ini", skinDirPath, skinName);
868     if (p >= end || !path_exists(temp)) {
869         DD("no skin-specific hardware.ini in %s", skinDirPath);
870         return 0;
871     }
872 
873     D("found skin-specific hardware.ini: %s", temp);
874     if (i->skinHardwareIni != NULL)
875         iniFile_free(i->skinHardwareIni);
876     i->skinHardwareIni = iniFile_newFromFile(temp);
877     if (i->skinHardwareIni == NULL)
878         return -1;
879 
880     return 0;
881 }
882 
883 AvdInfo*
avdInfo_newForAndroidBuild(const char * androidBuildRoot,const char * androidOut,AvdInfoParams * params)884 avdInfo_newForAndroidBuild( const char*     androidBuildRoot,
885                             const char*     androidOut,
886                             AvdInfoParams*  params )
887 {
888     AvdInfo*  i;
889 
890     ANEW0(i);
891 
892     i->inAndroidBuild   = 1;
893     i->androidBuildRoot = ASTRDUP(androidBuildRoot);
894     i->androidOut       = ASTRDUP(androidOut);
895     i->contentPath      = ASTRDUP(androidOut);
896 
897     // Find the build.prop file and read it.
898     char* buildPropPath = path_getBuildBuildProp(i->androidOut);
899     if (buildPropPath) {
900         _avdInfo_readPropertyFile(i, buildPropPath, i->buildProperties);
901         free(buildPropPath);
902     }
903 
904     // FInd the boot.prop file and read it.
905     char* bootPropPath = path_getBuildBootProp(i->androidOut);
906     if (bootPropPath) {
907         _avdInfo_readPropertyFile(i, bootPropPath, i->bootProperties);
908         free(bootPropPath);
909     }
910 
911     _avdInfo_extractBuildProperties(i);
912 
913     i->deviceName = ASTRDUP("<build>");
914 
915     /* out/target/product/<name>/config.ini, if exists, provide configuration
916      * from build files. */
917     if (_avdInfo_getConfigIni(i) < 0 ||
918         _avdInfo_getCoreHwIniPath(i, i->androidOut) < 0)
919         goto FAIL;
920 
921     /* Read the build skin's hardware.ini, if any */
922     _avdInfo_getBuildSkinHardwareIni(i);
923 
924     return i;
925 
926 FAIL:
927     avdInfo_free(i);
928     return NULL;
929 }
930 
931 const char*
avdInfo_getName(AvdInfo * i)932 avdInfo_getName( AvdInfo*  i )
933 {
934     return i ? i->deviceName : NULL;
935 }
936 
937 const char*
avdInfo_getImageFile(AvdInfo * i,AvdImageType imageType)938 avdInfo_getImageFile( AvdInfo*  i, AvdImageType  imageType )
939 {
940     if (i == NULL || (unsigned)imageType >= AVD_IMAGE_MAX)
941         return NULL;
942 
943     return i->imagePath[imageType];
944 }
945 
946 uint64_t
avdInfo_getImageFileSize(AvdInfo * i,AvdImageType imageType)947 avdInfo_getImageFileSize( AvdInfo*  i, AvdImageType  imageType )
948 {
949     const char* file = avdInfo_getImageFile(i, imageType);
950     uint64_t    size;
951 
952     if (file == NULL)
953         return 0ULL;
954 
955     if (path_get_size(file, &size) < 0)
956         return 0ULL;
957 
958     return size;
959 }
960 
961 int
avdInfo_isImageReadOnly(AvdInfo * i,AvdImageType imageType)962 avdInfo_isImageReadOnly( AvdInfo*  i, AvdImageType  imageType )
963 {
964     if (i == NULL || (unsigned)imageType >= AVD_IMAGE_MAX)
965         return 1;
966 
967     return (i->imageState[imageType] == IMAGE_STATE_READONLY);
968 }
969 
970 char*
avdInfo_getKernelPath(AvdInfo * i)971 avdInfo_getKernelPath( AvdInfo*  i )
972 {
973     const char* imageName = _imageFileNames[ AVD_IMAGE_KERNEL ];
974 
975     char*  kernelPath = _avdInfo_getContentOrSdkFilePath(i, imageName);
976 
977     do {
978         if (kernelPath || !i->inAndroidBuild)
979             break;
980 
981         /* When in the Android build, look into the prebuilt directory
982          * for our target architecture.
983          */
984         char temp[PATH_MAX], *p = temp, *end = p + sizeof(temp);
985         const char* suffix = "";
986 
987         // If the target ABI is armeabi-v7a, then look for
988         // kernel-qemu-armv7 instead of kernel-qemu in the prebuilt
989         // directory.
990         if (!strcmp(i->targetAbi, "armeabi-v7a")) {
991             suffix = "-armv7";
992         }
993 
994         p = bufprint(temp, end, "%s/kernel", i->androidOut);
995         if (p < end && path_exists(temp)) {
996             kernelPath = ASTRDUP(temp);
997             break;
998         }
999 
1000         p = bufprint(temp, end, "%s/prebuilts/qemu-kernel/%s/kernel-qemu%s",
1001                      i->androidBuildRoot, i->targetArch, suffix);
1002         if (p >= end || !path_exists(temp)) {
1003             derror("bad workspace: cannot find prebuilt kernel in: %s", temp);
1004             exit(1);
1005         }
1006         kernelPath = ASTRDUP(temp);
1007 
1008     } while (0);
1009 
1010     return kernelPath;
1011 }
1012 
1013 
1014 char*
avdInfo_getRamdiskPath(AvdInfo * i)1015 avdInfo_getRamdiskPath( AvdInfo* i )
1016 {
1017     const char* imageName = _imageFileNames[ AVD_IMAGE_RAMDISK ];
1018     return _avdInfo_getContentOrSdkFilePath(i, imageName);
1019 }
1020 
avdInfo_getCachePath(AvdInfo * i)1021 char*  avdInfo_getCachePath( AvdInfo*  i )
1022 {
1023     const char* imageName = _imageFileNames[ AVD_IMAGE_CACHE ];
1024     return _avdInfo_getContentFilePath(i, imageName);
1025 }
1026 
avdInfo_getDefaultCachePath(AvdInfo * i)1027 char*  avdInfo_getDefaultCachePath( AvdInfo*  i )
1028 {
1029     const char* imageName = _imageFileNames[ AVD_IMAGE_CACHE ];
1030     return _getFullFilePath(i->contentPath, imageName);
1031 }
1032 
avdInfo_getSdCardPath(AvdInfo * i)1033 char*  avdInfo_getSdCardPath( AvdInfo* i )
1034 {
1035     const char* imageName = _imageFileNames[ AVD_IMAGE_SDCARD ];
1036     char*       path;
1037 
1038     /* Special case, the config.ini can have a SDCARD_PATH entry
1039      * that gives the full path to the SD Card.
1040      */
1041     if (i->configIni != NULL) {
1042         path = iniFile_getString(i->configIni, SDCARD_PATH, NULL);
1043         if (path != NULL) {
1044             if (path_exists(path))
1045                 return path;
1046 
1047             dwarning("Ignoring invalid SDCard path: %s", path);
1048             AFREE(path);
1049         }
1050     }
1051 
1052     /* Otherwise, simply look into the content directory */
1053     return _avdInfo_getContentFilePath(i, imageName);
1054 }
1055 
1056 char*
avdInfo_getSnapStoragePath(AvdInfo * i)1057 avdInfo_getSnapStoragePath( AvdInfo* i )
1058 {
1059     const char* imageName = _imageFileNames[ AVD_IMAGE_SNAPSHOTS ];
1060     return _avdInfo_getContentFilePath(i, imageName);
1061 }
1062 
1063 char*
avdInfo_getSystemImagePath(AvdInfo * i)1064 avdInfo_getSystemImagePath( AvdInfo*  i )
1065 {
1066     const char* imageName = _imageFileNames[ AVD_IMAGE_USERSYSTEM ];
1067     return _avdInfo_getContentFilePath(i, imageName);
1068 }
1069 
1070 char*
avdInfo_getSystemInitImagePath(AvdInfo * i)1071 avdInfo_getSystemInitImagePath( AvdInfo*  i )
1072 {
1073     const char* imageName = _imageFileNames[ AVD_IMAGE_INITSYSTEM ];
1074     return _avdInfo_getContentOrSdkFilePath(i, imageName);
1075 }
1076 
1077 char*
avdInfo_getDataImagePath(AvdInfo * i)1078 avdInfo_getDataImagePath( AvdInfo*  i )
1079 {
1080     const char* imageName = _imageFileNames[ AVD_IMAGE_USERDATA ];
1081     return _avdInfo_getContentFilePath(i, imageName);
1082 }
1083 
1084 char*
avdInfo_getDefaultDataImagePath(AvdInfo * i)1085 avdInfo_getDefaultDataImagePath( AvdInfo*  i )
1086 {
1087     const char* imageName = _imageFileNames[ AVD_IMAGE_USERDATA ];
1088     return _getFullFilePath(i->contentPath, imageName);
1089 }
1090 
1091 char*
avdInfo_getDataInitImagePath(AvdInfo * i)1092 avdInfo_getDataInitImagePath( AvdInfo* i )
1093 {
1094     const char* imageName = _imageFileNames[ AVD_IMAGE_INITDATA ];
1095     return _avdInfo_getContentOrSdkFilePath(i, imageName);
1096 }
1097 
1098 int
avdInfo_initHwConfig(AvdInfo * i,AndroidHwConfig * hw)1099 avdInfo_initHwConfig( AvdInfo*  i, AndroidHwConfig*  hw )
1100 {
1101     int  ret = 0;
1102 
1103     androidHwConfig_init(hw, i->apiLevel);
1104 
1105     /* First read the config.ini, if any */
1106     if (i->configIni != NULL) {
1107         ret = androidHwConfig_read(hw, i->configIni);
1108     }
1109 
1110     /* The skin's hardware.ini can override values */
1111     if (ret == 0 && i->skinHardwareIni != NULL) {
1112         ret = androidHwConfig_read(hw, i->skinHardwareIni);
1113     }
1114 
1115     /* Auto-disable keyboard emulation on sapphire platform builds */
1116     if (i->androidOut != NULL) {
1117         char*  p = strrchr(i->androidOut, '/');
1118         if (p != NULL && !strcmp(p,"sapphire")) {
1119             hw->hw_keyboard = 0;
1120         }
1121     }
1122 
1123     return ret;
1124 }
1125 
1126 const char*
avdInfo_getContentPath(AvdInfo * i)1127 avdInfo_getContentPath( AvdInfo*  i )
1128 {
1129     return i->contentPath;
1130 }
1131 
1132 int
avdInfo_inAndroidBuild(AvdInfo * i)1133 avdInfo_inAndroidBuild( AvdInfo*  i )
1134 {
1135     return i->inAndroidBuild;
1136 }
1137 
1138 char*
avdInfo_getTargetCpuArch(AvdInfo * i)1139 avdInfo_getTargetCpuArch(AvdInfo* i) {
1140     return ASTRDUP(i->targetArch);
1141 }
1142 
1143 char*
avdInfo_getTargetAbi(AvdInfo * i)1144 avdInfo_getTargetAbi( AvdInfo* i )
1145 {
1146     /* For now, we can't get the ABI from SDK AVDs */
1147     return ASTRDUP(i->targetAbi);
1148 }
1149 
1150 char*
avdInfo_getTracePath(AvdInfo * i,const char * traceName)1151 avdInfo_getTracePath( AvdInfo*  i, const char*  traceName )
1152 {
1153     char   tmp[MAX_PATH], *p=tmp, *end=p + sizeof(tmp);
1154 
1155     if (i == NULL || traceName == NULL || traceName[0] == 0)
1156         return NULL;
1157 
1158     if (i->inAndroidBuild) {
1159         p = bufprint( p, end, "%s" PATH_SEP "traces" PATH_SEP "%s",
1160                       i->androidOut, traceName );
1161     } else {
1162         p = bufprint( p, end, "%s" PATH_SEP "traces" PATH_SEP "%s",
1163                       i->contentPath, traceName );
1164     }
1165     return ASTRDUP(tmp);
1166 }
1167 
1168 const char*
avdInfo_getCoreHwIniPath(AvdInfo * i)1169 avdInfo_getCoreHwIniPath( AvdInfo* i )
1170 {
1171     return i->coreHardwareIniPath;
1172 }
1173 
1174 
1175 void
avdInfo_getSkinInfo(AvdInfo * i,char ** pSkinName,char ** pSkinDir)1176 avdInfo_getSkinInfo( AvdInfo*  i, char** pSkinName, char** pSkinDir )
1177 {
1178     char*  skinName = NULL;
1179     char*  skinPath;
1180     char   temp[PATH_MAX], *p=temp, *end=p+sizeof(temp);
1181 
1182     *pSkinName = NULL;
1183     *pSkinDir  = NULL;
1184 
1185     /* First, see if the config.ini contains a SKIN_PATH entry that
1186      * names the full directory path for the skin.
1187      */
1188     if (i->configIni != NULL ) {
1189         skinPath = iniFile_getString( i->configIni, SKIN_PATH, NULL );
1190         if (skinPath != NULL) {
1191             /* If this skin name is magic or a direct directory path
1192             * we have our result right here.
1193             */
1194             if (_getSkinPathFromName(skinPath, i->sdkRootPath,
1195                                      pSkinName, pSkinDir )) {
1196                 AFREE(skinPath);
1197                 return;
1198             }
1199         }
1200 
1201         /* The SKIN_PATH entry was not valid, so look at SKIN_NAME */
1202         D("Warning: config.ini contains invalid %s entry: %s", SKIN_PATH, skinPath);
1203         AFREE(skinPath);
1204 
1205         skinName = iniFile_getString( i->configIni, SKIN_NAME, NULL );
1206     }
1207 
1208     if (skinName == NULL) {
1209         /* If there is no skin listed in the config.ini, try to see if
1210          * there is one single 'skin' directory in the content directory.
1211          */
1212         p = bufprint(temp, end, "%s/skin", i->contentPath);
1213         if (p < end && _checkSkinPath(temp)) {
1214             D("using skin content from %s", temp);
1215             AFREE(i->skinName);
1216             *pSkinName = ASTRDUP("skin");
1217             *pSkinDir  = ASTRDUP(i->contentPath);
1218             return;
1219         }
1220 
1221         /* otherwise, use the default name */
1222         skinName = ASTRDUP(SKIN_DEFAULT);
1223     }
1224 
1225     /* now try to find the skin directory for that name -
1226      */
1227     do {
1228         /* first try the content directory, i.e. $CONTENT/skins/<name> */
1229         skinPath = _checkSkinSkinsDir(i->contentPath, skinName);
1230         if (skinPath != NULL)
1231             break;
1232 
1233 #define  PREBUILT_SKINS_ROOT "development/tools/emulator"
1234 
1235         /* if we are in the Android build, try the prebuilt directory */
1236         if (i->inAndroidBuild) {
1237             p = bufprint( temp, end, "%s/%s",
1238                         i->androidBuildRoot, PREBUILT_SKINS_ROOT );
1239             if (p < end) {
1240                 skinPath = _checkSkinSkinsDir(temp, skinName);
1241                 if (skinPath != NULL)
1242                     break;
1243             }
1244 
1245             /* or in the parent directory of the system dir */
1246             {
1247                 char* parentDir = path_parent(i->androidOut, 1);
1248                 if (parentDir != NULL) {
1249                     skinPath = _checkSkinSkinsDir(parentDir, skinName);
1250                     AFREE(parentDir);
1251                     if (skinPath != NULL)
1252                         break;
1253                 }
1254             }
1255         }
1256 
1257         /* look in the search paths. For each <dir> in the list,
1258          * look into <dir>/../skins/<name>/ */
1259         {
1260             int  nn;
1261             for (nn = 0; nn < i->numSearchPaths; nn++) {
1262                 char*  parentDir = path_parent(i->searchPaths[nn], 1);
1263                 if (parentDir == NULL)
1264                     continue;
1265                 skinPath = _checkSkinSkinsDir(parentDir, skinName);
1266                 AFREE(parentDir);
1267                 if (skinPath != NULL)
1268                   break;
1269             }
1270             if (nn < i->numSearchPaths)
1271                 break;
1272         }
1273 
1274         /* We didn't find anything ! */
1275         *pSkinName = skinName;
1276         return;
1277 
1278     } while (0);
1279 
1280     if (path_split(skinPath, pSkinDir, pSkinName) < 0) {
1281         derror("weird skin path: %s", skinPath);
1282         AFREE(skinPath);
1283         return;
1284     }
1285     DD("found skin '%s' in directory: %s", *pSkinName, *pSkinDir);
1286     AFREE(skinPath);
1287     return;
1288 }
1289 
1290 int
avdInfo_shouldUseDynamicSkin(AvdInfo * i)1291 avdInfo_shouldUseDynamicSkin( AvdInfo* i)
1292 {
1293     if (i == NULL || i->configIni == NULL)
1294         return 0;
1295     return iniFile_getBoolean( i->configIni, SKIN_DYNAMIC, "no" );
1296 }
1297 
1298 char*
avdInfo_getDynamicSkinPath(AvdInfo * i)1299 avdInfo_getDynamicSkinPath( AvdInfo* i)
1300 {
1301     char tmp[PATH_MAX];
1302 
1303     if (i->inAndroidBuild) {
1304         snprintf(tmp, sizeof(tmp), "%s/sdk/emulator/skins/dynamic/", i->androidBuildRoot);
1305     } else {
1306         snprintf(tmp, sizeof(tmp), "%s/tools/lib/emulator/skins/dynamic/", i->sdkRootPath);
1307     }
1308 
1309     if (!path_exists(tmp))
1310         return NULL;
1311 
1312     return ASTRDUP(tmp);
1313 }
1314 
1315 char*
avdInfo_getCharmapFile(AvdInfo * i,const char * charmapName)1316 avdInfo_getCharmapFile( AvdInfo* i, const char* charmapName )
1317 {
1318     char        fileNameBuff[PATH_MAX];
1319     const char* fileName;
1320 
1321     if (charmapName == NULL || charmapName[0] == '\0')
1322         return NULL;
1323 
1324     if (strstr(charmapName, ".kcm") == NULL) {
1325         snprintf(fileNameBuff, sizeof fileNameBuff, "%s.kcm", charmapName);
1326         fileName = fileNameBuff;
1327     } else {
1328         fileName = charmapName;
1329     }
1330 
1331     return _avdInfo_getContentOrSdkFilePath(i, fileName);
1332 }
1333 
avdInfo_getAdbdCommunicationMode(AvdInfo * i)1334 int avdInfo_getAdbdCommunicationMode( AvdInfo* i )
1335 {
1336     if (i->apiLevel < 16) {
1337         // QEMU pipe for ADB communication was added in android-4.1.1_r1 API 16
1338         D("API < 16, forcing ro.adb.qemud==0");
1339         return 0;
1340     }
1341 
1342     return propertyFile_getAdbdCommunicationMode(i->buildProperties);
1343 }
1344 
avdInfo_getSnapshotPresent(AvdInfo * i)1345 int avdInfo_getSnapshotPresent(AvdInfo* i)
1346 {
1347     if (i->configIni == NULL) {
1348         return 0;
1349     } else {
1350         return iniFile_getBoolean(i->configIni, "snapshot.present", "no");
1351     }
1352 }
1353 
avdInfo_getBootProperties(AvdInfo * i)1354 const FileData* avdInfo_getBootProperties(AvdInfo* i) {
1355     return i->bootProperties;
1356 }
1357