PKG_CHECK_MODULES(SECURITY_MANAGER_DEPS REQUIRED security-manager)
PKG_CHECK_MODULES(MANIFEST_PARSER_DEPS REQUIRED manifest-parser)
PKG_CHECK_MODULES(MANIFEST_HANDLERS_DEPS REQUIRED manifest-handlers)
+PKG_CHECK_MODULES(DELTA_MANIFEST_HANDLERS_DEPS REQUIRED delta-manifest-handlers)
PKG_CHECK_MODULES(TPK_MANIFEST_HANDLERS_DEPS REQUIRED tpk-manifest-handlers)
PKG_CHECK_MODULES(CERT_SVC_DEPS_VCORE REQUIRED cert-svc-vcore)
PKG_CHECK_MODULES(PKGMGR_PARSER_DEPS REQUIRED pkgmgr-parser)
BuildRequires: pkgconfig(cert-svc-vcore)
BuildRequires: pkgconfig(manifest-parser)
BuildRequires: pkgconfig(manifest-handlers)
+BuildRequires: pkgconfig(delta-manifest-handlers)
BuildRequires: pkgconfig(capi-security-privilege-manager)
BuildRequires: pkgconfig(libwebappenc)
BuildRequires: pkgconfig(capi-appfw-app-manager)
Requires: ca-certificates-tizen
Requires: libtzplatform-config
+Requires: xdelta3
%description
This is a meta package that installs the common application
step/step_copy_backup.cc
step/step_copy_storage_directories.cc
step/step_create_storage_directories.cc
+ step/step_delta_patch.cc
step/step_fail.cc
step/step_kill_apps.cc
step/step_generate_xml.cc
ZLIB_DEPS
PRIVILEGE_CHECKER_DEPS
APPMANAGER_DEPS
+ DELTA_MANIFEST_HANDLERS_DEPS
Boost
)
#include "common/pkgmgr_interface.h"
+#include <boost/filesystem/path.hpp>
+
#include <memory>
+#include <string>
#include "common/app_query_interface.h"
+namespace bf = boost::filesystem;
+
+namespace {
+
+const char kDeltaFileExtension[] = ".delta";
+
+}
+
namespace common_installer {
PkgMgrPtr PkgMgrInterface::Create(int argc, char** argv,
RequestType PkgMgrInterface::GetRequestType() const {
switch (pkgmgr_installer_get_request_type(pi_)) {
- case PKGMGR_REQ_INSTALL:
+ case PKGMGR_REQ_INSTALL : {
+ auto request_info = GetRequestInfo();
+ if (!request_info)
+ return RequestType::Unknown;
+ std::string extension = bf::path(request_info).extension().string();
if (!is_app_installed_) {
- return RequestType::Install;
+ if (extension == kDeltaFileExtension) {
+ LOG(ERROR) << "Package is not installed. "
+ "Cannot update from delta package";
+ return RequestType::Unknown;
+ } else {
+ return RequestType::Install;
+ }
} else {
- return RequestType::Update;
+ if (extension == kDeltaFileExtension)
+ return RequestType::Delta;
+ else
+ return RequestType::Update;
}
+ }
case PKGMGR_REQ_UNINSTALL:
return RequestType::Uninstall;
case PKGMGR_REQ_REINSTALL:
{ci::RequestType::Recovery, PKGMGR_INSTALLER_INSTALL_EVENT_STR},
{ci::RequestType::Reinstall, PKGMGR_INSTALLER_INSTALL_EVENT_STR},
{ci::RequestType::Uninstall, PKGMGR_INSTALLER_UNINSTALL_EVENT_STR},
- {ci::RequestType::Unknown, PKGMGR_INSTALLER_INSTALL_EVENT_STR}, // not needed
- {ci::RequestType::Update, PKGMGR_INSTALLER_UPGRADE_EVENT_STR}
+ {ci::RequestType::Update, PKGMGR_INSTALLER_UPGRADE_EVENT_STR},
+ {ci::RequestType::Delta, PKGMGR_INSTALLER_UPGRADE_EVENT_STR}
};
} // namespace
}
auto key = kEventStr.find(request_type_);
+ if (key == kEventStr.end()) {
+ return false;
+ }
if (!SendSignal(PKGMGR_INSTALLER_START_KEY_STR, key->second, type, pkgid)) {
return false;
}
const char kRecoveryUpdateInstallationString[] = "UPDATE";
const char kRecoveryUninstallationString[] = "UNINSTALLATION";
const char kRecoveryRdsString[] = "RDS";
+const char kRecoveryDeltaString[] = "DELTA";
const char kRecoveryUnknownString[] = "UNKNOWN";
std::string TruncateNewLine(const char* data) {
type_ = RequestType::Uninstall;
} else if (mode == kRecoveryRdsString) {
type_ = RequestType::Reinstall;
+ } else if (mode == kRecoveryDeltaString) {
+ type_ = RequestType::Delta;
} else {
type_ = RequestType::Unknown;
}
case RequestType::Reinstall:
fputs(kRecoveryRdsString, handle);
break;
+ case RequestType::Delta:
+ fputs(kRecoveryDeltaString, handle);
+ break;
default:
fputs(kRecoveryUnknownString, handle);
break;
Update,
Uninstall,
Reinstall,
+ Delta,
Recovery
};
context_->pkgid.set(kStrEmpty);
context_->file_path.set(kStrEmpty);
break;
+ case RequestType::Delta:
+ context_->unpacked_dir_path.set(kStrEmpty);
+ context_->pkgid.set(kStrEmpty);
+ context_->file_path.set(pkgmgr_->GetRequestInfo());
+ break;
case RequestType::Recovery:
context_->file_path.set(pkgmgr_->GetRequestInfo());
context_->pkgid.set(kStrEmpty);
--- /dev/null
+// Copyright (c) 2015 Samsung Electronics Co., Ltd All Rights Reserved
+// Use of this source code is governed by an apache-2.0 license that can be
+// found in the LICENSE file.
+
+#include "common/step/step_delta_patch.h"
+
+#include <boost/system/error_code.hpp>
+#include <boost/filesystem/path.hpp>
+#include <delta/delta_handler.h>
+#include <delta/delta_parser.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <cstdlib>
+
+#include "common/utils/file_util.h"
+
+namespace bf = boost::filesystem;
+namespace bs = boost::system;
+namespace ci = common_installer;
+
+namespace {
+
+const char kBinaryDir[] = "bin";
+const char kCacheDir[] = "cache";
+const char kDataDir[] = "data";
+const char kSharedData[] = "shared/data";
+const char kSharedTrusted[] = "shared/trusted";
+
+const char kDeltaFile[] = "delta_info.xml";
+const char kXDeltaBinary[] = "/usr/bin/xdelta3";
+
+bool ValidateDeltaInfo(const delta::DeltaInfo& info) {
+ for (auto& item : info.added()) {
+ if (ci::HasDirectoryClimbing(item))
+ return false;
+ }
+ for (auto& item : info.modified()) {
+ if (ci::HasDirectoryClimbing(item))
+ return false;
+ }
+ for (auto& item : info.removed()) {
+ if (ci::HasDirectoryClimbing(item))
+ return false;
+ }
+ return true;
+}
+
+void RemoveBinarySymlinks(const bf::path& dir) {
+ for (bf::directory_iterator iter(dir / kBinaryDir);
+ iter != bf::directory_iterator(); ++iter) {
+ if (bf::is_symlink(iter->path())) {
+ // FIXME: note that this assumes that it is not possible to create
+ // explicitly symlinks in bin/ directory pointing to whatever
+ bs::error_code error;
+ bf::remove(iter->path(), error);
+ }
+ }
+}
+
+void RemoveStorageDirectories(const bf::path& dir) {
+ bs::error_code error;
+ bf::remove_all(dir / kDataDir, error);
+ bf::remove_all(dir / kCacheDir, error);
+ bf::remove_all(dir / kSharedData, error);
+ bf::remove_all(dir / kSharedTrusted, error);
+}
+
+bool ApplyDeletedFiles(const delta::DeltaInfo& info, const bf::path& app_dir) {
+ for (auto& relative : info.removed()) {
+ bs::error_code error;
+ bf::remove_all(app_dir / relative, error);
+ if (error) {
+ LOG(WARNING) << "Failed to remove";
+ }
+ LOG(DEBUG) << "Deleted: " << relative;
+ }
+ return true;
+}
+
+bool ApplyModifiedFiles(const delta::DeltaInfo& info, const bf::path& app_dir,
+ const bf::path& patch_dir) {
+ for (auto& relative : info.modified()) {
+ bf::path temp_file = ci::GenerateTemporaryPath(
+ bf::path(ci::GetRootAppPath()) / "tmp_file");
+ bf::path patch_file = patch_dir / relative;
+ bf::path input = app_dir / relative;
+ if (!bf::is_regular_file(input)) {
+ LOG(ERROR) << "Cannot modify. Not a regular file: " << input;
+ return false;
+ }
+ const char* const argv[] = {
+ kXDeltaBinary,
+ "-d",
+ "-s",
+ input.c_str(),
+ patch_file.c_str(),
+ temp_file.c_str(),
+ nullptr,
+ };
+ pid_t pid = fork();
+ if (pid == 0) {
+ int ret = execv(argv[0], const_cast<char* const*>(argv));
+ if (ret != 0) {
+ // no other thing to -> do just quit
+ exit(-1);
+ }
+ } else if (pid == -1) {
+ LOG(ERROR) << "Failed to fork with errno: " << errno;
+ return false;
+ } else {
+ int status;
+ waitpid(pid, &status, 0);
+ if (status != 0) {
+ LOG(ERROR) << "xdelta3 failed with error code: " << status;
+ return false;
+ }
+ }
+ bs::error_code error;
+ bf::copy_file(temp_file, input, bf::copy_option::overwrite_if_exists,
+ error);
+ if (error) {
+ LOG(ERROR) << "Failed to copy from " << temp_file << " to " << input;
+ bf::remove(temp_file, error);
+ return false;
+ }
+ bf::remove(temp_file, error);
+ LOG(DEBUG) << "Patched: " << relative;
+ }
+ return true;
+}
+
+bool ApplyAddedFiles(const delta::DeltaInfo& info, const bf::path& app_dir,
+ const bf::path& patch_dir) {
+ for (auto& relative : info.added()) {
+ bf::path source = patch_dir / relative;
+ bf::path target = app_dir / relative;
+ bs::error_code error;
+ if (bf::is_directory(source)) {
+ bf::create_directories(target, error);
+ if (error) {
+ LOG(ERROR) << "Failed to add: " << relative;
+ return false;
+ }
+ } else {
+ if (bf::exists(target)) {
+ bf::remove(target, error);
+ if (error) {
+ LOG(ERROR) << "Cannot remove file: " << target;
+ return false;
+ }
+ }
+ bf::create_directories(target.parent_path(), error);
+ if (!ci::MoveFile(source, target)) {
+ LOG(ERROR) << "Failed to move file: " << source << " to " << target;
+ return false;
+ }
+ }
+ LOG(DEBUG) << "Added: " << relative;
+ }
+ return true;
+}
+
+bool ApplyPatch(const delta::DeltaInfo& info, const bf::path& app_dir,
+ const bf::path& patch_dir) {
+ if (!ApplyDeletedFiles(info, app_dir))
+ return false;
+ if (!ApplyModifiedFiles(info, app_dir, patch_dir))
+ return false;
+ if (!ApplyAddedFiles(info, app_dir, patch_dir))
+ return false;
+ return true;
+}
+
+} // namespace
+
+namespace common_installer {
+namespace filesystem {
+
+Step::Status StepDeltaPatch::precheck() {
+ if (context_->unpacked_dir_path.get().empty()) {
+ LOG(ERROR) << "Unpacked dir is not set";
+ return Status::ERROR;
+ }
+ if (context_->pkgid.get().empty()) {
+ LOG(ERROR) << "Package id is not set";
+ return Status::ERROR;
+ }
+ return Status::OK;
+}
+
+Step::Status StepDeltaPatch::process() {
+ bf::path delta_file = context_->unpacked_dir_path.get() / kDeltaFile;
+ if (!bf::exists(delta_file)) {
+ LOG(ERROR) << "Delta file doesn't exist in package.";
+ return Status::ERROR;
+ }
+ delta::DeltaParser parser;
+ if (!parser.ParseManifest(delta_file)) {
+ LOG(ERROR) << parser.GetErrorMessage();
+ return Status::ERROR;
+ }
+ std::shared_ptr<const delta::DeltaInfo> delta_info =
+ std::static_pointer_cast<const delta::DeltaInfo>(
+ parser.GetManifestData(delta::kDeltaInfoKey));
+ if (!delta_info) {
+ LOG(ERROR) << "Failed to parse delta information";
+ return Status::ERROR;
+ }
+
+ // additional validation
+ if (!ValidateDeltaInfo(*delta_info)) {
+ LOG(ERROR) << "Delta info is malformed";
+ return Status::ERROR;
+ }
+
+ // create old content directory and patch directory
+ patch_dir_ = context_->unpacked_dir_path.get();
+ patch_dir_ += ".patch";
+ if (!MoveDir(context_->unpacked_dir_path.get(), patch_dir_)) {
+ LOG(ERROR) << "Failed to move content to patch directory";
+ return Status::ERROR;
+ }
+
+ if (!CopyDir(context_->root_application_path.get() / context_->pkgid.get(),
+ context_->unpacked_dir_path.get())) {
+ LOG(ERROR) << "Failed to copy package files";
+ return Status::ERROR;
+ }
+
+ RemoveBinarySymlinks(context_->unpacked_dir_path.get());
+ RemoveStorageDirectories(context_->unpacked_dir_path.get());
+
+ // apply changes mentioned in delta
+ if (!ApplyPatch(*delta_info, context_->unpacked_dir_path.get(), patch_dir_))
+ return Status::ERROR;
+
+ bs::error_code error;
+ bf::remove_all(patch_dir_, error);
+ LOG(INFO) << "Delta patch applied successfully";
+ return Status::OK;
+}
+
+Step::Status StepDeltaPatch::undo() {
+ bs::error_code error;
+ bf::remove_all(patch_dir_, error);
+ return Status::OK;
+}
+
+} // namespace filesystem
+} // namespace common_installer
--- /dev/null
+// Copyright (c) 2015 Samsung Electronics Co., Ltd All Rights Reserved
+// Use of this source code is governed by an apache-2.0 license that can be
+// found in the LICENSE file.
+
+#ifndef COMMON_STEP_STEP_DELTA_PATCH_H_
+#define COMMON_STEP_STEP_DELTA_PATCH_H_
+
+#include "common/installer_context.h"
+
+#include "common/step/step.h"
+#include "common/utils/logging.h"
+
+namespace common_installer {
+namespace filesystem {
+
+/**
+ * @brief The StepDeltaPatch class
+ * Patches the unpack directory content according to delta file so that
+ * new package content is complete and no file is missing.
+ *
+ * Patching package files with delta is performed in unpacked_dir before all
+ * step that operates on widget's final location. That means that whole new
+ * package content is prepared before any files of package installation are
+ * touched.
+ *
+ * Flow goes as below:
+ * 1) `unpacked_dir` (where delta package is unpacked) is moved to
+ * `unpacked_dir_patch` (PATCH DIR)
+ * 2) old package content is moved to `unpacked_dir` (it becomes APP_DIR)
+ * 3) `unpacked_dir` is being applied with patches (using xdelta3 tool)
+ * 4) `unpacked_dir_patch` is removed.
+ * 5) Normal update flow proceeds as if it it was normal update installation
+ * (as the unpacked_dir contains full new version of package).
+ */
+class StepDeltaPatch : public Step {
+ public:
+ using Step::Step;
+
+ Status process() override;
+ Status clean() override { return Status::OK; }
+ Status undo() override;
+ Status precheck() override;
+
+ private:
+ boost::filesystem::path patch_dir_;
+
+ SCOPE_LOG_TAG(DeltaPatch)
+};
+
+} // namespace filesystem
+} // namespace common_installer
+
+#endif // COMMON_STEP_STEP_DELTA_PATCH_H_
return false;
}
} catch (const bf::filesystem_error& error) {
- LOG(ERROR) << error.what();
+ LOG(ERROR) << "Failed to copy directory: " << error.what();
+ return false;
}
// Iterate through the source directory
bf::copy_file(current, dst / current.filename());
}
} catch (const bf::filesystem_error& error) {
- LOG(ERROR) << error.what();
+ LOG(ERROR) << "Failed to copy directory: " << error.what();
+ return false;
}
}
return true;
bf::path filename_in_zip_path(raw_file_name_in_zip);
// prevent "directory climbing" attack
- std::vector<std::string> segments;
- ba::split(segments, filename_in_zip_path.string(), ba::is_any_of("/\\"));
- if (std::any_of(segments.begin(), segments.end(),
- [](const std::string& segment) {
- return segment == "..";
- })) {
+ if (HasDirectoryClimbing(filename_in_zip_path)) {
LOG(ERROR) << "Relative path in widget in malformed";
return false;
}
return true;
}
+bool HasDirectoryClimbing(const boost::filesystem::path& path) {
+ std::vector<std::string> segments;
+ ba::split(segments, path.string(), ba::is_any_of("/\\"));
+ return std::any_of(segments.begin(), segments.end(),
+ [](const std::string& segment) {
+ return segment == "..";
+ });
+}
+
+boost::filesystem::path MakeRelativePath(const boost::filesystem::path& input,
+ const boost::filesystem::path& base) {
+ bf::path input_absolute = bf::absolute(input);
+ bf::path base_absolute = bf::absolute(base);
+ return input_absolute.string().substr(base_absolute.string().length() + 1);
+}
+
} // namespace common_installer
const boost::filesystem::path& tmp_dir,
const std::string& filter_prefix);
+bool HasDirectoryClimbing(const boost::filesystem::path& path);
+
+boost::filesystem::path MakeRelativePath(const boost::filesystem::path& input,
+ const boost::filesystem::path& base);
+
} // namespace common_installer
#endif // COMMON_UTILS_FILE_UTIL_H_
#include "common/step/step_copy.h"
#include "common/step/step_copy_backup.h"
#include "common/step/step_check_old_certificate.h"
+#include "common/step/step_delta_patch.h"
#include "common/step/step_fail.h"
#include "common/step/step_kill_apps.h"
#include "common/step/step_generate_xml.h"
case ci::RequestType::Reinstall:
ReinstallSteps();
break;
+ case ci::RequestType::Delta:
+ DeltaSteps();
+ break;
case ci::RequestType::Recovery:
RecoverySteps();
break;
AddStep<ci::configuration::StepFail>();
}
+void TpkInstaller::DeltaSteps() {
+ AddStep<ci::configuration::StepConfigure>(pkgmgr_);
+ AddStep<ci::filesystem::StepUnzip>();
+ AddStep<tpk::parse::StepParse>();
+ AddStep<ci::filesystem::StepDeltaPatch>();
+ AddStep<ci::security::StepCheckSignature>();
+ AddStep<ci::security::StepPrivilegeCompatibility>();
+ AddStep<ci::security::StepCheckOldCertificate>();
+ AddStep<ci::backup::StepOldManifest>();
+ AddStep<ci::pkgmgr::StepKillApps>();
+ AddStep<ci::backup::StepBackupManifest>();
+ AddStep<ci::backup::StepBackupIcons>();
+ AddStep<ci::backup::StepCopyBackup>();
+ AddStep<ci::filesystem::StepCreateStorageDirectories>();
+ // TODO(t.iwanek): handle coping storage directories
+ AddStep<tpk::filesystem::StepCreateSymbolicLink>();
+ AddStep<ci::filesystem::StepCreateIcons>();
+ AddStep<ci::security::StepUpdateSecurity>();
+ AddStep<ci::pkgmgr::StepGenerateXml>();
+ AddStep<ci::pkgmgr::StepUpdateApplication>();
+}
+
void TpkInstaller::RecoverySteps() {
AddStep<ci::configuration::StepConfigure>(pkgmgr_);
AddStep<ci::recovery::StepOpenRecoveryFile>();
void UpdateSteps();
void UninstallSteps();
void ReinstallSteps();
+ void DeltaSteps();
void RecoverySteps();
SCOPE_LOG_TAG(TpkInstaller)
AddStep<ci::security::StepUpdateSecurity>();
break;
}
+ case ci::RequestType::Delta: {
+ // TODO(t.iwanek): add proper steps for this mode...
+ AddStep<ci::configuration::StepFail>();
+ }
case ci::RequestType::Recovery: {
AddStep<ci::configuration::StepConfigure>(pkgmgr_);
AddStep<ci::recovery::StepOpenRecoveryFile>();