tizen 2.4 release
[framework/web/wrt-installer.git] / src / jobs / widget_uninstall / job_widget_uninstall.cpp
1 /*
2  * Copyright (c) 2011 Samsung Electronics Co., Ltd All Rights Reserved
3  *
4  *    Licensed under the Apache License, Version 2.0 (the "License");
5  *    you may not use this file except in compliance with the License.
6  *    You may obtain a copy of the License at
7  *
8  *        http://www.apache.org/licenses/LICENSE-2.0
9  *
10  *    Unless required by applicable law or agreed to in writing, software
11  *    distributed under the License is distributed on an "AS IS" BASIS,
12  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  *    See the License for the specific language governing permissions and
14  *    limitations under the License.
15  */
16
17 #include <regex.h>
18 #include <sys/stat.h>
19 #include <dpl/platform.h>
20 #include <widget_uninstall/job_widget_uninstall.h>
21 #include <widget_uninstall/widget_uninstall_errors.h>
22 #include <widget_uninstall/task_check.h>
23 #include <widget_uninstall/task_db_update.h>
24 #if ENABLE(PRE_LAUNCH)
25 #include <widget_uninstall/task_delete_prelaunching_info.h>
26 #endif
27 #include <widget_uninstall/task_remove_files.h>
28 #include <widget_uninstall/task_remove_custom_handlers.h>
29 #include <widget_uninstall/task_smack.h>
30 #include <widget_uninstall/task_uninstall_ospsvc.h>
31 #include <widget_uninstall/task_delete_pkginfo.h>
32 #include <dpl/wrt-dao-ro/global_config.h>
33 #include <pkg-manager/pkgmgr_signal.h>
34 #include <app2ext_interface.h>
35 #include <boost/filesystem.hpp>
36 #include <dpl/log/secure_log.h>
37
38 using namespace WrtDB;
39 namespace bf = boost::filesystem;
40
41 namespace { //anonymous
42 const char* REG_TIZEN_PKGID_PATTERN = "^[a-zA-Z0-9]{10}$";
43 const int PKGID_LENTH = 10;
44 const bf::path PRELOAD_INSTALLED_PATH("/usr/apps");
45 }
46
47 namespace Jobs {
48 namespace WidgetUninstall {
49
50 class UninstallerTaskFail :
51     public DPL::TaskDecl<UninstallerTaskFail>
52 {
53   private:
54     WidgetStatus m_status;
55
56     void StepFail()
57     {
58         if (WidgetStatus::NOT_INSTALLED == m_status) {
59             ThrowMsg(Jobs::WidgetUninstall::Exceptions::WidgetNotExist,
60                      "Widget does not exist");
61         } else if (WidgetStatus::PREALOAD == m_status) {
62             ThrowMsg(Jobs::WidgetUninstall::Exceptions::Unremovable,
63                      "Widget cann't uninstall");
64         } else {
65             Throw(Jobs::WidgetUninstall::Exceptions::Base);
66         }
67     }
68
69   public:
70     UninstallerTaskFail(WidgetStatus status) :
71         DPL::TaskDecl<UninstallerTaskFail>(this),
72         m_status(status)
73
74     {
75         AddStep(&UninstallerTaskFail::StepFail);
76     }
77 };
78
79 JobWidgetUninstall::JobWidgetUninstall(
80     const std::string & tizenPkgId,
81     const WidgetUninstallationStruct &
82     uninstallerStruct) :
83     Job(Uninstallation),
84     JobContextBase<WidgetUninstallationStruct>(uninstallerStruct),
85     m_exceptionCaught(Jobs::Exceptions::Success),
86     m_id(tizenPkgId)
87 {
88     using namespace PackageManager;
89     m_context.removeStarted = false;
90     m_context.removeFinished = false;
91     m_context.removeAbnormal = false;
92     m_context.job = this;
93
94     Try
95     {
96         WidgetStatus status = getWidgetStatus(tizenPkgId);
97
98         if (WidgetStatus::Ok == status) {
99             WrtDB::WidgetDAOReadOnly dao(*m_context.tzAppIdList.begin());
100             m_context.tzPkgid = DPL::ToUTF8String(dao.getTizenPkgId());
101             m_context.locations = WidgetLocation(m_context.tzPkgid);
102             m_context.locations->registerAppid(DPL::ToUTF8String(*m_context.tzAppIdList.begin()));
103             m_context.installedPath =
104                 bf::path(*dao.getWidgetInstalledPath());
105             m_context.manifestFile = getManifestFile();
106             PackagingType packagingType = dao.getPackagingType();
107
108             _D("Widget model exists. Pkg id : %s", m_context.tzPkgid.c_str());
109
110             // send start signal of pkgmgr
111             if (GetInstallerStruct().pkgmgrInterface->setPkgname(m_context.tzPkgid))
112             {
113                 GetAppIdsFromPackage(m_context.tzPkgid.c_str(), appIdsList);
114
115                 GetInstallerStruct().pkgmgrInterface->startJob(InstallationType::Uninstallation);
116             }
117 #if ENABLE(PRE_LAUNCH)
118             // Fundamentally, the pre-launching mechanism is based on
119             // re-launching target webapp on every termination.
120             // So the un-registration of pre-launching flag step
121             // should be processed firstly than step of killing those webapp.
122             AddTask(new TaskDeletePreLaunchingInfo(m_context));
123 #endif
124             AddTask(new TaskCheck(m_context));
125             if (packagingType == PKG_TYPE_HYBRID_WEB_APP) {
126                 AddTask(new TaskUninstallOspsvc(m_context));
127             }
128             AddTask(new TaskDeletePkgInfo(m_context));
129             AddTask(new TaskDbUpdate(m_context));
130
131             AddTask(new TaskRemoveCustomHandlers(m_context));
132             AddTask(new TaskRemoveFiles(m_context));
133             AddTask(new TaskSmack(m_context));
134         } else if (WidgetStatus::NOT_INSTALLED == status ||
135                 WidgetStatus::PREALOAD == status) {
136             AddTask(new UninstallerTaskFail(status));
137         } else if (WidgetStatus::ABNORMAL == status) {
138             m_context.tzPkgid = tizenPkgId;
139             m_context.manifestFile = getManifestFile();
140
141             m_context.locations = WidgetLocation(m_context.tzPkgid);
142             m_context.removeAbnormal = true;
143
144             AddTask(new TaskRemoveFiles(m_context));
145             AddTask(new TaskDeletePkgInfo(m_context));
146         } else {
147             AddTask(new UninstallerTaskFail(WidgetStatus::UNRECOGNIZED));
148         }
149     } Catch(WidgetDAOReadOnly::Exception::Base) {
150         AddTask(new UninstallerTaskFail(WidgetStatus::UNRECOGNIZED));
151     }
152 }
153
154 // regexec() function does not work properly in specific locale (ex, Estonian)
155 // So, change locale temporally before call regcomp and regexec
156 class ScopeLocale {
157 public:
158     ScopeLocale(){
159         currentLocale = setlocale(LC_ALL , NULL);
160         if (NULL == setlocale(LC_ALL, "C")) {
161             _W("Failed to change locale to \"C\"");
162         }
163     }
164
165     ~ScopeLocale() {
166         if (NULL == setlocale(LC_ALL, currentLocale.c_str())) {
167             _W("Failed to set previous locale");
168         }
169     }
170
171 private:
172     std::string currentLocale;
173 };
174
175 WidgetStatus JobWidgetUninstall::getWidgetStatus(const std::string &id)
176 {
177     ScopeLocale locale;
178
179     regex_t regx;
180     if(regcomp(&regx, REG_TIZEN_PKGID_PATTERN, REG_NOSUB | REG_EXTENDED)!=0){
181         _D("Regcomp failed");
182     }
183     std::string pkgId = id;
184
185     if ((regexec(&regx, id.c_str(),
186             static_cast<size_t>(0), NULL, 0) != REG_NOERROR)) {
187         //appid was passed
188         pkgId = id.substr(0, PKGID_LENTH);
189
190         //Service app cannot uninstall by appid
191         WrtDB::WidgetDAOReadOnly dao(DPL::FromUTF8String(id));
192         if (dao.getWidgetType().appType == APP_TYPE_TIZENWEBSERVICE) {
193             _E("Service app cannot uninstall by appid");
194             return WidgetStatus::NOT_INSTALLED;
195         }
196     }
197
198     m_context.tzAppIdList = WrtDB::WidgetDAOReadOnly::getTzAppIdList(DPL::FromUTF8String(pkgId));
199     if (m_context.tzAppIdList.empty()) {
200         try {
201             std::string installPath = std::string(GlobalConfig::GetUserInstalledWidgetPath()) + "/" + pkgId;
202             if (bf::exists(bf::path(installPath))) {
203                 _E("installed widget status is abnormal");
204                 return WidgetStatus::ABNORMAL;
205             }
206         } catch (const bf::filesystem_error& ex) {
207             _E("boost::filesystem::error: %s", ex.what());
208         }
209         return WidgetStatus::NOT_INSTALLED;
210     }
211     return WidgetStatus::Ok;
212 }
213
214 std::string JobWidgetUninstall::getRemovedTizenId() const
215 {
216     return m_id;
217 }
218
219 bool JobWidgetUninstall::getRemoveStartedFlag() const
220 {
221     return m_context.removeStarted;
222 }
223
224 bool JobWidgetUninstall::getRemoveFinishedFlag() const
225 {
226     return m_context.removeFinished;
227 }
228
229 bf::path JobWidgetUninstall::getManifestFile() const
230 {
231     std::ostringstream manifest_name;
232     manifest_name << m_context.tzPkgid << ".xml";
233     bf::path manifestFile;
234
235     const bf::path PRELOAD_INSTALLED_PATH("/usr/apps/" + m_context.tzPkgid);
236     const bf::path USR_PACKAGES_PATH("/usr/share/packages");
237     const bf::path OPT_PACKAGES_PATH("/opt/share/packages");
238
239     if (PRELOAD_INSTALLED_PATH == m_context.installedPath) {
240         _D("This widget is preloaded.");
241         manifestFile = USR_PACKAGES_PATH;
242     } else {
243         manifestFile = OPT_PACKAGES_PATH;
244     }
245
246     manifestFile /= manifest_name.str();
247     _D("Manifest file : %s", manifestFile.c_str());
248
249     return manifestFile;
250 }
251
252 void JobWidgetUninstall::SendProgress()
253 {
254     using namespace PackageManager;
255     if (!getRemoveStartedFlag() ||
256         (getRemoveStartedFlag() && getRemoveFinishedFlag()))
257     {
258         if (NULL != GetInstallerStruct().progressCallback) {
259             // send progress signal of pkgmgr
260             GetInstallerStruct().pkgmgrInterface->sendProgressUninstall(GetProgressPercent());
261
262             _D("Call widget uninstall progressCallback");
263             GetInstallerStruct().progressCallback(
264                 GetInstallerStruct().userParam,
265                 GetProgressPercent(), GetProgressDescription());
266         }
267     }
268 }
269
270 void JobWidgetUninstall::GetAppIdsFromPackage(const char* package, std::vector<std::string>& appIdsList)
271 {
272     package_info_h package_info;
273     appIdsList.clear();
274
275     int ret = package_info_create(package, &package_info);
276     if (PACKAGE_MANAGER_ERROR_NONE != ret) {
277         _E("Failed to create package info");
278         return;
279     }
280
281     ret = package_info_foreach_app_from_package(package_info,
282                                                 PACKAGE_INFO_ALLAPP,
283                                                 AppIdCallback,
284                                                 &appIdsList);
285     if (PACKAGE_MANAGER_ERROR_NONE != ret) {
286         _E("Failed to get application IDs");
287     }
288
289     ret = package_info_destroy(package_info);
290     if (PACKAGE_MANAGER_ERROR_NONE != ret) {
291         _E("Failed to destroy package info");
292     }
293
294     return;
295 }
296
297 bool JobWidgetUninstall::AppIdCallback(package_info_app_component_type_e /*comp_type*/,
298                                        const char *app_id, void *user_data)
299 {
300     if (app_id == NULL) {
301         _E("app_id is NULL");
302         return false;
303     }
304
305     if (user_data == NULL) {
306         _E("user data is NULL");
307         return false;
308     }
309
310     std::vector<std::string>* appList = static_cast<std::vector<std::string>*>(user_data);
311     appList->push_back(app_id);
312
313     return true;
314 }
315
316 void JobWidgetUninstall::SendFinishedSuccess()
317 {
318     using namespace PackageManager;
319
320     GetInstallerStruct().pkgmgrInterface->preEndJob(appIdsList);
321
322     // send signal of pkgmgr
323     GetInstallerStruct().pkgmgrInterface->endJob(m_exceptionCaught);
324
325     _D("Call widget uninstall success finishedCallback");
326     GetInstallerStruct().finishedCallback(GetInstallerStruct().userParam,
327                                           getRemovedTizenId(),
328                                           Jobs::Exceptions::Success);
329 }
330
331 void JobWidgetUninstall::SendFinishedFailure()
332 {
333     using namespace PackageManager;
334
335     LOGE("Error in uninstallation step: %d", m_exceptionCaught);
336     LOGE("Error message: %s", m_exceptionMessage.c_str());
337     fprintf(stderr, "[Err:%d] %s", m_exceptionCaught, m_exceptionMessage.c_str());
338
339     // send signal of pkgmgr
340     GetInstallerStruct().pkgmgrInterface->endJob(m_exceptionCaught, m_exceptionMessage.c_str());
341
342     _D("Call widget uninstall failure finishedCallback");
343     GetInstallerStruct().finishedCallback(GetInstallerStruct().userParam,
344                                           getRemovedTizenId(),
345                                           m_exceptionCaught);
346     _D("[JobWidgetUninstall] Asynchronous failure callback status sent");
347 }
348
349 void JobWidgetUninstall::SaveExceptionData(const Jobs::JobExceptionBase &e)
350 {
351     m_exceptionCaught = static_cast<Jobs::Exceptions::Type>(e.getParam());
352     m_exceptionMessage = e.GetMessage();
353 }
354
355 void JobWidgetUninstall::SecureRemove(const bf::path& path)
356 {
357     if (path.empty()) {
358         _E("error for get path");
359         ThrowMsg(Jobs::WidgetUninstall::Exceptions::RemoveFileFailure, "Try to remove insecure location");
360         return;
361     }
362
363     const char* blacklist[] = {
364         "/",
365         "/opt",
366         "/opt/",
367         "/opt/share",
368         "/opt/share/",
369         "/opt/share/widget",
370         "/opt/share/widget/",
371         "/opt/share/widget/temp_info",
372         "/opt/share/widget/temp_info/",
373         "/opt/share/packages",
374         "/opt/share/packages/",
375         "/opt/share/applications",
376         "/opt/share/applications/",
377         "/opt/usr",
378         "/opt/usr/",
379         "/opt/usr/apps",
380         "/opt/usr/apps/",
381         NULL
382     };
383
384     char* canonicalizeFilePath = canonicalize_file_name(path.c_str());
385     if (!canonicalizeFilePath) {
386         _E("error : a pathname[%s] component is unreadable or does not exist", path.c_str());
387         //Do NOT throw exception for defense code (remove directory again that removed before)
388         return;
389     }
390
391     int idx = 0;
392     while(blacklist[idx]){
393         char* cmpFilePath = canonicalize_file_name(blacklist[idx]);
394         if (cmpFilePath) {
395             if(!strcmp(canonicalizeFilePath, cmpFilePath)) {
396                 _E("illegal access : %s", path.c_str());
397                 free(cmpFilePath);
398                 free(canonicalizeFilePath);
399                 ThrowMsg(Jobs::WidgetUninstall::Exceptions::RemoveFileFailure, "Try to remove insecure location");
400                 return;
401             }
402             free(cmpFilePath);
403         }
404         idx++;
405     }
406     free(canonicalizeFilePath);
407     bf::remove_all(path);
408 }
409 } //namespace WidgetUninstall
410 } //namespace Jobs