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