diff options
Diffstat (limited to 'libpackagelistparser/packagelistparser.cpp')
-rw-r--r-- | libpackagelistparser/packagelistparser.cpp | 143 |
1 files changed, 143 insertions, 0 deletions
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); +} |