1/*
2 * Copyright 2015, Intel Corporation
3 * Copyright (C) 2015 The Android Open Source Project
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
17 * Written by William Roberts <william.c.roberts@intel.com>
18 *
19 */
20
21#include <errno.h>
22#include <stdbool.h>
23#include <stdio.h>
24#include <stdlib.h>
25#include <string.h>
26
27#include <sys/limits.h>
28
29#define LOG_TAG "packagelistparser"
30#include <utils/Log.h>
31
32#include <packagelistparser/packagelistparser.h>
33
34#define CLOGE(fmt, ...) \
35    do {\
36        IF_ALOGE() {\
37            ALOGE(fmt, ##__VA_ARGS__);\
38        }\
39    } while(0)
40
41static size_t get_gid_cnt(const char *gids)
42{
43    size_t cnt;
44
45    if (*gids == '\0') {
46        return 0;
47    }
48
49    if (!strcmp(gids, "none")) {
50        return 0;
51    }
52
53    for (cnt = 1; gids[cnt]; gids[cnt] == ',' ? cnt++ : *gids++)
54        ;
55
56    return cnt;
57}
58
59static bool parse_gids(char *gids, gid_t *gid_list, size_t *cnt)
60{
61    gid_t gid;
62    char* token;
63    char *endptr;
64    size_t cmp = 0;
65
66    while ((token = strsep(&gids, ",\r\n"))) {
67
68        if (cmp > *cnt) {
69            return false;
70        }
71
72        gid = strtoul(token, &endptr, 10);
73        if (*endptr != '\0') {
74            return false;
75        }
76
77        /*
78         * if unsigned long is greater than size of gid_t,
79         * prevent a truncation based roll-over
80         */
81        if (gid > GID_MAX) {
82            CLOGE("A gid in field \"gid list\" greater than GID_MAX");
83            return false;
84        }
85
86        gid_list[cmp++] = gid;
87    }
88    return true;
89}
90
91extern bool packagelist_parse(pfn_on_package callback, void *userdata)
92{
93
94    FILE *fp;
95    char *cur;
96    char *next;
97    char *endptr;
98    unsigned long tmp;
99    ssize_t bytesread;
100
101    bool rc = false;
102    char *buf = NULL;
103    size_t buflen = 0;
104    unsigned long lineno = 1;
105    const char *errmsg = NULL;
106    struct pkg_info *pkg_info = NULL;
107
108    fp = fopen(PACKAGES_LIST_FILE, "re");
109    if (!fp) {
110        CLOGE("Could not open: \"%s\", error: \"%s\"\n", PACKAGES_LIST_FILE,
111                strerror(errno));
112        return false;
113    }
114
115    while ((bytesread = getline(&buf, &buflen, fp)) > 0) {
116
117        pkg_info = calloc(1, sizeof(*pkg_info));
118        if (!pkg_info) {
119            goto err;
120        }
121
122        next = buf;
123
124        cur = strsep(&next, " \t\r\n");
125        if (!cur) {
126            errmsg = "Could not get next token for \"package name\"";
127            goto err;
128        }
129
130        pkg_info->name = strdup(cur);
131        if (!pkg_info->name) {
132            goto err;
133        }
134
135        cur = strsep(&next, " \t\r\n");
136        if (!cur) {
137            errmsg = "Could not get next token for field \"uid\"";
138            goto err;
139        }
140
141        tmp = strtoul(cur, &endptr, 10);
142        if (*endptr != '\0') {
143            errmsg = "Could not convert field \"uid\" to integer value";
144            goto err;
145        }
146
147        /*
148         * if unsigned long is greater than size of uid_t,
149         * prevent a truncation based roll-over
150         */
151        if (tmp > UID_MAX) {
152            errmsg = "Field \"uid\" greater than UID_MAX";
153            goto err;
154        }
155
156        pkg_info->uid = (uid_t) tmp;
157
158        cur = strsep(&next, " \t\r\n");
159        if (!cur) {
160            errmsg = "Could not get next token for field \"debuggable\"";
161            goto err;
162        }
163
164        tmp = strtoul(cur, &endptr, 10);
165        if (*endptr != '\0') {
166            errmsg = "Could not convert field \"debuggable\" to integer value";
167            goto err;
168        }
169
170        /* should be a valid boolean of 1 or 0 */
171        if (!(tmp == 0 || tmp == 1)) {
172            errmsg = "Field \"debuggable\" is not 0 or 1 boolean value";
173            goto err;
174        }
175
176        pkg_info->debuggable = (bool) tmp;
177
178        cur = strsep(&next, " \t\r\n");
179        if (!cur) {
180            errmsg = "Could not get next token for field \"data dir\"";
181            goto err;
182        }
183
184        pkg_info->data_dir = strdup(cur);
185        if (!pkg_info->data_dir) {
186            goto err;
187        }
188
189        cur = strsep(&next, " \t\r\n");
190        if (!cur) {
191            errmsg = "Could not get next token for field \"seinfo\"";
192            goto err;
193        }
194
195        pkg_info->seinfo = strdup(cur);
196        if (!pkg_info->seinfo) {
197            goto err;
198        }
199
200        cur = strsep(&next, " \t\r\n");
201        if (!cur) {
202            errmsg = "Could not get next token for field \"gid(s)\"";
203            goto err;
204        }
205
206        /*
207         * Parse the gid list, could be in the form of none, single gid or list:
208         * none
209         * gid
210         * gid, gid ...
211         */
212        pkg_info->gids.cnt = get_gid_cnt(cur);
213        if (pkg_info->gids.cnt > 0) {
214
215            pkg_info->gids.gids = calloc(pkg_info->gids.cnt, sizeof(gid_t));
216            if (!pkg_info->gids.gids) {
217                goto err;
218            }
219
220            rc = parse_gids(cur, pkg_info->gids.gids, &pkg_info->gids.cnt);
221            if (!rc) {
222                errmsg = "Could not parse field \"gid list\"";
223                goto err;
224            }
225        }
226
227        rc = callback(pkg_info, userdata);
228        if (rc == false) {
229            /*
230             * We do not log this as this can be intentional from
231             * callback to abort processing. We go to out to not
232             * free the pkg_info
233             */
234            rc = true;
235            goto out;
236        }
237        lineno++;
238    }
239
240    rc = true;
241
242out:
243    free(buf);
244    fclose(fp);
245    return rc;
246
247err:
248    if (errmsg) {
249        CLOGE("Error Parsing \"%s\" on line: %lu for reason: %s",
250                PACKAGES_LIST_FILE, lineno, errmsg);
251    }
252    rc = false;
253    packagelist_free(pkg_info);
254    goto out;
255}
256
257void packagelist_free(pkg_info *info)
258{
259    if (info) {
260        free(info->name);
261        free(info->data_dir);
262        free(info->seinfo);
263        free(info->gids.gids);
264        free(info);
265    }
266}
267