1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #define LOG_TAG "packagelistparser"
18 
19 #include <packagelistparser/packagelistparser.h>
20 
21 #include <errno.h>
22 #include <inttypes.h>
23 #include <stdio.h>
24 #include <string.h>
25 #include <sys/limits.h>
26 
27 #include <memory>
28 
29 #include <log/log.h>
30 
parse_gids(const char * path,size_t line_number,const char * gids,pkg_info * info)31 static bool parse_gids(const char* path, size_t line_number, const char* gids, pkg_info* info) {
32   // Nothing to do?
33   if (!gids || !strcmp(gids, "none")) return true;
34 
35   // How much space do we need?
36   info->gids.cnt = 1;
37   for (const char* p = gids; *p; ++p) {
38     if (*p == ',') ++info->gids.cnt;
39   }
40 
41   // Allocate the space.
42   info->gids.gids = new gid_t[info->gids.cnt];
43   if (!info->gids.gids) return false;
44 
45   // And parse the individual gids.
46   size_t i = 0;
47   while (true) {
48     char* end;
49     unsigned long gid = strtoul(gids, &end, 10);
50     if (gid > GID_MAX) {
51       ALOGE("%s:%zu: gid %lu > GID_MAX", path, line_number, gid);
52       return false;
53     }
54 
55     if (i >= info->gids.cnt) return false;
56     info->gids.gids[i++] = gid;
57 
58     if (*end == '\0') return true;
59     if (*end != ',') return false;
60     gids = end + 1;
61   }
62   return true;
63 }
64 
parse_line(const char * path,size_t line_number,const char * line,pkg_info * info)65 static bool parse_line(const char* path, size_t line_number, const char* line, pkg_info* info) {
66   unsigned long uid;
67   int debuggable;
68   char* gid_list;
69   int profileable_from_shell = 0;
70 
71   int fields =
72       sscanf(line, "%ms %lu %d %ms %ms %ms %d %ld", &info->name, &uid, &debuggable, &info->data_dir,
73              &info->seinfo, &gid_list, &profileable_from_shell, &info->version_code);
74 
75   // Handle the more complicated gids field and free the temporary string.
76   bool gids_okay = parse_gids(path, line_number, gid_list, info);
77   free(gid_list);
78   if (!gids_okay) return false;
79 
80   // Did we see enough fields to be getting on with?
81   // The final fields are optional (and not usually present).
82   if (fields < 6) {
83     ALOGE("%s:%zu: too few fields in line", path, line_number);
84     return false;
85   }
86 
87   // Extra validation.
88   if (uid > UID_MAX) {
89     ALOGE("%s:%zu: uid %lu > UID_MAX", path, line_number, uid);
90     return false;
91   }
92   info->uid = uid;
93 
94   // Integer to bool conversions.
95   info->debuggable = debuggable;
96   info->profileable_from_shell = profileable_from_shell;
97 
98   return true;
99 }
100 
packagelist_parse_file(const char * path,bool (* callback)(pkg_info *,void *),void * user_data)101 bool packagelist_parse_file(const char* path, bool (*callback)(pkg_info*, void*), void* user_data) {
102   std::unique_ptr<FILE, decltype(&fclose)> fp(fopen(path, "re"), &fclose);
103   if (!fp) {
104     ALOGE("couldn't open '%s': %s", path, strerror(errno));
105     return false;
106   }
107 
108   size_t line_number = 0;
109   char* line = nullptr;
110   size_t allocated_length = 0;
111   while (getline(&line, &allocated_length, fp.get()) > 0) {
112     ++line_number;
113     std::unique_ptr<pkg_info, decltype(&packagelist_free)> info(
114         static_cast<pkg_info*>(calloc(1, sizeof(pkg_info))), &packagelist_free);
115     if (!info) {
116       ALOGE("%s:%zu: couldn't allocate pkg_info", path, line_number);
117       return false;
118     }
119 
120     if (!parse_line(path, line_number, line, info.get())) return false;
121 
122     if (!callback(info.release(), user_data)) break;
123   }
124   free(line);
125   return true;
126 }
127 
packagelist_parse(bool (* callback)(pkg_info *,void *),void * user_data)128 bool packagelist_parse(bool (*callback)(pkg_info*, void*), void* user_data) {
129   return packagelist_parse_file("/data/system/packages.list", callback, user_data);
130 }
131 
packagelist_free(pkg_info * info)132 void packagelist_free(pkg_info* info) {
133   if (!info) return;
134 
135   free(info->name);
136   free(info->data_dir);
137   free(info->seinfo);
138   delete[] info->gids.gids;
139   free(info);
140 }
141