Fix bug on StepDeltaPatch step
[platform/core/appfw/app-installers.git] / src / common / step / filesystem / step_delta_patch.cc
1 // Copyright (c) 2015 Samsung Electronics Co., Ltd All Rights Reserved
2 // Use of this source code is governed by an apache-2.0 license that can be
3 // found in the LICENSE file.
4
5 #include "common/step/filesystem/step_delta_patch.h"
6
7 #include <boost/system/error_code.hpp>
8 #include <boost/filesystem/path.hpp>
9 #include <delta/delta_handler.h>
10 #include <delta/delta_parser.h>
11 #include <sys/types.h>
12 #include <sys/wait.h>
13 #include <unistd.h>
14
15 #include <algorithm>
16 #include <cstdlib>
17 #include <memory>
18
19 #include "common/utils/file_util.h"
20 #include "common/utils/glist_range.h"
21
22 namespace bf = boost::filesystem;
23 namespace bs = boost::system;
24 namespace ci = common_installer;
25
26 namespace {
27
28 const char kBinaryDir[] = "bin";
29 const char kCacheDir[] = "cache";
30 const char kDataDir[] = "data";
31 const char kSharedData[] = "shared/data";
32 const char kSharedTrusted[] = "shared/trusted";
33
34 const char kDeltaFile[] = "delta_info.xml";
35 const char kXDeltaBinary[] = "/usr/bin/xdelta3";
36
37 const char kExternalMemoryMountPoint[] = ".mmc";
38
39 bool ValidateDeltaInfo(const delta::DeltaInfo& info) {
40   for (auto& item : info.added()) {
41     if (ci::HasDirectoryClimbing(item))
42       return false;
43   }
44   for (auto& item : info.modified()) {
45     if (ci::HasDirectoryClimbing(item))
46       return false;
47   }
48   for (auto& item : info.removed()) {
49     if (ci::HasDirectoryClimbing(item))
50       return false;
51   }
52   return true;
53 }
54
55 void RemoveBinarySymlinks(const bf::path& dir) {
56   for (bf::directory_iterator iter(dir / kBinaryDir);
57       iter != bf::directory_iterator(); ++iter) {
58     if (bf::is_symlink(symlink_status(iter->path()))) {
59       // FIXME: note that this assumes that it is not possible to create
60       // explicitly symlinks in bin/ directory pointing to whatever
61       bs::error_code error;
62       ci::Remove(iter->path());
63     }
64   }
65 }
66
67 void RemoveStorageDirectories(const bf::path& dir) {
68   ci::RemoveAll(dir / kDataDir);
69   ci::RemoveAll(dir / kCacheDir);
70   ci::RemoveAll(dir / kSharedData);
71   ci::RemoveAll(dir / kSharedTrusted);
72 }
73
74 void RemoveExtraIconFiles(const bf::path& dir, const bf::path& pkg_dir,
75                           manifest_x* manifest) {
76   for (application_x* app : GListRange<application_x*>(manifest->application)) {
77     if (strcmp("webapp", app->type) != 0)
78       continue;
79     auto range = GListRange<icon_x*>(app->icon);
80     auto iter = range.begin();
81     if (iter != range.end()) {
82       bs::error_code error;
83       std::string old_path((*iter)->text);
84       bf::path icon_copy = dir / old_path.substr(pkg_dir.string().size());
85       ci::Remove(icon_copy);
86     }
87   }
88 }
89
90 bool ApplyDeletedFiles(const delta::DeltaInfo& info, const bf::path& app_dir) {
91   for (auto& relative : info.removed()) {
92     if (!ci::Remove(app_dir / relative))
93       return false;
94     LOG(DEBUG) << "Deleted: " << relative;
95   }
96   return true;
97 }
98
99 bool ApplyModifiedFiles(const delta::DeltaInfo& info, const bf::path& app_dir,
100                         const bf::path& patch_dir, bool is_readonly,
101                         uid_t uid) {
102   for (auto& relative : info.modified()) {
103     bf::path temp_file = ci::GenerateTemporaryPath(
104         bf::path(ci::GetRootAppPath(is_readonly, uid)) / "tmp_file");
105     bf::path patch_file = patch_dir / relative;
106     bf::path input = app_dir / relative;
107     if (!bf::is_regular_file(input)) {
108       LOG(ERROR) << "Cannot modify. Not a regular file: " << input;
109       return false;
110     }
111     pid_t pid = fork();
112     if (pid == 0) {
113       const char* const argv[] = {
114         kXDeltaBinary,
115         "-D",
116         "-d",
117         "-s",
118         input.c_str(),
119         patch_file.c_str(),
120         temp_file.c_str(),
121         nullptr,
122       };
123       int ret = execv(argv[0], const_cast<char* const*>(argv));
124       if (ret != 0) {
125         // no other thing to -> do just quit
126         exit(-1);
127       }
128     } else if (pid == -1) {
129       LOG(ERROR) << "Failed to fork with errno: " << errno;
130       return false;
131     } else {
132       int status;
133       waitpid(pid, &status, 0);
134       if (status != 0) {
135         LOG(ERROR) << "xdelta3 failed with error code: " << status;
136         return false;
137       }
138     }
139     bs::error_code error;
140     bf::copy_file(temp_file, input, bf::copy_option::overwrite_if_exists,
141                   error);
142     if (error) {
143       LOG(ERROR) << "Failed to copy from " << temp_file << " to " << input;
144       bf::remove(temp_file, error);
145       return false;
146     }
147     ci::Remove(temp_file);
148     LOG(DEBUG) << "Patched: " << relative;
149   }
150   return true;
151 }
152
153 bool ApplyAddedFiles(const delta::DeltaInfo& info, const bf::path& app_dir,
154                      const bf::path& patch_dir) {
155   for (auto& relative : info.added()) {
156     bf::path source = patch_dir / relative;
157     bf::path target = app_dir / relative;
158     bs::error_code error;
159     if (bf::is_directory(source)) {
160       bf::create_directories(target, error);
161       if (error) {
162         LOG(ERROR) << "Failed to add: " << relative;
163         return false;
164       }
165     } else {
166       if (!ci::Remove(target))
167         return false;
168       if (!bf::exists(target.parent_path())) {
169         bf::create_directories(target.parent_path(), error);
170         if (error) {
171           LOG(ERROR) << "Cannot create directory: " << target.parent_path();
172           return false;
173         }
174       }
175       if (!ci::MoveFile(source, target)) {
176         LOG(ERROR) << "Failed to move file: " << source << " to " << target;
177         return false;
178       }
179     }
180     LOG(DEBUG) << "Added: " << relative;
181   }
182   return true;
183 }
184
185 bool ApplyPatch(const delta::DeltaInfo& info, const bf::path& app_dir,
186                 const bf::path& patch_dir, bool is_readonly, uid_t uid) {
187   if (!ApplyDeletedFiles(info, app_dir))
188     return false;
189   if (!ApplyModifiedFiles(info, app_dir, patch_dir, is_readonly, uid))
190     return false;
191   if (!ApplyAddedFiles(info, app_dir, patch_dir))
192     return false;
193   return true;
194 }
195
196 bool CopySkipMount(const bf::path& from, const bf::path& to) {
197   bs::error_code error;
198   bf::create_directory(to, error);
199   if (error) {
200     LOG(ERROR) << "Failed to create target directory";
201     return false;
202   }
203   for (bf::directory_iterator iter(from); iter != bf::directory_iterator();
204        ++iter) {
205     if (iter->path().filename() == kExternalMemoryMountPoint)
206       continue;
207
208     if (bf::is_directory(iter->path())) {
209       if (!ci::CopyDir(iter->path(), to / iter->path().filename())) {
210         LOG(ERROR) << "Failed to create copy of: " << iter->path();
211         return false;
212       }
213     } else {
214       bs::error_code error;
215       bf::copy(iter->path(), to / iter->path().filename(), error);
216       if (error) {
217         LOG(ERROR) << "Failed to create copy of: " << iter->path();
218         return false;
219       }
220     }
221   }
222   return true;
223 }
224
225 }  // namespace
226
227 namespace common_installer {
228 namespace filesystem {
229
230 StepDeltaPatch::StepDeltaPatch(InstallerContext* context,
231                                const std::string& delta_root)
232     : Step(context),
233       delta_root_(delta_root) {
234 }
235
236 Step::Status StepDeltaPatch::precheck() {
237   if (context_->unpacked_dir_path.get().empty()) {
238     LOG(ERROR) << "Unpacked dir is not set";
239     return Status::INVALID_VALUE;
240   }
241   if (context_->pkgid.get().empty()) {
242     LOG(ERROR) << "Package id is not set";
243     return Status::PACKAGE_NOT_FOUND;
244   }
245   return Status::OK;
246 }
247
248 Step::Status StepDeltaPatch::process() {
249   bf::path delta_file = context_->unpacked_dir_path.get() / kDeltaFile;
250   if (!bf::exists(delta_file)) {
251     LOG(ERROR) << "Delta file doesn't exist in package.";
252     return Status::DELTA_ERROR;
253   }
254   delta::DeltaParser parser;
255   if (!parser.ParseManifest(delta_file)) {
256     LOG(ERROR) << parser.GetErrorMessage();
257     return Status::DELTA_ERROR;
258   }
259   std::shared_ptr<const delta::DeltaInfo> delta_info =
260       std::static_pointer_cast<const delta::DeltaInfo>(
261         parser.GetManifestData(delta::kDeltaInfoKey));
262   if (!delta_info) {
263     LOG(ERROR) << "Failed to parse delta information";
264     return Status::DELTA_ERROR;
265   }
266
267   // additional validation
268   if (!ValidateDeltaInfo(*delta_info)) {
269     LOG(ERROR) << "Delta info is malformed";
270     return Status::DELTA_ERROR;
271   }
272
273   // create old content directory and patch directory
274   patch_dir_ = context_->unpacked_dir_path.get();
275   patch_dir_ += ".patch";
276   if (!MoveDir(context_->unpacked_dir_path.get(), patch_dir_)) {
277     LOG(ERROR) << "Failed to move content to patch directory";
278     return Status::DELTA_ERROR;
279   }
280
281   if (!CopySkipMount(
282       context_->root_application_path.get() / context_->pkgid.get()
283           / delta_root_,
284       context_->unpacked_dir_path.get())) {
285     LOG(ERROR) << "Failed to copy package files";
286     return Status::DELTA_ERROR;
287   }
288
289   // if there is no root set, that means we need to handle files added by
290   // installer itself (during installation) and files added during runtime
291   // they will be restored in process so just remove extra copy here, so that
292   // it doesn't interfere with installation process
293   if (delta_root_.empty()) {
294     RemoveBinarySymlinks(context_->unpacked_dir_path.get());
295     RemoveStorageDirectories(context_->unpacked_dir_path.get());
296     RemoveExtraIconFiles(
297         context_->unpacked_dir_path.get(),
298         context_->root_application_path.get() / context_->pkgid.get(),
299         context_->old_manifest_data.get());
300   }
301
302   // apply changes mentioned in delta
303   if (!ApplyPatch(*delta_info, context_->unpacked_dir_path.get(), patch_dir_,
304     context_->is_readonly_package.get(), context_->uid.get()))
305     return Status::DELTA_ERROR;
306
307   ci::RemoveAll(patch_dir_);
308   LOG(INFO) << "Delta patch applied successfully";
309   return Status::OK;
310 }
311
312 Step::Status StepDeltaPatch::undo() {
313   RemoveAll(patch_dir_);
314   return Status::OK;
315 }
316
317 }  // namespace filesystem
318 }  // namespace common_installer