Browse Source

Revert "kill package_extract_dir"

changes for P:
 - bring back the mkdir_recursively variant which takes a timestamp.
 - add libziparchive dependency
 - fix otautil header paths

This reverts commit 53c38b1538.

Change-Id: I71c488e96a1f23aace3c38fc283aae0165129a12
Tom Marshall 3 years ago
parent
commit
c256153de8

+ 2
- 0
otautil/Android.bp View File

@@ -20,6 +20,7 @@ cc_library_static {
20 20
     srcs: [
21 21
         "SysUtil.cpp",
22 22
         "DirUtil.cpp",
23
+        "ZipUtil.cpp",
23 24
         "ThermalUtil.cpp",
24 25
         "cache_location.cpp",
25 26
         "rangeset.cpp",
@@ -28,6 +29,7 @@ cc_library_static {
28 29
     static_libs: [
29 30
         "libselinux",
30 31
         "libbase",
32
+        "libziparchive",
31 33
     ],
32 34
 
33 35
     cflags: [

+ 8
- 0
otautil/DirUtil.cpp View File

@@ -48,6 +48,11 @@ static DirStatus dir_status(const std::string& path) {
48 48
 
49 49
 int mkdir_recursively(const std::string& input_path, mode_t mode, bool strip_filename,
50 50
                       const selabel_handle* sehnd) {
51
+	return mkdir_recursively(input_path, mode, strip_filename, sehnd, NULL);
52
+}
53
+
54
+int mkdir_recursively(const std::string& input_path, mode_t mode, bool strip_filename,
55
+                      const selabel_handle* sehnd, const struct utimbuf *timestamp) {
51 56
   // Check for an empty string before we bother making any syscalls.
52 57
   if (input_path.empty()) {
53 58
     errno = ENOENT;
@@ -104,6 +109,9 @@ int mkdir_recursively(const std::string& input_path, mode_t mode, bool strip_fil
104 109
         if (err != 0) {
105 110
           return -1;
106 111
         }
112
+        if (timestamp != NULL && utime(dir_path.c_str(), timestamp)) {
113
+          return -1;
114
+        }
107 115
         break;
108 116
       }
109 117
       default:

+ 120
- 0
otautil/ZipUtil.cpp View File

@@ -0,0 +1,120 @@
1
+/*
2
+ * Copyright (C) 2016 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
+#include "ZipUtil.h"
18
+
19
+#include <errno.h>
20
+#include <fcntl.h>
21
+#include <utime.h>
22
+
23
+#include <string>
24
+
25
+#include <android-base/logging.h>
26
+#include <android-base/unique_fd.h>
27
+#include <selinux/label.h>
28
+#include <selinux/selinux.h>
29
+#include <ziparchive/zip_archive.h>
30
+
31
+#include "otautil/DirUtil.h"
32
+
33
+static constexpr mode_t UNZIP_DIRMODE = 0755;
34
+static constexpr mode_t UNZIP_FILEMODE = 0644;
35
+
36
+bool ExtractPackageRecursive(ZipArchiveHandle zip, const std::string& zip_path,
37
+                             const std::string& dest_path, const struct utimbuf* timestamp,
38
+                             struct selabel_handle* sehnd) {
39
+    if (!zip_path.empty() && zip_path[0] == '/') {
40
+        LOG(ERROR) << "ExtractPackageRecursive(): zip_path must be a relative path " << zip_path;
41
+        return false;
42
+    }
43
+    if (dest_path.empty() || dest_path[0] != '/') {
44
+        LOG(ERROR) << "ExtractPackageRecursive(): dest_path must be an absolute path " << dest_path;
45
+        return false;
46
+    }
47
+
48
+    void* cookie;
49
+    std::string target_dir(dest_path);
50
+    if (dest_path.back() != '/') {
51
+        target_dir += '/';
52
+    }
53
+    std::string prefix_path(zip_path);
54
+    if (!zip_path.empty() && zip_path.back() != '/') {
55
+        prefix_path += '/';
56
+    }
57
+    const ZipString zip_prefix(prefix_path.c_str());
58
+
59
+    int ret = StartIteration(zip, &cookie, &zip_prefix, nullptr);
60
+    if (ret != 0) {
61
+        LOG(ERROR) << "failed to start iterating zip entries.";
62
+        return false;
63
+    }
64
+
65
+    std::unique_ptr<void, decltype(&EndIteration)> guard(cookie, EndIteration);
66
+    ZipEntry entry;
67
+    ZipString name;
68
+    int extractCount = 0;
69
+    while (Next(cookie, &entry, &name) == 0) {
70
+        std::string entry_name(name.name, name.name + name.name_length);
71
+        CHECK_LE(prefix_path.size(), entry_name.size());
72
+        std::string path = target_dir + entry_name.substr(prefix_path.size());
73
+        // Skip dir.
74
+        if (path.back() == '/') {
75
+            continue;
76
+        }
77
+
78
+        if (mkdir_recursively(path.c_str(), UNZIP_DIRMODE, true, sehnd, timestamp) != 0) {
79
+            LOG(ERROR) << "failed to create dir for " << path;
80
+            return false;
81
+        }
82
+
83
+        char *secontext = NULL;
84
+        if (sehnd) {
85
+            selabel_lookup(sehnd, &secontext, path.c_str(), UNZIP_FILEMODE);
86
+            setfscreatecon(secontext);
87
+        }
88
+        android::base::unique_fd fd(open(path.c_str(), O_CREAT|O_WRONLY|O_TRUNC, UNZIP_FILEMODE));
89
+        if (fd == -1) {
90
+            PLOG(ERROR) << "Can't create target file \"" << path << "\"";
91
+            return false;
92
+        }
93
+        if (secontext) {
94
+            freecon(secontext);
95
+            setfscreatecon(NULL);
96
+        }
97
+
98
+        int err = ExtractEntryToFile(zip, &entry, fd);
99
+        if (err != 0) {
100
+            LOG(ERROR) << "Error extracting \"" << path << "\" : " << ErrorCodeString(err);
101
+            return false;
102
+        }
103
+
104
+        if (fsync(fd) != 0) {
105
+            PLOG(ERROR) << "Error syncing file descriptor when extracting \"" << path << "\"";
106
+            return false;
107
+        }
108
+
109
+        if (timestamp != nullptr && utime(path.c_str(), timestamp)) {
110
+            PLOG(ERROR) << "Error touching \"" << path << "\"";
111
+            return false;
112
+        }
113
+
114
+        LOG(INFO) << "Extracted file \"" << path << "\"";
115
+        ++extractCount;
116
+    }
117
+
118
+    LOG(INFO) << "Extracted " << extractCount << " file(s)";
119
+    return true;
120
+}

+ 57
- 0
otautil/ZipUtil.h View File

@@ -0,0 +1,57 @@
1
+/*
2
+ * Copyright (C) 2016 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
+#ifndef _OTAUTIL_ZIPUTIL_H
18
+#define _OTAUTIL_ZIPUTIL_H
19
+
20
+#include <utime.h>
21
+
22
+#include <string>
23
+
24
+#include <selinux/label.h>
25
+#include <ziparchive/zip_archive.h>
26
+
27
+/*
28
+ * Inflate all files under zip_path to the directory specified by
29
+ * dest_path, which must exist and be a writable directory. The zip_path
30
+ * is allowed to be an empty string, in which case the whole package
31
+ * will be extracted.
32
+ *
33
+ * Directory entries are not extracted.
34
+ *
35
+ * The immediate children of zip_path will become the immediate
36
+ * children of dest_path; e.g., if the archive contains the entries
37
+ *
38
+ *     a/b/c/one
39
+ *     a/b/c/two
40
+ *     a/b/c/d/three
41
+ *
42
+ * and ExtractPackageRecursive(a, "a/b/c", "/tmp", ...) is called, the resulting
43
+ * files will be
44
+ *
45
+ *     /tmp/one
46
+ *     /tmp/two
47
+ *     /tmp/d/three
48
+ *
49
+ * If timestamp is non-NULL, file timestamps will be set accordingly.
50
+ *
51
+ * Returns true on success, false on failure.
52
+ */
53
+bool ExtractPackageRecursive(ZipArchiveHandle zip, const std::string& zip_path,
54
+                             const std::string& dest_path, const struct utimbuf* timestamp,
55
+                             struct selabel_handle* sehnd);
56
+
57
+#endif // _OTAUTIL_ZIPUTIL_H

+ 5
- 0
otautil/include/otautil/DirUtil.h View File

@@ -19,6 +19,7 @@
19 19
 
20 20
 #include <limits.h> // PATH_MAX
21 21
 #include <sys/stat.h>  // mode_t
22
+#include <utime.h> // utime/utimbuf
22 23
 
23 24
 #include <string>
24 25
 
@@ -37,6 +38,10 @@ struct selabel_handle;
37 38
 int mkdir_recursively(const std::string& path, mode_t mode, bool strip_filename,
38 39
                       const struct selabel_handle* sehnd);
39 40
 
41
+// As above, but if timestamp is non-NULL, directories will be timestamped accordingly.
42
+int mkdir_recursively(const std::string& input_path, mode_t mode, bool strip_filename,
43
+                      const selabel_handle* sehnd, const struct utimbuf *timestamp);
44
+
40 45
 // rm -rf <path>
41 46
 int dirUnlinkHierarchy(const char *path);
42 47
 

+ 1
- 0
tests/Android.mk View File

@@ -40,6 +40,7 @@ LOCAL_SRC_FILES := \
40 40
     unit/rangeset_test.cpp \
41 41
     unit/sysutil_test.cpp \
42 42
     unit/zip_test.cpp \
43
+    unit/ziputil_test.cpp
43 44
 
44 45
 LOCAL_C_INCLUDES := bootable/recovery
45 46
 LOCAL_SHARED_LIBRARIES := liblog

+ 37
- 0
tests/unit/zip_test.cpp View File

@@ -24,10 +24,47 @@
24 24
 #include <android-base/test_utils.h>
25 25
 #include <gtest/gtest.h>
26 26
 #include <otautil/SysUtil.h>
27
+#include <otautil/ZipUtil.h>
27 28
 #include <ziparchive/zip_archive.h>
28 29
 
29 30
 #include "common/test_constants.h"
30 31
 
32
+TEST(ZipTest, ExtractPackageRecursive) {
33
+  std::string zip_path = from_testdata_base("ziptest_valid.zip");
34
+  ZipArchiveHandle handle;
35
+  ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle));
36
+
37
+  // Extract the whole package into a temp directory.
38
+  TemporaryDir td;
39
+  ASSERT_NE(nullptr, td.path);
40
+  ExtractPackageRecursive(handle, "", td.path, nullptr, nullptr);
41
+
42
+  // Make sure all the files are extracted correctly.
43
+  std::string path(td.path);
44
+  ASSERT_EQ(0, access((path + "/a.txt").c_str(), F_OK));
45
+  ASSERT_EQ(0, access((path + "/b.txt").c_str(), F_OK));
46
+  ASSERT_EQ(0, access((path + "/b/c.txt").c_str(), F_OK));
47
+  ASSERT_EQ(0, access((path + "/b/d.txt").c_str(), F_OK));
48
+
49
+  // The content of the file is the same as expected.
50
+  std::string content1;
51
+  ASSERT_TRUE(android::base::ReadFileToString(path + "/a.txt", &content1));
52
+  ASSERT_EQ(kATxtContents, content1);
53
+
54
+  std::string content2;
55
+  ASSERT_TRUE(android::base::ReadFileToString(path + "/b/d.txt", &content2));
56
+  ASSERT_EQ(kDTxtContents, content2);
57
+
58
+  CloseArchive(handle);
59
+
60
+  // Clean up.
61
+  ASSERT_EQ(0, unlink((path + "/a.txt").c_str()));
62
+  ASSERT_EQ(0, unlink((path + "/b.txt").c_str()));
63
+  ASSERT_EQ(0, unlink((path + "/b/c.txt").c_str()));
64
+  ASSERT_EQ(0, unlink((path + "/b/d.txt").c_str()));
65
+  ASSERT_EQ(0, rmdir((path + "/b").c_str()));
66
+}
67
+
31 68
 TEST(ZipTest, OpenFromMemory) {
32 69
   std::string zip_path = from_testdata_base("ziptest_dummy-update.zip");
33 70
   MemMapping map;

+ 191
- 0
tests/unit/ziputil_test.cpp View File

@@ -0,0 +1,191 @@
1
+/*
2
+ * Copyright 2016 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
+#include <errno.h>
18
+#include <sys/stat.h>
19
+#include <unistd.h>
20
+
21
+#include <string>
22
+
23
+#include <android-base/file.h>
24
+#include <android-base/test_utils.h>
25
+#include <gtest/gtest.h>
26
+#include <otautil/ZipUtil.h>
27
+#include <ziparchive/zip_archive.h>
28
+
29
+#include "common/test_constants.h"
30
+
31
+TEST(ZipUtilTest, invalid_args) {
32
+  std::string zip_path = from_testdata_base("ziptest_valid.zip");
33
+  ZipArchiveHandle handle;
34
+  ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle));
35
+
36
+  // zip_path must be a relative path.
37
+  ASSERT_FALSE(ExtractPackageRecursive(handle, "/a/b", "/tmp", nullptr, nullptr));
38
+
39
+  // dest_path must be an absolute path.
40
+  ASSERT_FALSE(ExtractPackageRecursive(handle, "a/b", "tmp", nullptr, nullptr));
41
+  ASSERT_FALSE(ExtractPackageRecursive(handle, "a/b", "", nullptr, nullptr));
42
+
43
+  CloseArchive(handle);
44
+}
45
+
46
+TEST(ZipUtilTest, extract_all) {
47
+  std::string zip_path = from_testdata_base("ziptest_valid.zip");
48
+  ZipArchiveHandle handle;
49
+  ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle));
50
+
51
+  // Extract the whole package into a temp directory.
52
+  TemporaryDir td;
53
+  ExtractPackageRecursive(handle, "", td.path, nullptr, nullptr);
54
+
55
+  // Make sure all the files are extracted correctly.
56
+  std::string path(td.path);
57
+  ASSERT_EQ(0, access((path + "/a.txt").c_str(), F_OK));
58
+  ASSERT_EQ(0, access((path + "/b.txt").c_str(), F_OK));
59
+  ASSERT_EQ(0, access((path + "/b/c.txt").c_str(), F_OK));
60
+  ASSERT_EQ(0, access((path + "/b/d.txt").c_str(), F_OK));
61
+
62
+  // The content of the file is the same as expected.
63
+  std::string content1;
64
+  ASSERT_TRUE(android::base::ReadFileToString(path + "/a.txt", &content1));
65
+  ASSERT_EQ(kATxtContents, content1);
66
+
67
+  std::string content2;
68
+  ASSERT_TRUE(android::base::ReadFileToString(path + "/b/d.txt", &content2));
69
+  ASSERT_EQ(kDTxtContents, content2);
70
+
71
+  // Clean up the temp files under td.
72
+  ASSERT_EQ(0, unlink((path + "/a.txt").c_str()));
73
+  ASSERT_EQ(0, unlink((path + "/b.txt").c_str()));
74
+  ASSERT_EQ(0, unlink((path + "/b/c.txt").c_str()));
75
+  ASSERT_EQ(0, unlink((path + "/b/d.txt").c_str()));
76
+  ASSERT_EQ(0, rmdir((path + "/b").c_str()));
77
+
78
+  CloseArchive(handle);
79
+}
80
+
81
+TEST(ZipUtilTest, extract_prefix_with_slash) {
82
+  std::string zip_path = from_testdata_base("ziptest_valid.zip");
83
+  ZipArchiveHandle handle;
84
+  ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle));
85
+
86
+  // Extract all the entries starting with "b/".
87
+  TemporaryDir td;
88
+  ExtractPackageRecursive(handle, "b/", td.path, nullptr, nullptr);
89
+
90
+  // Make sure all the files with "b/" prefix are extracted correctly.
91
+  std::string path(td.path);
92
+  ASSERT_EQ(0, access((path + "/c.txt").c_str(), F_OK));
93
+  ASSERT_EQ(0, access((path + "/d.txt").c_str(), F_OK));
94
+
95
+  // And the rest are not extracted.
96
+  ASSERT_EQ(-1, access((path + "/a.txt").c_str(), F_OK));
97
+  ASSERT_EQ(ENOENT, errno);
98
+  ASSERT_EQ(-1, access((path + "/b.txt").c_str(), F_OK));
99
+  ASSERT_EQ(ENOENT, errno);
100
+
101
+  // The content of the file is the same as expected.
102
+  std::string content1;
103
+  ASSERT_TRUE(android::base::ReadFileToString(path + "/c.txt", &content1));
104
+  ASSERT_EQ(kCTxtContents, content1);
105
+
106
+  std::string content2;
107
+  ASSERT_TRUE(android::base::ReadFileToString(path + "/d.txt", &content2));
108
+  ASSERT_EQ(kDTxtContents, content2);
109
+
110
+  // Clean up the temp files under td.
111
+  ASSERT_EQ(0, unlink((path + "/c.txt").c_str()));
112
+  ASSERT_EQ(0, unlink((path + "/d.txt").c_str()));
113
+
114
+  CloseArchive(handle);
115
+}
116
+
117
+TEST(ZipUtilTest, extract_prefix_without_slash) {
118
+  std::string zip_path = from_testdata_base("ziptest_valid.zip");
119
+  ZipArchiveHandle handle;
120
+  ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle));
121
+
122
+  // Extract all the file entries starting with "b/".
123
+  TemporaryDir td;
124
+  ExtractPackageRecursive(handle, "b", td.path, nullptr, nullptr);
125
+
126
+  // Make sure all the files with "b/" prefix are extracted correctly.
127
+  std::string path(td.path);
128
+  ASSERT_EQ(0, access((path + "/c.txt").c_str(), F_OK));
129
+  ASSERT_EQ(0, access((path + "/d.txt").c_str(), F_OK));
130
+
131
+  // And the rest are not extracted.
132
+  ASSERT_EQ(-1, access((path + "/a.txt").c_str(), F_OK));
133
+  ASSERT_EQ(ENOENT, errno);
134
+  ASSERT_EQ(-1, access((path + "/b.txt").c_str(), F_OK));
135
+  ASSERT_EQ(ENOENT, errno);
136
+
137
+  // The content of the file is the same as expected.
138
+  std::string content1;
139
+  ASSERT_TRUE(android::base::ReadFileToString(path + "/c.txt", &content1));
140
+  ASSERT_EQ(kCTxtContents, content1);
141
+
142
+  std::string content2;
143
+  ASSERT_TRUE(android::base::ReadFileToString(path + "/d.txt", &content2));
144
+  ASSERT_EQ(kDTxtContents, content2);
145
+
146
+  // Clean up the temp files under td.
147
+  ASSERT_EQ(0, unlink((path + "/c.txt").c_str()));
148
+  ASSERT_EQ(0, unlink((path + "/d.txt").c_str()));
149
+
150
+  CloseArchive(handle);
151
+}
152
+
153
+TEST(ZipUtilTest, set_timestamp) {
154
+  std::string zip_path = from_testdata_base("ziptest_valid.zip");
155
+  ZipArchiveHandle handle;
156
+  ASSERT_EQ(0, OpenArchive(zip_path.c_str(), &handle));
157
+
158
+  // Set the timestamp to 8/1/2008.
159
+  constexpr struct utimbuf timestamp = { 1217592000, 1217592000 };
160
+
161
+  // Extract all the entries starting with "b/".
162
+  TemporaryDir td;
163
+  ExtractPackageRecursive(handle, "b", td.path, &timestamp, nullptr);
164
+
165
+  // Make sure all the files with "b/" prefix are extracted correctly.
166
+  std::string path(td.path);
167
+  std::string file_c = path + "/c.txt";
168
+  std::string file_d = path + "/d.txt";
169
+  ASSERT_EQ(0, access(file_c.c_str(), F_OK));
170
+  ASSERT_EQ(0, access(file_d.c_str(), F_OK));
171
+
172
+  // Verify the timestamp.
173
+  timespec time;
174
+  time.tv_sec = 1217592000;
175
+  time.tv_nsec = 0;
176
+
177
+  struct stat sb;
178
+  ASSERT_EQ(0, stat(file_c.c_str(), &sb)) << strerror(errno);
179
+  ASSERT_EQ(time.tv_sec, static_cast<long>(sb.st_atime));
180
+  ASSERT_EQ(time.tv_sec, static_cast<long>(sb.st_mtime));
181
+
182
+  ASSERT_EQ(0, stat(file_d.c_str(), &sb)) << strerror(errno);
183
+  ASSERT_EQ(time.tv_sec, static_cast<long>(sb.st_atime));
184
+  ASSERT_EQ(time.tv_sec, static_cast<long>(sb.st_mtime));
185
+
186
+  // Clean up the temp files under td.
187
+  ASSERT_EQ(0, unlink(file_c.c_str()));
188
+  ASSERT_EQ(0, unlink(file_d.c_str()));
189
+
190
+  CloseArchive(handle);
191
+}

+ 1
- 0
updater/Android.mk View File

@@ -48,6 +48,7 @@ updater_common_static_libraries := \
48 48
     libcutils \
49 49
     libtune2fs \
50 50
     libbrotli \
51
+    libziparchive \
51 52
     $(tune2fs_static_libraries)
52 53
 
53 54
 # libupdater (static library)

+ 33
- 0
updater/install.cpp View File

@@ -62,6 +62,8 @@
62 62
 #include "otautil/DirUtil.h"
63 63
 #include "otautil/error_code.h"
64 64
 #include "otautil/print_sha1.h"
65
+#include "otautil/ZipUtil.h"
66
+#include "tune2fs.h"
65 67
 #include "updater/updater.h"
66 68
 
67 69
 // Send over the buffer to recovery though the command pipe.
@@ -135,6 +137,36 @@ Value* UIPrintFn(const char* name, State* state, const std::vector<std::unique_p
135 137
   return StringValue(buffer);
136 138
 }
137 139
 
140
+// package_extract_dir(package_dir, dest_dir)
141
+//   Extracts all files from the package underneath package_dir and writes them to the
142
+//   corresponding tree beneath dest_dir. Any existing files are overwritten.
143
+//   Example: package_extract_dir("system", "/system")
144
+//
145
+//   Note: package_dir needs to be a relative path; dest_dir needs to be an absolute path.
146
+Value* PackageExtractDirFn(const char* name, State* state,
147
+                           const std::vector<std::unique_ptr<Expr>>&argv) {
148
+  if (argv.size() != 2) {
149
+    return ErrorAbort(state, kArgsParsingFailure, "%s() expects 2 args, got %zu", name,
150
+                      argv.size());
151
+  }
152
+
153
+  std::vector<std::string> args;
154
+  if (!ReadArgs(state, argv, &args)) {
155
+    return ErrorAbort(state, kArgsParsingFailure, "%s() Failed to parse the argument(s)", name);
156
+  }
157
+  const std::string& zip_path = args[0];
158
+  const std::string& dest_path = args[1];
159
+
160
+  ZipArchiveHandle za = static_cast<UpdaterInfo*>(state->cookie)->package_zip;
161
+
162
+  // To create a consistent system image, never use the clock for timestamps.
163
+  constexpr struct utimbuf timestamp = { 1217592000, 1217592000 };  // 8/1/2008 default
164
+
165
+  bool success = ExtractPackageRecursive(za, zip_path, dest_path, &timestamp, sehandle);
166
+
167
+  return StringValue(success ? "t" : "");
168
+}
169
+
138 170
 // package_extract_file(package_file[, dest_file])
139 171
 //   Extracts a single package_file from the update package and writes it to dest_file,
140 172
 //   overwriting existing files if necessary. Without the dest_file argument, returns the
@@ -1045,6 +1077,7 @@ void RegisterInstallFunctions() {
1045 1077
   RegisterFunction("format", FormatFn);
1046 1078
   RegisterFunction("show_progress", ShowProgressFn);
1047 1079
   RegisterFunction("set_progress", SetProgressFn);
1080
+  RegisterFunction("package_extract_dir", PackageExtractDirFn);
1048 1081
   RegisterFunction("package_extract_file", PackageExtractFileFn);
1049 1082
 
1050 1083
   RegisterFunction("getprop", GetPropFn);