Fix StepCopyBackup's undo operation
[platform/core/appfw/app-installers.git] / src / common / step / backup / step_copy_backup.cc
1 // Copyright (c) 2015 Samsung Electronics Co., Ltd All Rights Reserved
2 // Use of this source code is governed by a apache 2.0 license that can be
3 // found in the LICENSE file.
4
5 #include "common/step/backup/step_copy_backup.h"
6
7 #include <sys/types.h>
8 #include <tzplatform_config.h>
9
10 #include <boost/filesystem/operations.hpp>
11 #include <boost/system/error_code.hpp>
12
13 #include <cassert>
14 #include <cstdint>
15 #include <string>
16
17 #include "common/utils/paths.h"
18 #include "common/shared_dirs.h"
19 #include "common/utils/file_util.h"
20 #include "common/utils/user_util.h"
21
22 namespace bf = boost::filesystem;
23 namespace bs = boost::system;
24 namespace ci = common_installer;
25
26 namespace {
27
28 const char kExternalMemoryMountPoint[] = ".mmc";
29 const char kSharedResPath[] = "shared/res";
30
31 bool CheckFreeSpace(const bf::path& backup_path, const bf::path& shared_path) {
32   int64_t shared_size = ci::GetDirectorySize(shared_path);
33
34   if (!ci::CheckFreeSpaceAtPath(shared_size, backup_path))
35     return false;
36
37   return true;
38 }
39
40 bool CreateSharedRes(const bf::path& src, const bf::path& dst) {
41   bs::error_code error;
42
43   bf::create_directories(dst / kSharedResPath, error);
44   if (error) {
45     LOG(ERROR) << "Cannot create package directory";
46     return false;
47   }
48
49   if (!ci::CopyOwnershipAndPermissions(src / "shared", dst / "shared") ||
50       !ci::CopyOwnershipAndPermissions(src / kSharedResPath,
51           dst / kSharedResPath))
52     return false;
53
54   return true;
55 }
56
57 bool Move(const boost::filesystem::path& from,
58     const boost::filesystem::path& to,
59     common_installer::FSFlag flag = common_installer::FSFlag::FS_NONE) {
60   if (bf::is_directory(from)) {
61     if (!common_installer::MoveDir(from, to / from.filename(), flag)) {
62       LOG(ERROR) << "Failed to move directory: " << from;
63       return false;
64     }
65   } else {
66     if (!common_installer::MoveFile(from, to / from.filename())) {
67       LOG(ERROR) << "Fail to move file: " << from;
68       return false;
69     }
70   }
71
72   return true;
73 }
74
75 }  // namespace
76
77 namespace common_installer {
78 namespace backup {
79
80 Step::Status StepCopyBackup::precheck() {
81   if (context_->pkgid.get().empty()) {
82     LOG(ERROR) << "pkgid attribute is empty";
83     return Step::Status::PACKAGE_NOT_FOUND;
84   }
85
86   if (context_->root_application_path.get().empty()) {
87     LOG(ERROR) << "root_application_path attribute is empty";
88     return Step::Status::INVALID_VALUE;
89   }
90
91   install_path_ = context_->GetPkgPath();
92   backup_path_ = GetBackupPathForPackagePath(context_->GetPkgPath());
93
94   // We only "copy" shared dir for backup.
95   // So if there is no shared dir, we don't need to check free space.
96   bf::path shared_dir = install_path_ / "shared";
97   if (!bf::exists(shared_dir))
98     return Status::OK;
99
100   if (!CheckFreeSpace(backup_path_, shared_dir)) {
101     LOG(ERROR) << "not enough space for backup";
102     return Step::Status::OUT_OF_SPACE;
103   }
104
105   return Status::OK;
106 }
107
108 Step::Status StepCopyBackup::process() {
109   if (!CleanBackupDirectory())
110     return Status::APP_DIR_ERROR;
111
112   if (!Backup())
113     return Status::APP_DIR_ERROR;
114
115   if (!NewContent())
116     return Status::APP_DIR_ERROR;
117
118   RemoveContent();
119
120   return Status::OK;
121 }
122
123 Step::Status StepCopyBackup::clean() {
124   LOG(DEBUG) << "Remove Applications files backup directory";
125   CleanBackupDirectory();
126
127   if (context_->external_storage)
128     context_->external_storage->Commit();
129
130   return Status::OK;
131 }
132
133 Step::Status StepCopyBackup::undo() {
134   if (context_->external_storage)
135     context_->external_storage->Abort();
136
137   if (!bf::exists(backup_path_))
138     return Status::OK;
139
140   if (!RollbackApplicationDirectory()) {
141     LOG(ERROR) << "Failed to revert package directory";
142     return Status::APP_DIR_ERROR;
143   }
144
145   LOG(DEBUG) << "Application files reverted from backup";
146
147   return Status::OK;
148 }
149
150 bool StepCopyBackup::Backup() {
151   bs::error_code error;
152
153   if (!bf::exists(backup_path_)) {
154     bf::create_directories(backup_path_, error);
155     if (error) {
156       LOG(ERROR) << "Failed to create backup directory: " << backup_path_;
157       return false;
158     }
159   }
160   // create copy of old package content skipping the external memory mount point
161   for (bf::directory_iterator iter(context_->GetPkgPath());
162        iter != bf::directory_iterator(); ++iter) {
163     if (iter->path().filename() == kExternalMemoryMountPoint)
164       continue;
165
166     // external storage directories are mounted
167     // therefore move only content
168     if (context_->external_storage) {
169       auto& ext_dirs = context_->external_storage->external_dirs();
170       auto found = std::find(ext_dirs.begin(), ext_dirs.end(),
171                              iter->path().filename());
172       if (found != ext_dirs.end()) {
173         bool done = MoveMountPointContent(iter->path(),
174             backup_path_ / iter->path().filename());
175         if (!done) {
176           LOG(ERROR) << "Failed to move: " << iter->path();
177           return false;
178         }
179         continue;
180       }
181     }
182
183     if (!Move(iter->path(), backup_path_))
184       return false;
185   }
186
187   if (ShouldBackupSharedRes()) {
188     if (!CreateSharedRes(backup_path_, context_->GetPkgPath()))
189       return false;
190   }
191
192   AddRecoveryInfo();
193
194   LOG(INFO) << "Old package context saved to: " << backup_path_;
195   return true;
196 }
197
198
199 bool StepCopyBackup::MoveMountPointContent(const boost::filesystem::path& from,
200                                            const boost::filesystem::path& to) {
201   bs::error_code error;
202   bf::create_directories(to, error);
203
204   for (bf::directory_iterator iter(from);
205        iter != bf::directory_iterator(); ++iter) {
206     if (bf::is_symlink(symlink_status(iter->path()))) {
207       bf::copy_symlink(iter->path(), to / iter->path().filename(), error);
208       if (error) {
209         LOG(ERROR) << "Failed to backup package symlink: " << iter->path();
210         return false;
211       }
212     } else {
213       if (!Move(iter->path(), to)) {
214         LOG(ERROR) << "Fail to backup package file: " << iter->path();
215         return false;
216       }
217     }
218   }
219
220   return true;
221 }
222
223 void StepCopyBackup::RemoveContent() {
224   if (context_->request_type.get() == RequestType::Update &&
225     !context_->external_storage && bf::exists(install_path_ / ".mmc")) {
226     LOG(WARNING) << "Remove unnecessary files for external storage";
227
228     bs::error_code error;
229     bf::remove((install_path_ / ".mmc"), error);
230     if (error)
231       LOG(WARNING) << "error while remove files";
232   }
233 }
234
235 bool StepCopyBackup::NewContent() {
236   ci::RemoveRWDirectories(context_->unpacked_dir_path.get());
237
238   bs::error_code error;
239   bf::create_directories(install_path_.parent_path(), error);
240   if (error) {
241     LOG(ERROR) << "Cannot create package directory";
242     return false;
243   }
244
245   for (bf::directory_iterator iter(context_->unpacked_dir_path.get());
246        iter != bf::directory_iterator(); ++iter) {
247     if (!Move(iter->path(), install_path_, FS_MERGE_SKIP))
248       return false;
249   }
250
251   // If other application tries to access shared/res of package being installed,
252   // it will be failed due to permission deny. Set its permission before
253   // StepChangeOwnershipAndPermission to prevent it.
254   uid_t uid = context_->uid.get();
255   boost::optional<gid_t> gid = common_installer::GetGidByUid(uid);
256   if (!gid) {
257     LOG(ERROR) << "Failed to get gid";
258     return false;
259   }
260   if (bf::exists(install_path_ / "shared/res") &&
261       !common_installer::SetOwnershipAll(
262           install_path_ / "shared/res", uid, *gid)) {
263     LOG(ERROR) << "Failed to set ownership";
264     return false;
265   }
266   LOG(INFO) << "Successfully move: " << context_->unpacked_dir_path.get()
267             << " to: " << install_path_ << " directory";
268
269   ci::RemoveAll(context_->unpacked_dir_path.get());
270   return true;
271 }
272
273 bool StepCopyBackup::CleanBackupDirectory() {
274   return ci::RemoveAll(backup_path_);
275 }
276
277 bool StepCopyBackup::RollbackApplicationDirectory() {
278   bs::error_code error;
279   bf::path root_path = context_->GetPkgPath();
280   if (bf::exists(root_path)) {
281     for (bf::directory_iterator iter(root_path);
282          iter != bf::directory_iterator(); ++iter) {
283       bf::remove_all(iter->path(), error);
284       if (error)
285         return false;
286     }
287   }
288
289   for (bf::directory_iterator iter(backup_path_);
290        iter != bf::directory_iterator(); ++iter) {
291     if (!Move(iter->path(), root_path)) {
292       LOG(ERROR) << "Failed to recovery backup file(" << iter->path() << ")";
293       return false;
294     }
295   }
296   RemoveAll(backup_path_);
297
298   uid_t uid = context_->uid.get();
299   // restore ownership changed during installation
300   if (!SetPackageDirectoryOwnerAndPermissions(context_->GetPkgPath(), uid))
301     return false;
302
303   return true;
304 }
305
306 void StepCopyBackup::AddRecoveryInfo() {
307   recovery::RecoveryFile* recovery_file =
308       context_->recovery_info.get().recovery_file.get();
309   recovery_file->set_backup_done(true);
310   recovery_file->WriteAndCommitFileContent();
311 }
312
313 bool StepCopyBackup::ShouldBackupSharedRes() {
314   if (bf::exists(backup_path_ / kSharedResPath) &&
315       bf::exists(context_->unpacked_dir_path.get() / kSharedResPath))
316     return true;
317
318   return false;
319 }
320
321 }  // namespace backup
322 }  // namespace common_installer