1 // Copyright (c) 2014 Intel Corporation. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "xwalk/application/tools/tizen/xwalk_package_installer.h"
10 #include <pkgmgr/pkgmgr_parser.h>
16 #include "base/file_util.h"
17 #include "base/files/file_enumerator.h"
18 #include "base/logging.h"
19 #include "base/path_service.h"
20 #include "base/command_line.h"
21 #include "base/process/launch.h"
22 #include "base/version.h"
23 #include "third_party/libxml/chromium/libxml_utils.h"
24 #include "xwalk/application/common/application_data.h"
25 #include "xwalk/application/common/application_file_util.h"
26 #include "xwalk/application/common/application_manifest_constants.h"
27 #include "xwalk/application/common/id_util.h"
28 #include "xwalk/application/common/manifest_handlers/tizen_application_handler.h"
29 #include "xwalk/application/common/manifest_handlers/tizen_metadata_handler.h"
30 #include "xwalk/application/common/permission_policy_manager.h"
31 #include "xwalk/application/common/tizen/application_storage.h"
32 #include "xwalk/application/tools/tizen/xwalk_packageinfo_constants.h"
33 #include "xwalk/runtime/common/xwalk_paths.h"
35 namespace info = application_packageinfo_constants;
37 using xwalk::application::ApplicationData;
38 using xwalk::application::ApplicationStorage;
39 using xwalk::application::FileDeleter;
40 using xwalk::application::Manifest;
41 using xwalk::application::Package;
45 const base::FilePath::CharType kApplicationsDir[] =
46 FILE_PATH_LITERAL("applications");
48 const base::FilePath::CharType kInstallTempDir[] =
49 FILE_PATH_LITERAL("install_temp");
51 const base::FilePath::CharType kUpdateTempDir[] =
52 FILE_PATH_LITERAL("update_temp");
54 namespace widget_keys = xwalk::application_widget_keys;
56 const base::FilePath kPkgHelper("/usr/bin/xwalk-pkg-helper");
58 const base::FilePath kXWalkLauncherBinary("/usr/bin/xwalk-launcher");
60 const base::FilePath kDefaultIcon(
61 "/usr/share/icons/default/small/crosswalk.png");
63 const std::string kServicePrefix("xwalk-service.");
64 const std::string kAppIdPrefix("xwalk.");
66 bool CopyDirectoryContents(const base::FilePath& from,
67 const base::FilePath& to) {
68 base::FileEnumerator iter(from, false,
69 base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES);
70 for (base::FilePath path = iter.Next(); !path.empty(); path = iter.Next()) {
71 if (iter.GetInfo().IsDirectory()) {
72 if (!base::CopyDirectory(path, to, true))
74 } else if (!base::CopyFile(path, to.Append(path.BaseName()))) {
82 void WriteMetaDataElement(
83 XmlWriter& writer, // NOLINT
84 xwalk::application::TizenMetaDataInfo* info) {
88 const std::map<std::string, std::string>& metadata = info->metadata();
89 std::map<std::string, std::string>::const_iterator it;
90 for (it = metadata.begin(); it != metadata.end(); ++it) {
91 writer.StartElement("metadata");
92 writer.AddAttribute("key", it->first);
93 writer.AddAttribute("value", it->second);
98 bool GeneratePkgInfoXml(xwalk::application::ApplicationData* application,
99 const std::string& icon_name,
100 const base::FilePath& app_dir,
101 const base::FilePath& xml_path) {
102 if (!base::PathExists(app_dir) &&
103 !base::CreateDirectory(app_dir))
106 std::string package_id =
107 xwalk::application::AppIdToPkgId(application->ID());
109 base::FilePath execute_path =
110 app_dir.AppendASCII("bin/").AppendASCII(application->ID());
111 std::string stripped_name = application->Name();
113 FILE* file = base::OpenFile(xml_path, "w");
115 XmlWriter xml_writer;
116 xml_writer.StartWriting();
117 xml_writer.StartElement("manifest");
118 xml_writer.AddAttribute("xmlns", "http://tizen.org/ns/packages");
119 xml_writer.AddAttribute("package", package_id);
120 xml_writer.AddAttribute("type", "wgt");
121 xml_writer.AddAttribute("version", application->VersionString());
122 xml_writer.WriteElement("label", application->Name());
123 xml_writer.WriteElement("description", application->Description());
125 xml_writer.StartElement("ui-application");
126 xml_writer.AddAttribute("appid", application->ID());
127 xml_writer.AddAttribute("exec", execute_path.MaybeAsASCII());
128 xml_writer.AddAttribute("type", "webapp");
129 xml_writer.AddAttribute("taskmanage", "true");
130 xml_writer.WriteElement("label", application->Name());
132 xwalk::application::TizenMetaDataInfo* info =
133 static_cast<xwalk::application::TizenMetaDataInfo*>(
134 application->GetManifestData(widget_keys::kTizenMetaDataKey));
135 WriteMetaDataElement(xml_writer, info);
137 if (icon_name.empty())
138 xml_writer.WriteElement("icon", info::kDefaultIconName);
140 xml_writer.WriteElement("icon",
141 kServicePrefix + application->ID() + ".png");
142 xml_writer.EndElement(); // Ends "ui-application"
144 xml_writer.EndElement(); // Ends "manifest" element.
145 xml_writer.StopWriting();
147 base::WriteFile(xml_path,
148 xml_writer.GetWrittenString().c_str(),
149 xml_writer.GetWrittenString().size());
151 base::CloseFile(file);
152 LOG(INFO) << "Converting manifest.json into "
153 << xml_path.BaseName().MaybeAsASCII()
154 << " for installation. [DONE]";
158 bool CreateAppSymbolicLink(const base::FilePath& app_dir,
159 const std::string& app_id) {
160 base::FilePath execute_path =
161 app_dir.AppendASCII("bin/").AppendASCII(app_id);
163 if (!base::CreateDirectory(execute_path.DirName())) {
164 LOG(ERROR) << "Could not create directory '"
165 << execute_path.DirName().value() << "'.";
169 if (!base::CreateSymbolicLink(kXWalkLauncherBinary, execute_path)) {
170 LOG(ERROR) << "Could not create symbolic link to launcher from '"
171 << execute_path.value() << "'.";
179 PackageInstaller::PackageInstaller(ApplicationStorage* storage)
184 PackageInstaller::~PackageInstaller() {
187 scoped_ptr<PackageInstaller> PackageInstaller::Create(
188 ApplicationStorage* storage) {
189 return scoped_ptr<PackageInstaller>(new PackageInstaller(storage));
192 void PackageInstaller::SetQuiet(bool quiet) {
196 void PackageInstaller::SetInstallationKey(const std::string& key) {
200 std::string PackageInstaller::PrepareUninstallationID(
201 const std::string& id) {
202 // this function fix pkg_id to app_id
203 // if installer was launched with pkg_id
204 if (xwalk::application::IsValidPkgID(id)) {
205 LOG(INFO) << "The package id is given " << id << " Will find app_id...";
206 std::string appid = xwalk::application::PkgIdToAppId(id);
213 bool PackageInstaller::PlatformInstall(ApplicationData* app_data) {
214 std::string app_id(app_data->ID());
215 base::FilePath data_dir;
216 CHECK(PathService::Get(xwalk::DIR_DATA_PATH, &data_dir));
218 base::FilePath app_dir =
219 data_dir.AppendASCII(info::kAppDir).AppendASCII(app_id);
220 base::FilePath xml_path = data_dir.AppendASCII(info::kAppDir).AppendASCII(
221 app_id + std::string(info::kXmlExtension));
223 std::string icon_name;
224 if (!app_data->GetManifest()->GetString(
225 GetIcon128Key(app_data->manifest_type()), &icon_name))
226 LOG(WARNING) << "'icon' not included in manifest";
228 // This will clean everything inside '<data dir>/<app id>'.
229 FileDeleter app_dir_cleaner(app_dir, true);
231 if (!GeneratePkgInfoXml(app_data, icon_name, app_dir, xml_path)) {
232 LOG(ERROR) << "Failed to create XML metadata file '"
233 << xml_path.value() << "'.";
237 if (!CreateAppSymbolicLink(app_dir, app_id)) {
238 LOG(ERROR) << "Failed to create symbolic link for " << app_id;
242 base::FilePath icon =
243 icon_name.empty() ? kDefaultIcon : app_dir.AppendASCII(icon_name);
245 CommandLine cmdline(kPkgHelper);
246 cmdline.AppendSwitchASCII("--install", app_id);
247 cmdline.AppendSwitchPath("--xml", xml_path);
248 cmdline.AppendSwitchPath("--icon", icon);
250 cmdline.AppendSwitch("-q");
252 cmdline.AppendSwitchASCII("--key", key_);
258 if (!base::GetAppOutputWithExitCode(cmdline, &output, &exit_code)) {
259 LOG(ERROR) << "Could not launch the installation helper process.";
263 if (exit_code != 0) {
264 LOG(ERROR) << "Could not install application: "
265 << output << " (" << exit_code << ")";
269 app_dir_cleaner.Dismiss();
274 bool PackageInstaller::PlatformUninstall(ApplicationData* app_data) {
276 std::string app_id(app_data->ID());
277 base::FilePath data_dir;
278 CHECK(PathService::Get(xwalk::DIR_DATA_PATH, &data_dir));
280 CommandLine cmdline(kPkgHelper);
281 cmdline.AppendSwitchASCII("--uninstall", app_id);
283 cmdline.AppendSwitch("-q");
285 cmdline.AppendSwitchASCII("--key", key_);
291 if (!base::GetAppOutputWithExitCode(cmdline, &output, &exit_code)) {
292 LOG(ERROR) << "Could not launch installer helper";
296 if (exit_code != 0) {
297 LOG(ERROR) << "Could not uninstall application: "
298 << output << " (" << exit_code << ")";
302 base::FilePath app_dir =
303 data_dir.AppendASCII(info::kAppDir).AppendASCII(app_id);
304 if (!base::DeleteFile(app_dir, true)) {
305 LOG(ERROR) << "Could not remove directory '" << app_dir.value() << "'";
309 base::FilePath xml_path = data_dir.AppendASCII(
310 app_id + std::string(info::kXmlExtension));
311 if (!base::DeleteFile(xml_path, false)) {
312 LOG(ERROR) << "Could not remove file '" << xml_path.value() << "'";
319 bool PackageInstaller::PlatformUpdate(ApplicationData* app_data) {
320 std::string app_id(app_data->ID());
321 base::FilePath data_dir;
322 CHECK(PathService::Get(xwalk::DIR_DATA_PATH, &data_dir));
324 base::FilePath app_dir =
325 data_dir.AppendASCII(info::kAppDir).AppendASCII(app_id);
326 base::FilePath new_xml_path = data_dir.AppendASCII(info::kAppDir).AppendASCII(
327 app_id + ".new" + std::string(info::kXmlExtension));
329 std::string icon_name;
330 if (!app_data->GetManifest()->GetString(
331 GetIcon128Key(app_data->manifest_type()), &icon_name))
332 LOG(WARNING) << "'icon' not included in manifest";
334 // This will clean everything inside '<data dir>/<app id>' and the new XML.
335 FileDeleter app_dir_cleaner(app_dir, true);
336 FileDeleter new_xml_cleaner(new_xml_path, true);
338 if (!GeneratePkgInfoXml(app_data, icon_name, app_dir, new_xml_path)) {
339 LOG(ERROR) << "Could not create new XML metadata file '"
340 << new_xml_path.value() << "'.";
344 if (!CreateAppSymbolicLink(app_dir, app_id))
347 base::FilePath icon =
348 icon_name.empty() ? kDefaultIcon : app_dir.AppendASCII(icon_name);
350 CommandLine cmdline(kPkgHelper);
351 cmdline.AppendSwitchASCII("--update", app_id);
352 cmdline.AppendSwitchPath("--xml", new_xml_path);
353 cmdline.AppendSwitchPath("--icon", icon);
355 cmdline.AppendSwitch("-q");
357 cmdline.AppendSwitchASCII("--key", key_);
363 if (!base::GetAppOutputWithExitCode(cmdline, &output, &exit_code)) {
364 LOG(ERROR) << "Could not launch installer helper";
368 if (exit_code != 0) {
369 LOG(ERROR) << "Could not update application: "
370 << output << " (" << exit_code << ")";
374 base::FilePath old_xml_path = data_dir.AppendASCII(info::kAppDir).AppendASCII(
375 app_id + std::string(info::kXmlExtension));
376 base::Move(new_xml_path, old_xml_path);
377 app_dir_cleaner.Dismiss();
381 bool PackageInstaller::PlatformReinstall(const base::FilePath& path) {
382 CommandLine cmdline(kPkgHelper);
383 cmdline.AppendSwitchPath("--reinstall", path);
385 cmdline.AppendSwitch("-q");
387 cmdline.AppendSwitchASCII("--key", key_);
393 if (!base::GetAppOutputWithExitCode(cmdline, &output, &exit_code)) {
394 LOG(ERROR) << "Could not launch installer helper";
398 if (exit_code != 0) {
399 LOG(ERROR) << "Could not reinstall application: "
400 << output << " (" << exit_code << ")";
407 bool PackageInstaller::Install(const base::FilePath& path, std::string* id) {
408 // FIXME(leandro): Installation is not robust enough -- should any step
409 // fail, it can't roll back to a consistent state.
410 if (!base::PathExists(path))
413 base::FilePath data_dir, install_temp_dir;
414 CHECK(PathService::Get(xwalk::DIR_DATA_PATH, &data_dir));
415 install_temp_dir = data_dir.Append(kInstallTempDir);
416 data_dir = data_dir.Append(kApplicationsDir);
418 // Make sure the kApplicationsDir exists under data_path, otherwise,
419 // the installation will always fail because of moving application
420 // resources into an invalid directory.
421 if (!base::DirectoryExists(data_dir) &&
422 !base::CreateDirectory(data_dir))
425 if (!base::DirectoryExists(install_temp_dir) &&
426 !base::CreateDirectory(install_temp_dir))
430 base::FilePath unpacked_dir;
431 scoped_ptr<Package> package;
432 FileDeleter tmp_path(install_temp_dir.Append(path.BaseName()), false);
433 if (!base::DirectoryExists(path)) {
434 if (tmp_path.path() != path &&
435 !base::CopyFile(path, tmp_path.path()))
437 package = Package::Create(tmp_path.path());
438 if (!package->IsValid())
440 package->ExtractToTemporaryDir(&unpacked_dir);
441 app_id = package->Id();
447 scoped_refptr<ApplicationData> app_data = LoadApplication(
448 unpacked_dir, app_id, ApplicationData::LOCAL_DIRECTORY,
449 package->manifest_type(), &error);
451 LOG(ERROR) << "Error during application installation: " << error;
455 // FIXME: Probably should be removed, as we should not handle permissions
457 xwalk::application::PermissionPolicyManager permission_policy_handler;
458 if (!permission_policy_handler.
459 InitApplicationPermission(app_data)) {
460 LOG(ERROR) << "Application permission data is invalid";
464 if (storage_->Contains(app_data->ID())) {
465 *id = app_data->ID();
466 LOG(INFO) << "Already installed: " << *id;
470 base::FilePath app_dir = data_dir.AppendASCII(app_data->ID());
471 if (base::DirectoryExists(app_dir)) {
472 if (!base::DeleteFile(app_dir, true))
476 if (!base::CreateDirectory(app_dir))
478 if (!CopyDirectoryContents(unpacked_dir, app_dir))
481 if (!base::Move(unpacked_dir, app_dir))
485 app_data->set_path(app_dir);
487 if (!storage_->AddApplication(app_data)) {
488 LOG(ERROR) << "Application with id " << app_data->ID()
489 << " couldn't be installed due to a Storage error";
490 base::DeleteFile(app_dir, true);
494 if (!PlatformInstall(app_data)) {
495 LOG(ERROR) << "Application with id " << app_data->ID()
496 << " couldn't be installed due to a platform error";
497 storage_->RemoveApplication(app_data->ID());
498 base::DeleteFile(app_dir, true);
502 LOG(INFO) << "Installed application with id: " << app_data->ID()
503 << "to" << app_dir.MaybeAsASCII() << " successfully.";
504 *id = app_data->ID();
509 bool PackageInstaller::Update(const std::string& app_id,
510 const base::FilePath& path) {
511 if (!xwalk::application::IsValidApplicationID(app_id)) {
512 LOG(ERROR) << "The given application id " << app_id << " is invalid.";
516 if (!base::PathExists(path)) {
517 LOG(ERROR) << "The XPK/WGT package file " << path.value() << " is invalid.";
521 if (base::DirectoryExists(path)) {
522 LOG(WARNING) << "Cannot update an unpacked XPK/WGT package.";
526 base::FilePath unpacked_dir, update_temp_dir;
527 CHECK(PathService::Get(xwalk::DIR_DATA_PATH, &update_temp_dir));
528 update_temp_dir = update_temp_dir.Append(kUpdateTempDir);
529 if (!base::DirectoryExists(update_temp_dir) &&
530 !base::CreateDirectory(update_temp_dir))
533 FileDeleter tmp_path(update_temp_dir.Append(path.BaseName()), false);
534 if (tmp_path.path() != path &&
535 !base::CopyFile(path, tmp_path.path()))
538 scoped_ptr<Package> package = Package::Create(tmp_path.path());
540 LOG(ERROR) << "XPK/WGT file is invalid.";
544 if (app_id.compare(package->Id()) != 0) {
545 LOG(ERROR) << "The XPK/WGT file is invalid, the application id is not the"
546 << "same as the installed application has.";
550 if (!package->ExtractToTemporaryDir(&unpacked_dir))
554 scoped_refptr<ApplicationData> new_app_data =
555 LoadApplication(unpacked_dir, app_id, ApplicationData::TEMP_DIRECTORY,
556 package->manifest_type(), &error);
558 LOG(ERROR) << "An error occurred during application updating: " << error;
562 scoped_refptr<ApplicationData> old_app_data =
563 storage_->GetApplicationData(app_id);
565 LOG(INFO) << "Application haven't installed yet: " << app_id;
569 // For Tizen WGT package, downgrade to a lower version or reinstall
570 // is permitted when using Tizen WRT, Crosswalk runtime need to follow
571 // this behavior on Tizen platform.
572 if (package->manifest_type() != Manifest::TYPE_WIDGET &&
573 old_app_data->Version()->CompareTo(
574 *(new_app_data->Version())) >= 0) {
575 LOG(INFO) << "The version number of new XPK/WGT package "
576 "should be higher than "
577 << old_app_data->VersionString();
581 const base::FilePath& app_dir = old_app_data->path();
582 const base::FilePath tmp_dir(app_dir.value()
583 + FILE_PATH_LITERAL(".tmp"));
585 if (!base::Move(app_dir, tmp_dir) ||
586 !base::Move(unpacked_dir, app_dir))
589 new_app_data = LoadApplication(
590 app_dir, app_id, ApplicationData::LOCAL_DIRECTORY,
591 package->manifest_type(), &error);
593 LOG(ERROR) << "Error during loading new package: " << error;
594 base::DeleteFile(app_dir, true);
595 base::Move(tmp_dir, app_dir);
599 if (!storage_->UpdateApplication(new_app_data)) {
600 LOG(ERROR) << "Fail to update application, roll back to the old one.";
601 base::DeleteFile(app_dir, true);
602 base::Move(tmp_dir, app_dir);
606 if (!PlatformUpdate(new_app_data)) {
607 LOG(ERROR) << "Fail to update application, roll back to the old one.";
608 base::DeleteFile(app_dir, true);
609 if (!storage_->UpdateApplication(old_app_data)) {
610 LOG(ERROR) << "Fail to revert old application info, "
611 << "remove the application as a last resort.";
612 storage_->RemoveApplication(old_app_data->ID());
613 base::DeleteFile(tmp_dir, true);
616 base::Move(tmp_dir, app_dir);
620 base::DeleteFile(tmp_dir, true);
625 bool PackageInstaller::Uninstall(const std::string& id) {
626 std::string app_id = PrepareUninstallationID(id);
628 if (!xwalk::application::IsValidApplicationID(app_id)) {
629 LOG(ERROR) << "The given application id " << app_id << " is invalid.";
634 scoped_refptr<ApplicationData> app_data =
635 storage_->GetApplicationData(app_id);
637 LOG(ERROR) << "Failed to find application with id " << app_id
638 << " among the installed ones.";
642 if (!storage_->RemoveApplication(app_id)) {
643 LOG(ERROR) << "Cannot uninstall application with id " << app_id
644 << "; application is not installed.";
648 base::FilePath resources;
649 CHECK(PathService::Get(xwalk::DIR_DATA_PATH, &resources));
650 resources = resources.Append(kApplicationsDir).AppendASCII(app_id);
651 if (base::DirectoryExists(resources) &&
652 !base::DeleteFile(resources, true)) {
653 LOG(ERROR) << "Error occurred while trying to remove application with id "
654 << app_id << "; Cannot remove all resources.";
658 if (!PlatformUninstall(app_data))
664 bool PackageInstaller::Reinstall(const base::FilePath& path) {
665 return PlatformReinstall(path);
668 void PackageInstaller::ContinueUnfinishedTasks() {
669 base::FilePath config_dir;
670 CHECK(PathService::Get(xwalk::DIR_DATA_PATH, &config_dir));
672 base::FilePath install_temp_dir = config_dir.Append(kInstallTempDir),
673 update_temp_dir = config_dir.Append(kUpdateTempDir);
674 FileDeleter install_cleaner(install_temp_dir, true),
675 update_cleaner(update_temp_dir, true);
677 if (base::DirectoryExists(install_temp_dir)) {
678 base::FileEnumerator install_iter(
679 install_temp_dir, false, base::FileEnumerator::FILES);
680 for (base::FilePath file = install_iter.Next();
681 !file.empty(); file = install_iter.Next()) {
683 Install(file, &app_id);
687 if (base::DirectoryExists(update_temp_dir)) {
688 base::FileEnumerator update_iter(
689 update_temp_dir, false, base::FileEnumerator::FILES);
690 for (base::FilePath file = update_iter.Next();
691 !file.empty(); file = update_iter.Next()) {
693 if (!Install(file, &app_id) && storage_->Contains(app_id)) {
694 LOG(INFO) << "trying to update %s" << app_id;
695 Update(app_id, file);