diff --git a/edify/include/edify/updater_runtime_interface.h b/edify/include/edify/updater_runtime_interface.h index bdd6aec..054b85d 100644 --- a/edify/include/edify/updater_runtime_interface.h +++ b/edify/include/edify/updater_runtime_interface.h @@ -20,6 +20,8 @@ #include #include +struct selabel_handle; + // This class serves as the base to updater runtime. It wraps the runtime dependent functions; and // updates on device and host simulations can have different implementations. e.g. block devices // during host simulation merely a temporary file. With this class, the caller side in registered @@ -74,4 +76,8 @@ class UpdaterRuntimeInterface { // On devices supports A/B, add current slot suffix to arg. Otherwise, return |arg| as is. virtual std::string AddSlotSuffix(const std::string_view arg) const = 0; + + virtual struct selabel_handle* sehandle() const { + return nullptr; + } }; diff --git a/tests/unit/updater_test.cpp b/tests/unit/updater_test.cpp index 8993dd8..0720e82 100644 --- a/tests/unit/updater_test.cpp +++ b/tests/unit/updater_test.cpp @@ -339,6 +339,212 @@ TEST_F(UpdaterTest, file_getprop) { expect("", script6, kNoCause); } +TEST_F(UpdaterTest, delete) { + // Delete none. + expect("0", "delete()", kNoCause); + expect("0", "delete(\"/doesntexist\")", kNoCause); + expect("0", "delete(\"/doesntexist1\", \"/doesntexist2\")", kNoCause); + expect("0", "delete(\"/doesntexist1\", \"/doesntexist2\", \"/doesntexist3\")", kNoCause); + + // Delete one file. + TemporaryFile temp_file1; + ASSERT_TRUE(android::base::WriteStringToFile("abc", temp_file1.path)); + std::string script1("delete(\"" + std::string(temp_file1.path) + "\")"); + expect("1", script1.c_str(), kNoCause); + + // Delete two files. + TemporaryFile temp_file2; + ASSERT_TRUE(android::base::WriteStringToFile("abc", temp_file2.path)); + TemporaryFile temp_file3; + ASSERT_TRUE(android::base::WriteStringToFile("abc", temp_file3.path)); + std::string script2("delete(\"" + std::string(temp_file2.path) + "\", \"" + + std::string(temp_file3.path) + "\")"); + expect("2", script2.c_str(), kNoCause); + + // Delete already deleted files. + expect("0", script2.c_str(), kNoCause); + + // Delete one out of three. + TemporaryFile temp_file4; + ASSERT_TRUE(android::base::WriteStringToFile("abc", temp_file4.path)); + std::string script3("delete(\"/doesntexist1\", \"" + std::string(temp_file4.path) + + "\", \"/doesntexist2\")"); + expect("1", script3.c_str(), kNoCause); +} + +TEST_F(UpdaterTest, rename) { + // rename() expects two arguments. + expect(nullptr, "rename()", kArgsParsingFailure); + expect(nullptr, "rename(\"arg1\")", kArgsParsingFailure); + expect(nullptr, "rename(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure); + + // src_name or dst_name cannot be empty. + expect(nullptr, "rename(\"\", \"arg2\")", kArgsParsingFailure); + expect(nullptr, "rename(\"arg1\", \"\")", kArgsParsingFailure); + + // File doesn't exist (both of src and dst). + expect(nullptr, "rename(\"/doesntexist\", \"/doesntexisteither\")", kFileRenameFailure); + + // Can't create parent directory. + TemporaryFile temp_file1; + ASSERT_TRUE(android::base::WriteStringToFile("abc", temp_file1.path)); + std::string script1("rename(\"" + std::string(temp_file1.path) + "\", \"/proc/0/file1\")"); + expect(nullptr, script1.c_str(), kFileRenameFailure); + + // Rename. + TemporaryFile temp_file2; + std::string script2("rename(\"" + std::string(temp_file1.path) + "\", \"" + + std::string(temp_file2.path) + "\")"); + expect(temp_file2.path, script2.c_str(), kNoCause); + + // Already renamed. + expect(temp_file2.path, script2.c_str(), kNoCause); + + // Parents create successfully. + TemporaryFile temp_file3; + TemporaryDir td; + std::string temp_dir(td.path); + std::string dst_file = temp_dir + "/aaa/bbb/a.txt"; + std::string script3("rename(\"" + std::string(temp_file3.path) + "\", \"" + dst_file + "\")"); + expect(dst_file.c_str(), script3.c_str(), kNoCause); + + // Clean up the temp files under td. + ASSERT_EQ(0, unlink(dst_file.c_str())); + ASSERT_EQ(0, rmdir((temp_dir + "/aaa/bbb").c_str())); + ASSERT_EQ(0, rmdir((temp_dir + "/aaa").c_str())); +} + +TEST_F(UpdaterTest, symlink) { + // symlink expects 1+ argument. + expect(nullptr, "symlink()", kArgsParsingFailure); + + // symlink should fail if src is an empty string. + TemporaryFile temp_file1; + std::string script1("symlink(\"" + std::string(temp_file1.path) + "\", \"\")"); + expect(nullptr, script1.c_str(), kSymlinkFailure); + + std::string script2("symlink(\"" + std::string(temp_file1.path) + "\", \"src1\", \"\")"); + expect(nullptr, script2.c_str(), kSymlinkFailure); + + // symlink failed to remove old src. + std::string script3("symlink(\"" + std::string(temp_file1.path) + "\", \"/proc\")"); + expect(nullptr, script3.c_str(), kSymlinkFailure); + + // symlink can create symlinks. + TemporaryFile temp_file; + std::string content = "magicvalue"; + ASSERT_TRUE(android::base::WriteStringToFile(content, temp_file.path)); + + TemporaryDir td; + std::string src1 = std::string(td.path) + "/symlink1"; + std::string src2 = std::string(td.path) + "/symlink2"; + std::string script4("symlink(\"" + std::string(temp_file.path) + "\", \"" + src1 + "\", \"" + + src2 + "\")"); + expect("t", script4.c_str(), kNoCause); + + // Verify the created symlinks. + struct stat sb; + ASSERT_TRUE(lstat(src1.c_str(), &sb) == 0 && S_ISLNK(sb.st_mode)); + ASSERT_TRUE(lstat(src2.c_str(), &sb) == 0 && S_ISLNK(sb.st_mode)); + + // Clean up the leftovers. + ASSERT_EQ(0, unlink(src1.c_str())); + ASSERT_EQ(0, unlink(src2.c_str())); +} + +TEST_F(UpdaterTest, package_extract_dir) { + // package_extract_dir expects 2 arguments. + expect(nullptr, "package_extract_dir()", kArgsParsingFailure); + expect(nullptr, "package_extract_dir(\"arg1\")", kArgsParsingFailure); + expect(nullptr, "package_extract_dir(\"arg1\", \"arg2\", \"arg3\")", kArgsParsingFailure); + + std::string zip_path = from_testdata_base("ziptest_valid.zip"); + ZipArchiveHandle handle; + ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle)); + + // Need to set up the ziphandle. + SetUpdaterOtaPackageHandle(handle); + + // Extract "b/c.txt" and "b/d.txt" with package_extract_dir("b", ""). + TemporaryDir td; + std::string temp_dir(td.path); + std::string script("package_extract_dir(\"b\", \"" + temp_dir + "\")"); + expect("t", script.c_str(), kNoCause, &updater_); + + // Verify. + std::string data; + std::string file_c = temp_dir + "/c.txt"; + ASSERT_TRUE(android::base::ReadFileToString(file_c, &data)); + ASSERT_EQ(kCTxtContents, data); + + std::string file_d = temp_dir + "/d.txt"; + ASSERT_TRUE(android::base::ReadFileToString(file_d, &data)); + ASSERT_EQ(kDTxtContents, data); + + // Modify the contents in order to retry. It's expected to be overwritten. + ASSERT_TRUE(android::base::WriteStringToFile("random", file_c)); + ASSERT_TRUE(android::base::WriteStringToFile("random", file_d)); + + // Extract again and verify. + expect("t", script.c_str(), kNoCause, &updater_); + + ASSERT_TRUE(android::base::ReadFileToString(file_c, &data)); + ASSERT_EQ(kCTxtContents, data); + ASSERT_TRUE(android::base::ReadFileToString(file_d, &data)); + ASSERT_EQ(kDTxtContents, data); + + // Clean up the temp files under td. + ASSERT_EQ(0, unlink(file_c.c_str())); + ASSERT_EQ(0, unlink(file_d.c_str())); + + // Extracting "b/" (with slash) should give the same result. + script = "package_extract_dir(\"b/\", \"" + temp_dir + "\")"; + expect("t", script.c_str(), kNoCause, &updater_); + + ASSERT_TRUE(android::base::ReadFileToString(file_c, &data)); + ASSERT_EQ(kCTxtContents, data); + ASSERT_TRUE(android::base::ReadFileToString(file_d, &data)); + ASSERT_EQ(kDTxtContents, data); + + ASSERT_EQ(0, unlink(file_c.c_str())); + ASSERT_EQ(0, unlink(file_d.c_str())); + + // Extracting "" is allowed. The entries will carry the path name. + script = "package_extract_dir(\"\", \"" + temp_dir + "\")"; + expect("t", script.c_str(), kNoCause, &updater_); + + std::string file_a = temp_dir + "/a.txt"; + ASSERT_TRUE(android::base::ReadFileToString(file_a, &data)); + ASSERT_EQ(kATxtContents, data); + std::string file_b = temp_dir + "/b.txt"; + ASSERT_TRUE(android::base::ReadFileToString(file_b, &data)); + ASSERT_EQ(kBTxtContents, data); + std::string file_b_c = temp_dir + "/b/c.txt"; + ASSERT_TRUE(android::base::ReadFileToString(file_b_c, &data)); + ASSERT_EQ(kCTxtContents, data); + std::string file_b_d = temp_dir + "/b/d.txt"; + ASSERT_TRUE(android::base::ReadFileToString(file_b_d, &data)); + ASSERT_EQ(kDTxtContents, data); + + ASSERT_EQ(0, unlink(file_a.c_str())); + ASSERT_EQ(0, unlink(file_b.c_str())); + ASSERT_EQ(0, unlink(file_b_c.c_str())); + ASSERT_EQ(0, unlink(file_b_d.c_str())); + ASSERT_EQ(0, rmdir((temp_dir + "/b").c_str())); + + // Extracting non-existent entry should still give "t". + script = "package_extract_dir(\"doesntexist\", \"" + temp_dir + "\")"; + expect("t", script.c_str(), kNoCause, &updater_); + + // Only relative zip_path is allowed. + script = "package_extract_dir(\"/b\", \"" + temp_dir + "\")"; + expect("", script.c_str(), kNoCause, &updater_); + + // Only absolute dest_path is allowed. + script = "package_extract_dir(\"b\", \"path\")"; + expect("", script.c_str(), kNoCause, &updater_); +} + // TODO: Test extracting to block device. TEST_F(UpdaterTest, package_extract_file) { // package_extract_file expects 1 or 2 arguments. diff --git a/updater/include/updater/updater_runtime.h b/updater/include/updater/updater_runtime.h index b943dfc..fc537d2 100644 --- a/updater/include/updater/updater_runtime.h +++ b/updater/include/updater/updater_runtime.h @@ -24,8 +24,6 @@ #include "edify/updater_runtime_interface.h" -struct selabel_handle; - class UpdaterRuntime : public UpdaterRuntimeInterface { public: explicit UpdaterRuntime(struct selabel_handle* sehandle) : sehandle_(sehandle) {} @@ -58,6 +56,10 @@ class UpdaterRuntime : public UpdaterRuntimeInterface { bool UpdateDynamicPartitions(const std::string_view op_list_value) override; std::string AddSlotSuffix(const std::string_view arg) const override; + struct selabel_handle* sehandle() const override { + return sehandle_; + } + private: struct selabel_handle* sehandle_{ nullptr }; }; diff --git a/updater/install.cpp b/updater/install.cpp index 2959650..ff09281 100644 --- a/updater/install.cpp +++ b/updater/install.cpp @@ -35,7 +35,10 @@ #include #include +#include + #include + #include #include #include @@ -63,6 +66,7 @@ #include "otautil/error_code.h" #include "otautil/print_sha1.h" #include "otautil/sysutil.h" +#include "otautil/ziputil.h" #ifndef __ANDROID__ #include // for strlcpy @@ -80,6 +84,34 @@ static bool UpdateBlockDeviceNameForPartition(UpdaterInterface* updater, Partiti return true; } +static bool is_dir(const std::string& dirpath) { + struct stat st; + return stat(dirpath.c_str(), &st) == 0 && S_ISDIR(st.st_mode); +} + +// Create all parent directories of name, if necessary. +static bool make_parents(const std::string& name) { + size_t prev_end = 0; + while (prev_end < name.size()) { + size_t next_end = name.find('/', prev_end + 1); + if (next_end == std::string::npos) { + break; + } + std::string dir_path = name.substr(0, next_end); + if (!is_dir(dir_path)) { + int result = mkdir(dir_path.c_str(), 0700); + if (result != 0) { + PLOG(ERROR) << "failed to mkdir " << dir_path << " when make parents for " << name; + return false; + } + + LOG(INFO) << "created [" << dir_path << "]"; + } + prev_end = next_end; + } + return true; +} + // This is the updater side handler for ui_print() in edify script. Contents will be sent over to // the recovery side for on-screen display. Value* UIPrintFn(const char* name, State* state, const std::vector>& argv) { @@ -93,6 +125,39 @@ Value* UIPrintFn(const char* name, State* state, const std::vector>& argv) { + if (argv.size() != 2) { + return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %zu", name, + argv.size()); + } + + std::vector args; + if (!ReadArgs(state, argv, &args)) { + return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name); + } + const std::string& zip_path = args[0]; + const std::string& dest_path = args[1]; + + auto updater = state->updater; + + ZipArchiveHandle za = updater->GetPackageHandle(); + + // To create a consistent system image, never use the clock for timestamps. + constexpr struct utimbuf timestamp = { 1217592000, 1217592000 }; // 8/1/2008 default + + bool success = ExtractPackageRecursive(za, zip_path, dest_path, ×tamp, + updater->GetRuntime()->sehandle()); + + return StringValue(success ? "t" : ""); +} + // package_extract_file(package_file[, dest_file]) // Extracts a single package_file from the update package and writes it to dest_file, // overwriting existing files if necessary. Without the dest_file argument, returns the @@ -480,6 +545,66 @@ Value* FormatFn(const char* name, State* state, const std::vector>& argv) { + if (argv.size() != 2) { + return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %zu", name, + argv.size()); + } + + std::vector args; + if (!ReadArgs(state, argv, &args)) { + return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name); + } + const std::string& src_name = args[0]; + const std::string& dst_name = args[1]; + + if (src_name.empty()) { + return ErrorAbort(state, kArgsParsingFailure, "src_name argument to %s() can't be empty", name); + } + if (dst_name.empty()) { + return ErrorAbort(state, kArgsParsingFailure, "dst_name argument to %s() can't be empty", name); + } + if (!make_parents(dst_name)) { + return ErrorAbort(state, kFileRenameFailure, "Creating parent of %s failed, error %s", + dst_name.c_str(), strerror(errno)); + } else if (access(dst_name.c_str(), F_OK) == 0 && access(src_name.c_str(), F_OK) != 0) { + // File was already moved + return StringValue(dst_name); + } else if (rename(src_name.c_str(), dst_name.c_str()) != 0) { + return ErrorAbort(state, kFileRenameFailure, "Rename of %s to %s failed, error %s", + src_name.c_str(), dst_name.c_str(), strerror(errno)); + } + + return StringValue(dst_name); +} + +// delete([filename, ...]) +// Deletes all the filenames listed. Returns the number of files successfully deleted. +// +// delete_recursive([dirname, ...]) +// Recursively deletes dirnames and all their contents. Returns the number of directories +// successfully deleted. +Value* DeleteFn(const char* name, State* state, const std::vector>& argv) { + std::vector paths; + if (!ReadArgs(state, argv, &paths)) { + return nullptr; + } + + bool recursive = (strcmp(name, "delete_recursive") == 0); + + int success = 0; + for (const auto& path : paths) { + if ((recursive ? dirUnlinkHierarchy(path.c_str()) : unlink(path.c_str())) == 0) { + ++success; + } + } + + return StringValue(std::to_string(success)); +} + Value* ShowProgressFn(const char* name, State* state, const std::vector>& argv) { if (argv.size() != 2) { @@ -533,6 +658,308 @@ Value* SetProgressFn(const char* name, State* state, return StringValue(frac_str); } +// symlink(target, [src1, src2, ...]) +// Creates all sources as symlinks to target. It unlinks any previously existing src1, src2, etc +// before creating symlinks. +Value* SymlinkFn(const char* name, State* state, const std::vector>& argv) { + if (argv.size() == 0) { + return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1+ args, got %zu", name, + argv.size()); + } + + std::vector args; + if (!ReadArgs(state, argv, &args)) { + return ErrorAbort(state, kArgsParsingFailure, "%s(): Failed to parse the argument(s)", name); + } + + const auto& target = args[0]; + if (target.empty()) { + return ErrorAbort(state, kArgsParsingFailure, "%s() target argument can't be empty", name); + } + + size_t bad = 0; + for (size_t i = 1; i < args.size(); ++i) { + const auto& src = args[i]; + if (unlink(src.c_str()) == -1 && errno != ENOENT) { + PLOG(ERROR) << name << ": failed to remove " << src; + ++bad; + } else if (!make_parents(src)) { + LOG(ERROR) << name << ": failed to symlink " << src << " to " << target + << ": making parents failed"; + ++bad; + } else if (symlink(target.c_str(), src.c_str()) == -1) { + PLOG(ERROR) << name << ": failed to symlink " << src << " to " << target; + ++bad; + } + } + if (bad != 0) { + return ErrorAbort(state, kSymlinkFailure, "%s: Failed to create %zu symlink(s)", name, bad); + } + return StringValue("t"); +} + +struct perm_parsed_args { + bool has_uid; + uid_t uid; + bool has_gid; + gid_t gid; + bool has_mode; + mode_t mode; + bool has_fmode; + mode_t fmode; + bool has_dmode; + mode_t dmode; + bool has_selabel; + const char* selabel; + bool has_capabilities; + uint64_t capabilities; +}; + +static struct perm_parsed_args ParsePermArgs(State* state, const std::vector& args) { + struct perm_parsed_args parsed; + auto updater = state->updater; + int bad = 0; + static int max_warnings = 20; + + memset(&parsed, 0, sizeof(parsed)); + + for (size_t i = 1; i < args.size(); i += 2) { + if (args[i] == "uid") { + int64_t uid; + if (sscanf(args[i + 1].c_str(), "%" SCNd64, &uid) == 1) { + parsed.uid = uid; + parsed.has_uid = true; + } else { + updater->UiPrint(android::base::StringPrintf("ParsePermArgs: invalid UID \"%s\"\n", + args[i + 1].c_str())); + bad++; + } + continue; + } + if (args[i] == "gid") { + int64_t gid; + if (sscanf(args[i + 1].c_str(), "%" SCNd64, &gid) == 1) { + parsed.gid = gid; + parsed.has_gid = true; + } else { + updater->UiPrint(android::base::StringPrintf("ParsePermArgs: invalid GID \"%s\"\n", + args[i + 1].c_str())); + bad++; + } + continue; + } + if (args[i] == "mode") { + int32_t mode; + if (sscanf(args[i + 1].c_str(), "%" SCNi32, &mode) == 1) { + parsed.mode = mode; + parsed.has_mode = true; + } else { + updater->UiPrint(android::base::StringPrintf("ParsePermArgs: invalid mode \"%s\"\n", + args[i + 1].c_str())); + bad++; + } + continue; + } + if (args[i] == "dmode") { + int32_t mode; + if (sscanf(args[i + 1].c_str(), "%" SCNi32, &mode) == 1) { + parsed.dmode = mode; + parsed.has_dmode = true; + } else { + updater->UiPrint(android::base::StringPrintf("ParsePermArgs: invalid dmode \"%s\"\n", + args[i + 1].c_str())); + bad++; + } + continue; + } + if (args[i] == "fmode") { + int32_t mode; + if (sscanf(args[i + 1].c_str(), "%" SCNi32, &mode) == 1) { + parsed.fmode = mode; + parsed.has_fmode = true; + } else { + updater->UiPrint(android::base::StringPrintf("ParsePermArgs: invalid fmode \"%s\"\n", + args[i + 1].c_str())); + bad++; + } + continue; + } + if (args[i] == "capabilities") { + int64_t capabilities; + if (sscanf(args[i + 1].c_str(), "%" SCNi64, &capabilities) == 1) { + parsed.capabilities = capabilities; + parsed.has_capabilities = true; + } else { + updater->UiPrint(android::base::StringPrintf("ParsePermArgs: invalid capabilities \"%s\"\n", + args[i + 1].c_str())); + bad++; + } + continue; + } + if (args[i] == "selabel") { + if (!args[i + 1].empty()) { + parsed.selabel = args[i + 1].c_str(); + parsed.has_selabel = true; + } else { + updater->UiPrint(android::base::StringPrintf("ParsePermArgs: invalid selabel \"%s\"\n", + args[i + 1].c_str())); + bad++; + } + continue; + } + if (max_warnings != 0) { + printf("ParsedPermArgs: unknown key \"%s\", ignoring\n", args[i].c_str()); + max_warnings--; + if (max_warnings == 0) { + LOG(INFO) << "ParsedPermArgs: suppressing further warnings"; + } + } + } + return parsed; +} + +static int ApplyParsedPerms(State* state, const char* filename, const struct stat* statptr, + struct perm_parsed_args parsed) { + auto updater = state->updater; + int bad = 0; + + if (parsed.has_selabel) { + if (lsetfilecon(filename, parsed.selabel) != 0) { + updater->UiPrint(android::base::StringPrintf( + "ApplyParsedPerms: lsetfilecon of %s to %s failed: %s\n", + filename, parsed.selabel, strerror(errno))); + bad++; + } + } + + /* ignore symlinks */ + if (S_ISLNK(statptr->st_mode)) { + return bad; + } + + if (parsed.has_uid) { + if (chown(filename, parsed.uid, -1) < 0) { + updater->UiPrint(android::base::StringPrintf( + "ApplyParsedPerms: chown of %s to %d failed: %s\n", + filename, parsed.uid, strerror(errno))); + bad++; + } + } + + if (parsed.has_gid) { + if (chown(filename, -1, parsed.gid) < 0) { + updater->UiPrint(android::base::StringPrintf( + "ApplyParsedPerms: chgrp of %s to %d failed: %s\n", + filename, parsed.gid, strerror(errno))); + bad++; + } + } + + if (parsed.has_mode) { + if (chmod(filename, parsed.mode) < 0) { + updater->UiPrint(android::base::StringPrintf( + "ApplyParsedPerms: chmod of %s to %d failed: %s\n", + filename, parsed.mode, strerror(errno))); + bad++; + } + } + + if (parsed.has_dmode && S_ISDIR(statptr->st_mode)) { + if (chmod(filename, parsed.dmode) < 0) { + updater->UiPrint(android::base::StringPrintf( + "ApplyParsedPerms: chmod of %s to %d failed: %s\n", + filename, parsed.dmode, strerror(errno))); + bad++; + } + } + + if (parsed.has_fmode && S_ISREG(statptr->st_mode)) { + if (chmod(filename, parsed.fmode) < 0) { + updater->UiPrint(android::base::StringPrintf( + "ApplyParsedPerms: chmod of %s to %d failed: %s\n", + filename, parsed.fmode, strerror(errno))); + bad++; + } + } + + if (parsed.has_capabilities && S_ISREG(statptr->st_mode)) { + if (parsed.capabilities == 0) { + if ((removexattr(filename, XATTR_NAME_CAPS) == -1) && (errno != ENODATA)) { + // Report failure unless it's ENODATA (attribute not set) + updater->UiPrint(android::base::StringPrintf( + "ApplyParsedPerms: removexattr of %s to %" PRIx64 " failed: %s\n", + filename, parsed.capabilities, strerror(errno))); + bad++; + } + } else { + struct vfs_cap_data cap_data; + memset(&cap_data, 0, sizeof(cap_data)); + cap_data.magic_etc = VFS_CAP_REVISION_2 | VFS_CAP_FLAGS_EFFECTIVE; + cap_data.data[0].permitted = (uint32_t)(parsed.capabilities & 0xffffffff); + cap_data.data[0].inheritable = 0; + cap_data.data[1].permitted = (uint32_t)(parsed.capabilities >> 32); + cap_data.data[1].inheritable = 0; + if (setxattr(filename, XATTR_NAME_CAPS, &cap_data, sizeof(cap_data), 0) < 0) { + updater->UiPrint(android::base::StringPrintf( + "ApplyParsedPerms: setcap of %s to %" PRIx64 " failed: %s\n", + filename, parsed.capabilities, strerror(errno))); + bad++; + } + } + } + + return bad; +} + +// nftw doesn't allow us to pass along context, so we need to use +// global variables. *sigh* +static struct perm_parsed_args recursive_parsed_args; +static State* recursive_state; + +static int do_SetMetadataRecursive(const char* filename, const struct stat* statptr, + int /*fileflags*/, struct FTW* /*pfwt*/) { + return ApplyParsedPerms(recursive_state, filename, statptr, recursive_parsed_args); +} + +static Value* SetMetadataFn(const char* name, State* state, + const std::vector>& argv) { + if ((argv.size() % 2) != 1) { + return ErrorAbort(state, kArgsParsingFailure, + "%s() expects an odd number of arguments, got %zu", name, argv.size()); + } + + std::vector args; + if (!ReadArgs(state, argv, &args)) { + return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name); + } + + struct stat sb; + if (lstat(args[0].c_str(), &sb) == -1) { + return ErrorAbort(state, kSetMetadataFailure, "%s: Error on lstat of \"%s\": %s", name, + args[0].c_str(), strerror(errno)); + } + + struct perm_parsed_args parsed = ParsePermArgs(state, args); + int bad = 0; + bool recursive = (strcmp(name, "set_metadata_recursive") == 0); + + if (recursive) { + recursive_parsed_args = parsed; + recursive_state = state; + bad += nftw(args[0].c_str(), do_SetMetadataRecursive, 30, FTW_CHDIR | FTW_DEPTH | FTW_PHYS); + memset(&recursive_parsed_args, 0, sizeof(recursive_parsed_args)); + recursive_state = NULL; + } else { + bad += ApplyParsedPerms(state, args[0].c_str(), &sb, parsed); + } + + if (bad > 0) { + return ErrorAbort(state, kSetMetadataFailure, "%s: some changes failed", name); + } + + return StringValue(""); +} + Value* GetPropFn(const char* name, State* state, const std::vector>& argv) { if (argv.size() != 1) { return ErrorAbort(state, kArgsParsingFailure, "%s() expects 1 arg, got %zu", name, argv.size()); @@ -879,7 +1306,25 @@ void RegisterInstallFunctions() { RegisterFunction("format", FormatFn); RegisterFunction("show_progress", ShowProgressFn); RegisterFunction("set_progress", SetProgressFn); + RegisterFunction("delete", DeleteFn); + RegisterFunction("delete_recursive", DeleteFn); + RegisterFunction("package_extract_dir", PackageExtractDirFn); RegisterFunction("package_extract_file", PackageExtractFileFn); + RegisterFunction("symlink", SymlinkFn); + + // Usage: + // set_metadata("filename", "key1", "value1", "key2", "value2", ...) + // Example: + // set_metadata("/system/bin/netcfg", "uid", 0, "gid", 3003, "mode", 02750, "selabel", + // "u:object_r:system_file:s0", "capabilities", 0x0); + RegisterFunction("set_metadata", SetMetadataFn); + + // Usage: + // set_metadata_recursive("dirname", "key1", "value1", "key2", "value2", ...) + // Example: + // set_metadata_recursive("/system", "uid", 0, "gid", 0, "fmode", 0644, "dmode", 0755, + // "selabel", "u:object_r:system_file:s0", "capabilities", 0x0); + RegisterFunction("set_metadata_recursive", SetMetadataFn); RegisterFunction("getprop", GetPropFn); RegisterFunction("file_getprop", FileGetPropFn); @@ -891,6 +1336,7 @@ void RegisterInstallFunctions() { RegisterFunction("wipe_block_device", WipeBlockDeviceFn); RegisterFunction("read_file", ReadFileFn); + RegisterFunction("rename", RenameFn); RegisterFunction("write_value", WriteValueFn); RegisterFunction("wipe_cache", WipeCacheFn);