diff options
-rw-r--r-- | TEST_MAPPING | 3 | ||||
l--------- | libpackagelistparser/.clang-format | 1 | ||||
-rw-r--r-- | libpackagelistparser/Android.bp | 17 | ||||
-rw-r--r-- | libpackagelistparser/include/packagelistparser/packagelistparser.h | 117 | ||||
-rw-r--r-- | libpackagelistparser/packagelistparser.c | 291 | ||||
-rw-r--r-- | libpackagelistparser/packagelistparser.cpp | 143 | ||||
-rw-r--r-- | libpackagelistparser/packagelistparser_test.cpp | 134 |
7 files changed, 344 insertions, 362 deletions
diff --git a/TEST_MAPPING b/TEST_MAPPING index 66d0c9203..a3bd44f77 100644 --- a/TEST_MAPPING +++ b/TEST_MAPPING @@ -19,6 +19,9 @@ "name": "libbase_test" }, { + "name": "libpackagelistparser_test" + }, + { "name": "libprocinfo_test" }, { diff --git a/libpackagelistparser/.clang-format b/libpackagelistparser/.clang-format new file mode 120000 index 000000000..fd0645fdf --- /dev/null +++ b/libpackagelistparser/.clang-format @@ -0,0 +1 @@ +../.clang-format-2
\ No newline at end of file diff --git a/libpackagelistparser/Android.bp b/libpackagelistparser/Android.bp index c38594a97..0740e7d65 100644 --- a/libpackagelistparser/Android.bp +++ b/libpackagelistparser/Android.bp @@ -1,12 +1,7 @@ cc_library { - name: "libpackagelistparser", recovery_available: true, - srcs: ["packagelistparser.c"], - cflags: [ - "-Wall", - "-Werror", - ], + srcs: ["packagelistparser.cpp"], shared_libs: ["liblog"], local_include_dirs: ["include"], export_include_dirs: ["include"], @@ -15,3 +10,13 @@ cc_library { misc_undefined: ["integer"], }, } + +cc_test { + name: "libpackagelistparser_test", + srcs: ["packagelistparser_test.cpp"], + shared_libs: [ + "libbase", + "libpackagelistparser", + ], + test_suites: ["device-tests"], +} diff --git a/libpackagelistparser/include/packagelistparser/packagelistparser.h b/libpackagelistparser/include/packagelistparser/packagelistparser.h index 3cb6b9af0..e89cb5400 100644 --- a/libpackagelistparser/include/packagelistparser/packagelistparser.h +++ b/libpackagelistparser/include/packagelistparser/packagelistparser.h @@ -1,94 +1,81 @@ /* - * Copyright 2015, Intel Corporation - * Copyright (C) 2015 The Android Open Source Project + * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - * - * Written by William Roberts <william.c.roberts@intel.com> - * - * This is a parser library for parsing the packages.list file generated - * by PackageManager service. - * - * This simple parser is sensitive to format changes in - * frameworks/base/services/core/java/com/android/server/pm/Settings.java - * A dependency note has been added to that file to correct - * this parser. */ -#ifndef PACKAGELISTPARSER_H_ -#define PACKAGELISTPARSER_H_ +#pragma once #include <stdbool.h> -#include <sys/cdefs.h> #include <sys/types.h> __BEGIN_DECLS -/** The file containing the list of installed packages on the system */ -#define PACKAGES_LIST_FILE "/data/system/packages.list" - -typedef struct pkg_info pkg_info; -typedef struct gid_list gid_list; - -struct gid_list { - size_t cnt; - gid_t *gids; -}; - -struct pkg_info { - char *name; - uid_t uid; - bool debuggable; - char *data_dir; - char *seinfo; - gid_list gids; - void *private_data; - bool profileable_from_shell; - long version_code; -}; +typedef struct gid_list { + /** Number of gids. */ + size_t cnt; -/** - * Callback function to be used by packagelist_parse() routine. - * @param info - * The parsed package information - * @param userdata - * The supplied userdata pointer to packagelist_parse() - * @return - * true to keep processing, false to stop. - */ -typedef bool (*pfn_on_package)(pkg_info *info, void *userdata); + /** Array of gids. */ + gid_t* gids; +} gid_list; + +typedef struct pkg_info { + /** Package name like "com.android.blah". */ + char* name; + + /** Package uid like 10014. */ + uid_t uid; + + /** Package's AndroidManifest.xml debuggable flag. */ + bool debuggable; + + /** Package data directory like "/data/user/0/com.android.blah" */ + char* data_dir; + + /** Package SELinux info. */ + char* seinfo; + + /** Package's list of gids. */ + gid_list gids; + + /** Spare pointer for the caller to stash extra data off. */ + void* private_data; + + /** Package's AndroidManifest.xml profileable flag. */ + bool profileable_from_shell; + + /** Package's AndroidManifest.xml version code. */ + long version_code; +} pkg_info; /** - * Parses the file specified by PACKAGES_LIST_FILE and invokes the callback on - * each entry found. Once the callback is invoked, ownership of the pkg_info pointer - * is passed to the callback routine, thus they are required to perform any cleanup - * desired. - * @param callback - * The callback function called on each parsed line of the packages list. - * @param userdata - * An optional userdata supplied pointer to pass to the callback function. - * @return - * true on success false on failure. + * Parses the system's default package list. + * Invokes `callback` once for each package. + * The callback owns the `pkg_info*` and should call packagelist_free(). + * The callback should return `false` to exit early or `true` to continue. */ -extern bool packagelist_parse(pfn_on_package callback, void *userdata); +bool packagelist_parse(bool (*callback)(pkg_info* info, void* user_data), void* user_data); /** - * Frees a pkg_info structure. - * @param info - * The struct to free + * Parses the given package list. + * Invokes `callback` once for each package. + * The callback owns the `pkg_info*` and should call packagelist_free(). + * The callback should return `false` to exit early or `true` to continue. */ -extern void packagelist_free(pkg_info *info); +bool packagelist_parse_file(const char* path, bool (*callback)(pkg_info* info, void* user_data), + void* user_data); -__END_DECLS +/** Frees the given `pkg_info`. */ +void packagelist_free(pkg_info* info); -#endif /* PACKAGELISTPARSER_H_ */ +__END_DECLS diff --git a/libpackagelistparser/packagelistparser.c b/libpackagelistparser/packagelistparser.c deleted file mode 100644 index edc533cbf..000000000 --- a/libpackagelistparser/packagelistparser.c +++ /dev/null @@ -1,291 +0,0 @@ -/* - * Copyright 2015, Intel Corporation - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Written by William Roberts <william.c.roberts@intel.com> - * - */ - -#define LOG_TAG "packagelistparser" - -#include <errno.h> -#include <stdbool.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sys/limits.h> - -#include <log/log.h> -#include <packagelistparser/packagelistparser.h> - -#define CLOGE(fmt, ...) \ - do {\ - IF_ALOGE() {\ - ALOGE(fmt, ##__VA_ARGS__);\ - }\ - } while(0) - -static size_t get_gid_cnt(const char *gids) -{ - size_t cnt; - - if (*gids == '\0') { - return 0; - } - - if (!strcmp(gids, "none")) { - return 0; - } - - for (cnt = 1; gids[cnt]; gids[cnt] == ',' ? cnt++ : *gids++) - ; - - return cnt; -} - -static bool parse_gids(char *gids, gid_t *gid_list, size_t *cnt) -{ - gid_t gid; - char* token; - char *endptr; - size_t cmp = 0; - - while ((token = strsep(&gids, ",\r\n"))) { - - if (cmp > *cnt) { - return false; - } - - gid = strtoul(token, &endptr, 10); - if (*endptr != '\0') { - return false; - } - - /* - * if unsigned long is greater than size of gid_t, - * prevent a truncation based roll-over - */ - if (gid > GID_MAX) { - CLOGE("A gid in field \"gid list\" greater than GID_MAX"); - return false; - } - - gid_list[cmp++] = gid; - } - return true; -} - -extern bool packagelist_parse(pfn_on_package callback, void *userdata) -{ - - FILE *fp; - char *cur; - char *next; - char *endptr; - unsigned long tmp; - ssize_t bytesread; - - bool rc = false; - char *buf = NULL; - size_t buflen = 0; - unsigned long lineno = 1; - const char *errmsg = NULL; - struct pkg_info *pkg_info = NULL; - - fp = fopen(PACKAGES_LIST_FILE, "re"); - if (!fp) { - CLOGE("Could not open: \"%s\", error: \"%s\"\n", PACKAGES_LIST_FILE, - strerror(errno)); - return false; - } - - while ((bytesread = getline(&buf, &buflen, fp)) > 0) { - - pkg_info = calloc(1, sizeof(*pkg_info)); - if (!pkg_info) { - goto err; - } - - next = buf; - - cur = strsep(&next, " \t\r\n"); - if (!cur) { - errmsg = "Could not get next token for \"package name\""; - goto err; - } - - pkg_info->name = strdup(cur); - if (!pkg_info->name) { - goto err; - } - - cur = strsep(&next, " \t\r\n"); - if (!cur) { - errmsg = "Could not get next token for field \"uid\""; - goto err; - } - - tmp = strtoul(cur, &endptr, 10); - if (*endptr != '\0') { - errmsg = "Could not convert field \"uid\" to integer value"; - goto err; - } - - /* - * if unsigned long is greater than size of uid_t, - * prevent a truncation based roll-over - */ - if (tmp > UID_MAX) { - errmsg = "Field \"uid\" greater than UID_MAX"; - goto err; - } - - pkg_info->uid = (uid_t) tmp; - - cur = strsep(&next, " \t\r\n"); - if (!cur) { - errmsg = "Could not get next token for field \"debuggable\""; - goto err; - } - - tmp = strtoul(cur, &endptr, 10); - if (*endptr != '\0') { - errmsg = "Could not convert field \"debuggable\" to integer value"; - goto err; - } - - /* should be a valid boolean of 1 or 0 */ - if (!(tmp == 0 || tmp == 1)) { - errmsg = "Field \"debuggable\" is not 0 or 1 boolean value"; - goto err; - } - - pkg_info->debuggable = (bool) tmp; - - cur = strsep(&next, " \t\r\n"); - if (!cur) { - errmsg = "Could not get next token for field \"data dir\""; - goto err; - } - - pkg_info->data_dir = strdup(cur); - if (!pkg_info->data_dir) { - goto err; - } - - cur = strsep(&next, " \t\r\n"); - if (!cur) { - errmsg = "Could not get next token for field \"seinfo\""; - goto err; - } - - pkg_info->seinfo = strdup(cur); - if (!pkg_info->seinfo) { - goto err; - } - - cur = strsep(&next, " \t\r\n"); - if (!cur) { - errmsg = "Could not get next token for field \"gid(s)\""; - goto err; - } - - /* - * Parse the gid list, could be in the form of none, single gid or list: - * none - * gid - * gid, gid ... - */ - pkg_info->gids.cnt = get_gid_cnt(cur); - if (pkg_info->gids.cnt > 0) { - - pkg_info->gids.gids = calloc(pkg_info->gids.cnt, sizeof(gid_t)); - if (!pkg_info->gids.gids) { - goto err; - } - - rc = parse_gids(cur, pkg_info->gids.gids, &pkg_info->gids.cnt); - if (!rc) { - errmsg = "Could not parse field \"gid list\""; - goto err; - } - } - - cur = strsep(&next, " \t\r\n"); - if (cur) { - tmp = strtoul(cur, &endptr, 10); - if (*endptr != '\0') { - errmsg = "Could not convert field \"profileable_from_shell\" to integer value"; - goto err; - } - - /* should be a valid boolean of 1 or 0 */ - if (!(tmp == 0 || tmp == 1)) { - errmsg = "Field \"profileable_from_shell\" is not 0 or 1 boolean value"; - goto err; - } - - pkg_info->profileable_from_shell = (bool)tmp; - } - cur = strsep(&next, " \t\r\n"); - if (cur) { - tmp = strtoul(cur, &endptr, 10); - if (*endptr != '\0') { - errmsg = "Could not convert field \"versionCode\" to integer value"; - goto err; - } - pkg_info->version_code = tmp; - } - - rc = callback(pkg_info, userdata); - if (rc == false) { - /* - * We do not log this as this can be intentional from - * callback to abort processing. We go to out to not - * free the pkg_info - */ - rc = true; - goto out; - } - lineno++; - } - - rc = true; - -out: - free(buf); - fclose(fp); - return rc; - -err: - if (errmsg) { - CLOGE("Error Parsing \"%s\" on line: %lu for reason: %s", - PACKAGES_LIST_FILE, lineno, errmsg); - } - rc = false; - packagelist_free(pkg_info); - goto out; -} - -void packagelist_free(pkg_info *info) -{ - if (info) { - free(info->name); - free(info->data_dir); - free(info->seinfo); - free(info->gids.gids); - free(info); - } -} diff --git a/libpackagelistparser/packagelistparser.cpp b/libpackagelistparser/packagelistparser.cpp new file mode 100644 index 000000000..ddf558b5b --- /dev/null +++ b/libpackagelistparser/packagelistparser.cpp @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "packagelistparser" + +#include <packagelistparser/packagelistparser.h> + +#include <errno.h> +#include <inttypes.h> +#include <stdio.h> +#include <string.h> +#include <sys/limits.h> + +#include <memory> + +#include <log/log.h> + +static bool parse_gids(const char* path, size_t line_number, const char* gids, pkg_info* info) { + // Nothing to do? + if (!gids || !strcmp(gids, "none")) return true; + + // How much space do we need? + info->gids.cnt = 1; + for (const char* p = gids; *p; ++p) { + if (*p == ',') ++info->gids.cnt; + } + + // Allocate the space. + info->gids.gids = new gid_t[info->gids.cnt]; + if (!info->gids.gids) return false; + + // And parse the individual gids. + size_t i = 0; + while (true) { + char* end; + unsigned long gid = strtoul(gids, &end, 10); + if (gid > GID_MAX) { + ALOGE("%s:%zu: gid %lu > GID_MAX", path, line_number, gid); + return false; + } + + if (i >= info->gids.cnt) return false; + info->gids.gids[i++] = gid; + + if (*end == '\0') return true; + if (*end != ',') return false; + gids = end + 1; + } + return true; +} + +static bool parse_line(const char* path, size_t line_number, const char* line, pkg_info* info) { + unsigned long uid; + int debuggable; + char* gid_list; + int profileable_from_shell = 0; + + int fields = + sscanf(line, "%ms %lu %d %ms %ms %ms %d %ld", &info->name, &uid, &debuggable, &info->data_dir, + &info->seinfo, &gid_list, &profileable_from_shell, &info->version_code); + + // Handle the more complicated gids field and free the temporary string. + bool gids_okay = parse_gids(path, line_number, gid_list, info); + free(gid_list); + if (!gids_okay) return false; + + // Did we see enough fields to be getting on with? + // The final fields are optional (and not usually present). + if (fields < 6) { + ALOGE("%s:%zu: too few fields in line", path, line_number); + return false; + } + + // Extra validation. + if (uid > UID_MAX) { + ALOGE("%s:%zu: uid %lu > UID_MAX", path, line_number, uid); + return false; + } + info->uid = uid; + + // Integer to bool conversions. + if (debuggable != 0 && debuggable != 1) return false; + info->debuggable = debuggable; + + if (profileable_from_shell != 0 && profileable_from_shell != 1) return false; + info->profileable_from_shell = profileable_from_shell; + + return true; +} + +bool packagelist_parse_file(const char* path, bool (*callback)(pkg_info*, void*), void* user_data) { + std::unique_ptr<FILE, decltype(&fclose)> fp(fopen(path, "re"), &fclose); + if (!fp) { + ALOGE("couldn't open '%s': %s", path, strerror(errno)); + return false; + } + + size_t line_number = 0; + char* line = nullptr; + size_t allocated_length = 0; + while (getline(&line, &allocated_length, fp.get()) > 0) { + ++line_number; + std::unique_ptr<pkg_info, decltype(&packagelist_free)> info( + static_cast<pkg_info*>(calloc(1, sizeof(pkg_info))), &packagelist_free); + if (!info) { + ALOGE("%s:%zu: couldn't allocate pkg_info", path, line_number); + return false; + } + + if (!parse_line(path, line_number, line, info.get())) return false; + + if (!callback(info.release(), user_data)) break; + } + free(line); + return true; +} + +bool packagelist_parse(bool (*callback)(pkg_info*, void*), void* user_data) { + return packagelist_parse_file("/data/system/packages.list", callback, user_data); +} + +void packagelist_free(pkg_info* info) { + if (!info) return; + + free(info->name); + free(info->data_dir); + free(info->seinfo); + delete[] info->gids.gids; + free(info); +} diff --git a/libpackagelistparser/packagelistparser_test.cpp b/libpackagelistparser/packagelistparser_test.cpp new file mode 100644 index 000000000..76cb886e2 --- /dev/null +++ b/libpackagelistparser/packagelistparser_test.cpp @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <packagelistparser/packagelistparser.h> + +#include <memory> + +#include <android-base/file.h> + +#include <gtest/gtest.h> + +TEST(packagelistparser, smoke) { + TemporaryFile tf; + android::base::WriteStringToFile( + // No gids. + "com.test.a0 10014 0 /data/user/0/com.test.a0 platform:privapp:targetSdkVersion=19 none\n" + // One gid. + "com.test.a1 10007 1 /data/user/0/com.test.a1 platform:privapp:targetSdkVersion=21 1023\n" + // Multiple gids. + "com.test.a2 10011 0 /data/user/0/com.test.a2 media:privapp:targetSdkVersion=30 " + "2001,1065,1023,3003,3007,1024\n" + // The two new fields (profileable flag and version code). + "com.test.a3 10022 0 /data/user/0/com.test.a3 selabel:blah none 1 123\n", + tf.path); + + std::vector<pkg_info*> packages; + packagelist_parse_file( + tf.path, + [](pkg_info* info, void* user_data) -> bool { + reinterpret_cast<std::vector<pkg_info*>*>(user_data)->push_back(info); + return true; + }, + &packages); + + ASSERT_EQ(4U, packages.size()); + + ASSERT_STREQ("com.test.a0", packages[0]->name); + ASSERT_EQ(10014, packages[0]->uid); + ASSERT_FALSE(packages[0]->debuggable); + ASSERT_STREQ("/data/user/0/com.test.a0", packages[0]->data_dir); + ASSERT_STREQ("platform:privapp:targetSdkVersion=19", packages[0]->seinfo); + ASSERT_EQ(0U, packages[0]->gids.cnt); + ASSERT_FALSE(packages[0]->profileable_from_shell); + ASSERT_EQ(0, packages[0]->version_code); + + ASSERT_STREQ("com.test.a1", packages[1]->name); + ASSERT_EQ(10007, packages[1]->uid); + ASSERT_TRUE(packages[1]->debuggable); + ASSERT_STREQ("/data/user/0/com.test.a1", packages[1]->data_dir); + ASSERT_STREQ("platform:privapp:targetSdkVersion=21", packages[1]->seinfo); + ASSERT_EQ(1U, packages[1]->gids.cnt); + ASSERT_EQ(1023U, packages[1]->gids.gids[0]); + ASSERT_FALSE(packages[0]->profileable_from_shell); + ASSERT_EQ(0, packages[0]->version_code); + + ASSERT_STREQ("com.test.a2", packages[2]->name); + ASSERT_EQ(10011, packages[2]->uid); + ASSERT_FALSE(packages[2]->debuggable); + ASSERT_STREQ("/data/user/0/com.test.a2", packages[2]->data_dir); + ASSERT_STREQ("media:privapp:targetSdkVersion=30", packages[2]->seinfo); + ASSERT_EQ(6U, packages[2]->gids.cnt); + ASSERT_EQ(2001U, packages[2]->gids.gids[0]); + ASSERT_EQ(1024U, packages[2]->gids.gids[5]); + ASSERT_FALSE(packages[0]->profileable_from_shell); + ASSERT_EQ(0, packages[0]->version_code); + + ASSERT_STREQ("com.test.a3", packages[3]->name); + ASSERT_EQ(10022, packages[3]->uid); + ASSERT_FALSE(packages[3]->debuggable); + ASSERT_STREQ("/data/user/0/com.test.a3", packages[3]->data_dir); + ASSERT_STREQ("selabel:blah", packages[3]->seinfo); + ASSERT_EQ(0U, packages[3]->gids.cnt); + ASSERT_TRUE(packages[3]->profileable_from_shell); + ASSERT_EQ(123, packages[3]->version_code); + + for (auto& package : packages) packagelist_free(package); +} + +TEST(packagelistparser, early_exit) { + TemporaryFile tf; + android::base::WriteStringToFile( + "com.test.a0 1 0 / a none\n" + "com.test.a1 1 0 / a none\n" + "com.test.a2 1 0 / a none\n", + tf.path); + + std::vector<pkg_info*> packages; + packagelist_parse_file( + tf.path, + [](pkg_info* info, void* user_data) -> bool { + std::vector<pkg_info*>* p = reinterpret_cast<std::vector<pkg_info*>*>(user_data); + p->push_back(info); + return p->size() < 2; + }, + &packages); + + ASSERT_EQ(2U, packages.size()); + + ASSERT_STREQ("com.test.a0", packages[0]->name); + ASSERT_STREQ("com.test.a1", packages[1]->name); + + for (auto& package : packages) packagelist_free(package); +} + +TEST(packagelistparser, system_package_list) { + // Check that we can actually read the packages.list installed on the device. + std::vector<pkg_info*> packages; + packagelist_parse( + [](pkg_info* info, void* user_data) -> bool { + reinterpret_cast<std::vector<pkg_info*>*>(user_data)->push_back(info); + return true; + }, + &packages); + // Not much we can say for sure about what we expect, other than that there + // are likely to be lots of packages... + ASSERT_GT(packages.size(), 10U); +} + +TEST(packagelistparser, packagelist_free_nullptr) { + packagelist_free(nullptr); +} |