tdf#163083: try to release lock to avoid deadlock
[LibreOffice.git] / desktop / source / app / updater.cxx
blobce0836b6511b2903878dff7f739a421f7df3b053
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 */
10 // Include this early, as it uses the identifier "Yield" which is defined as a macro in Windows
11 // system files indirectly included via some of the later includes here:
12 #include <vcl/svapp.hxx>
14 #include "updater.hxx"
16 #if UNX
17 #include <unistd.h>
18 #include <errno.h>
20 #endif
22 #ifdef _WIN32
23 #include <comphelper/windowsStart.hxx>
24 #endif
26 #include <fstream>
27 #include <config_folders.h>
28 #include <rtl/bootstrap.hxx>
30 #include <officecfg/Office/Update.hxx>
32 #include <rtl/ustring.hxx>
33 #include <unotools/tempfile.hxx>
34 #include <unotools/configmgr.hxx>
35 #include <o3tl/char16_t2wchar_t.hxx>
36 #include <o3tl/runtimetooustring.hxx>
37 #include <osl/file.hxx>
38 #include <osl/thread.h>
39 #include <rtl/process.h>
40 #include <sal/log.hxx>
41 #include <tools/stream.hxx>
43 #include <curl/curl.h>
45 #include <orcus/json_document_tree.hpp>
46 #include <orcus/config.hpp>
48 #include <systools/curlinit.hxx>
49 #include <comphelper/hash.hxx>
51 #include <com/sun/star/container/XNameAccess.hpp>
53 #include <officecfg/Setup.hxx>
55 #include <functional>
56 #include <memory>
57 #include <set>
58 #include <string_view>
60 namespace {
62 class error_updater : public std::exception
64 OString maStr;
65 public:
67 error_updater(const OString& rStr):
68 maStr(rStr)
72 virtual const char* what() const throw() override
74 return maStr.getStr();
78 #ifdef UNX
79 const char* const pUpdaterName = "updater";
80 const char* const pSofficeExeName = "soffice";
81 #elif defined(_WIN32)
82 const char* pUpdaterName = "updater.exe";
83 const char* pSofficeExeName = "soffice.exe";
84 #else
85 #error "Need implementation"
86 #endif
88 OUString normalizePath(const OUString& rPath)
90 OUString aPath = rPath;
91 #if defined WNT
92 aPath = aPath.replace('\\', '/');
93 #endif
95 aPath = aPath.replaceAll("//", "/");
97 // remove final /
98 if (aPath.endsWith("/"))
100 aPath = aPath.copy(0, aPath.getLength() - 1);
103 while (aPath.indexOf("/..") != -1)
105 sal_Int32 nIndex = aPath.indexOf("/..");
106 sal_Int32 i = nIndex - 1;
107 for (; i > 0; --i)
109 if (aPath[i] == '/')
110 break;
113 OUString aTempPath = aPath;
114 aPath = aTempPath.copy(0, i) + aPath.copy(nIndex + 3);
117 #if defined WNT
118 aPath = aPath.replace('/', '\\');
119 #endif
120 return aPath;
123 void CopyFileToDir(const OUString& rTempDirURL, const OUString & rFileName, const OUString& rOldDir)
125 OUString aSourceURL = rOldDir + "/" + rFileName;
126 OUString aDestURL = rTempDirURL + "/" + rFileName;
128 osl::File::RC eError = osl::File::copy(aSourceURL, aDestURL);
129 if (eError != osl::File::E_None)
131 SAL_WARN("desktop.updater", "could not copy the file to a temp directory: " << rFileName);
132 throw std::exception();
136 OUString getPathFromURL(const OUString& rURL)
138 OUString aPath;
139 osl::FileBase::getSystemPathFromFileURL(rURL, aPath);
141 return normalizePath(aPath);
144 void CopyUpdaterToTempDir(const OUString& rInstallDirURL, const OUString& rTempDirURL)
146 OUString aUpdaterName = OUString::fromUtf8(pUpdaterName);
147 CopyFileToDir(rTempDirURL, aUpdaterName, rInstallDirURL);
148 CopyFileToDir(rTempDirURL, u"updater.ini"_ustr, rInstallDirURL);
151 #ifdef UNX
152 typedef char CharT;
153 #define tstrncpy std::strncpy
154 char const * toStream(char const * s) { return s; }
155 #elif defined(_WIN32)
156 typedef wchar_t CharT;
157 #define tstrncpy std::wcsncpy
158 OUString toStream(wchar_t const * s) { return OUString(o3tl::toU(s)); }
159 #else
160 #error "Need an implementation"
161 #endif
163 void createStr(const OUString& rStr, CharT** pArgs, size_t i)
165 #ifdef UNX
166 OString aStr = OUStringToOString(rStr, RTL_TEXTENCODING_UTF8);
167 #elif defined(_WIN32)
168 OUString aStr = rStr;
169 #else
170 #error "Need an implementation"
171 #endif
172 CharT* pStr = new CharT[aStr.getLength() + 1];
173 tstrncpy(pStr, (CharT*)aStr.getStr(), aStr.getLength());
174 pStr[aStr.getLength()] = '\0';
175 pArgs[i] = pStr;
178 CharT** createCommandLine(OUString const & argv0)
180 OUString aInstallDir = Updater::getInstallationPath();
182 size_t nCommandLineArgs = rtl_getAppCommandArgCount();
183 size_t nArgs = 8 + nCommandLineArgs;
184 CharT** pArgs = new CharT*[nArgs];
185 createStr(argv0, pArgs, 0);
187 // directory with the patch log
188 OUString aPatchDir = Updater::getPatchDirURL();
189 rtl::Bootstrap::expandMacros(aPatchDir);
190 OUString aTempDirPath = getPathFromURL(aPatchDir);
191 Updater::log("Patch Dir: " + aTempDirPath);
192 createStr(aTempDirPath, pArgs, 1);
195 // the actual update directory
196 Updater::log("Install Dir: " + aInstallDir);
197 createStr(aInstallDir, pArgs, 2);
200 // the temporary updated build
201 Updater::log("Working Dir: " + aInstallDir);
202 createStr(aInstallDir, pArgs, 3);
205 #ifdef UNX
206 OUString aPID("0");
207 #elif defined(_WIN32)
208 oslProcessInfo aInfo;
209 aInfo.Size = sizeof(oslProcessInfo);
210 osl_getProcessInfo(nullptr, osl_Process_IDENTIFIER, &aInfo);
211 OUString aPID = OUString::number(aInfo.Ident);
212 #else
213 #error "Need an implementation"
214 #endif
215 createStr(aPID, pArgs, 4);
218 OUString aExeDir = Updater::getExecutableDirURL();
219 OUString aSofficePath = getPathFromURL(aExeDir);
220 Updater::log("soffice Path: " + aSofficePath);
221 createStr(aSofficePath, pArgs, 5);
224 // the executable to start after the successful update
225 OUString aExeDir = Updater::getExecutableDirURL();
226 OUString aSofficePathURL = aExeDir + OUString::fromUtf8(pSofficeExeName);
227 OUString aSofficePath = getPathFromURL(aSofficePathURL);
228 createStr(aSofficePath, pArgs, 6);
231 // add the command line arguments from the soffice list
232 for (size_t i = 0; i < nCommandLineArgs; ++i)
234 OUString aCommandLineArg;
235 rtl_getAppCommandArg(i, &aCommandLineArg.pData);
236 createStr(aCommandLineArg, pArgs, 7 + i);
239 pArgs[nArgs - 1] = nullptr;
241 return pArgs;
244 struct update_file
246 OUString aURL;
247 OUString aHash;
248 size_t nSize;
251 struct language_file
253 update_file aUpdateFile;
254 OUString aLangCode;
257 struct update_info
259 OUString aFromBuildID;
260 OUString aSeeAlsoURL;
261 OUString aMessage;
263 update_file aUpdateFile;
264 std::vector<language_file> aLanguageFiles;
267 bool isUserWritable(const OUString& rFileURL)
269 osl::FileStatus aStatus(osl_FileStatus_Mask_Attributes);
270 osl::DirectoryItem aDirectoryItem;
272 osl::FileBase::RC eRes = osl::DirectoryItem::get(rFileURL, aDirectoryItem);
273 if (eRes != osl::FileBase::E_None)
275 Updater::log("Could not get the directory item for: " + rFileURL);
276 return false;
279 osl::FileBase::RC eResult = aDirectoryItem.getFileStatus(aStatus);
280 if (eResult != osl::FileBase::E_None)
282 Updater::log("Could not get the file status for: " + rFileURL);
283 return false;
286 bool bReadOnly = (aStatus.getAttributes() & static_cast<sal_uInt64>(osl_File_Attribute_ReadOnly)) != 0;
287 if (bReadOnly)
289 Updater::log("Update location as determined by: " + rFileURL + " is read-only.");
290 return false;
293 return true;
298 bool update()
300 utl::TempFileNamed aTempDir(nullptr, true);
301 OUString aTempDirURL = aTempDir.GetURL();
302 CopyUpdaterToTempDir(Updater::getExecutableDirURL(), aTempDirURL);
304 OUString aUpdaterPath = getPathFromURL(aTempDirURL + "/" + OUString::fromUtf8(pUpdaterName));
306 Updater::log("Calling the updater with parameters: ");
307 CharT** pArgs = createCommandLine(aUpdaterPath);
309 bool bSuccess = true;
310 const char* pUpdaterTestReplace = std::getenv("LIBO_UPDATER_TEST_REPLACE");
311 if (!pUpdaterTestReplace)
313 #if UNX
314 OString aPath = OUStringToOString(aUpdaterPath, RTL_TEXTENCODING_UTF8);
315 if (execv(aPath.getStr(), pArgs))
317 printf("execv failed with error %d %s\n",errno,strerror(errno));
318 bSuccess = false;
320 #elif defined(_WIN32)
321 bSuccess = WinLaunchChild(o3tl::toW(aUpdaterPath.getStr()), pArgs);
322 #endif
324 else
326 SAL_WARN("desktop.updater", "Updater executable path: " << aUpdaterPath);
327 for (size_t i = 0; i < 8 + rtl_getAppCommandArgCount(); ++i)
329 SAL_WARN("desktop.updater", toStream(pArgs[i]));
331 bSuccess = false;
334 for (size_t i = 0; i < 8 + rtl_getAppCommandArgCount(); ++i)
336 delete[] pArgs[i];
338 delete[] pArgs;
340 return bSuccess;
343 namespace {
345 // Callback to get the response data from server.
346 size_t WriteCallback(void *ptr, size_t size,
347 size_t nmemb, void *userp)
349 if (!userp)
350 return 0;
352 std::string* response = static_cast<std::string *>(userp);
353 size_t real_size = size * nmemb;
354 response->append(static_cast<char *>(ptr), real_size);
355 return real_size;
360 class invalid_update_info : public std::exception
364 class invalid_hash : public std::exception
366 OString maMessage;
367 public:
369 invalid_hash(const OUString& rExpectedHash, const OUString& rReceivedHash)
370 : maMessage(
371 OUStringToOString(
372 OUString("Invalid hash found.\nExpected: " + rExpectedHash + ";\nReceived: " + rReceivedHash),
373 RTL_TEXTENCODING_UTF8)
378 const char* what() const noexcept override
380 return maMessage.getStr();
384 class invalid_size : public std::exception
386 OString maMessage;
387 public:
389 invalid_size(const size_t nExpectedSize, const size_t nReceivedSize)
390 : maMessage(
391 OUStringToOString(
392 OUString("Invalid file size found.\nExpected: " + OUString::number(nExpectedSize) + ";\nReceived: " + OUString::number(nReceivedSize)),
393 RTL_TEXTENCODING_UTF8)
398 const char* what() const noexcept override
400 return maMessage.getStr();
404 OUString toOUString(std::string_view str)
406 return OUString::fromUtf8(str);
409 update_file parse_update_file(orcus::json::node& rNode)
411 if (rNode.type() != orcus::json::node_t::object)
413 SAL_WARN("desktop.updater", "invalid update or language file entry");
414 throw invalid_update_info();
417 if (rNode.child_count() < 4)
419 SAL_WARN("desktop.updater", "invalid update or language file entry");
420 throw invalid_update_info();
423 orcus::json::node aURLNode = rNode.child("url");
424 orcus::json::node aHashNode = rNode.child("hash");
425 orcus::json::node aHashTypeNode = rNode.child("hash_function");
426 orcus::json::node aSizeNode = rNode.child("size");
428 if (aHashTypeNode.string_value() != "sha512")
430 SAL_WARN("desktop.updater", "invalid hash type");
431 throw invalid_update_info();
434 update_file aUpdateFile;
435 aUpdateFile.aURL = toOUString(aURLNode.string_value());
437 if (aUpdateFile.aURL.isEmpty())
438 throw invalid_update_info();
440 aUpdateFile.aHash = toOUString(aHashNode.string_value());
441 aUpdateFile.nSize = static_cast<sal_uInt32>(aSizeNode.numeric_value());
442 return aUpdateFile;
445 update_info parse_response(const std::string& rResponse)
447 orcus::json::document_tree aJsonDoc;
448 orcus::json_config aConfig;
449 aJsonDoc.load(rResponse, aConfig);
451 auto aDocumentRoot = aJsonDoc.get_document_root();
452 if (aDocumentRoot.type() != orcus::json::node_t::object)
454 SAL_WARN("desktop.updater", "invalid root entries: " << rResponse);
455 throw invalid_update_info();
458 auto aRootKeys = aDocumentRoot.keys();
459 if (std::find(aRootKeys.begin(), aRootKeys.end(), "error") != aRootKeys.end())
461 throw invalid_update_info();
463 else if (std::find(aRootKeys.begin(), aRootKeys.end(), "response") != aRootKeys.end())
465 update_info aUpdateInfo;
466 auto aMsgNode = aDocumentRoot.child("response");
467 aUpdateInfo.aMessage = toOUString(aMsgNode.string_value());
468 return aUpdateInfo;
471 orcus::json::node aFromNode = aDocumentRoot.child("from");
472 if (aFromNode.type() != orcus::json::node_t::string)
474 throw invalid_update_info();
477 orcus::json::node aSeeAlsoNode = aDocumentRoot.child("see also");
478 if (aSeeAlsoNode.type() != orcus::json::node_t::string)
480 throw invalid_update_info();
483 orcus::json::node aUpdateNode = aDocumentRoot.child("update");
484 if (aUpdateNode.type() != orcus::json::node_t::object)
486 throw invalid_update_info();
489 orcus::json::node aLanguageNode = aDocumentRoot.child("languages");
490 if (aLanguageNode.type() != orcus::json::node_t::object)
492 throw invalid_update_info();
495 update_info aUpdateInfo;
496 aUpdateInfo.aFromBuildID = toOUString(aFromNode.string_value());
497 aUpdateInfo.aSeeAlsoURL = toOUString(aSeeAlsoNode.string_value());
499 aUpdateInfo.aUpdateFile = parse_update_file(aUpdateNode);
501 std::vector<std::string_view> aLanguages = aLanguageNode.keys();
502 for (auto const& language : aLanguages)
504 language_file aLanguageFile;
505 auto aLangEntry = aLanguageNode.child(language);
506 aLanguageFile.aLangCode = toOUString(language);
507 aLanguageFile.aUpdateFile = parse_update_file(aLangEntry);
508 aUpdateInfo.aLanguageFiles.push_back(aLanguageFile);
511 return aUpdateInfo;
514 struct WriteDataFile
516 comphelper::Hash maHash;
517 SvStream* mpStream;
519 WriteDataFile(SvStream* pStream):
520 maHash(comphelper::HashType::SHA512),
521 mpStream(pStream)
525 OUString getHash()
527 auto final_hash = maHash.finalize();
528 std::stringstream aStrm;
529 for (auto& i: final_hash)
531 aStrm << std::setw(2) << std::setfill('0') << std::hex << (int)i;
534 return toOUString(aStrm.str());
538 // Callback to get the response data from server to a file.
539 size_t WriteCallbackFile(void *ptr, size_t size,
540 size_t nmemb, void *userp)
542 if (!userp)
543 return 0;
545 WriteDataFile* response = static_cast<WriteDataFile *>(userp);
546 size_t real_size = size * nmemb;
547 response->mpStream->WriteBytes(ptr, real_size);
548 response->maHash.update(static_cast<const unsigned char*>(ptr), real_size);
549 return real_size;
552 std::string download_content(const OString& rURL, bool bFile, OUString& rHash)
554 Updater::log("Download: " + OStringToOUString(rURL, osl_getThreadTextEncoding()));
555 std::unique_ptr<CURL, std::function<void(CURL *)>> curl(
556 curl_easy_init(), [](CURL * p) { curl_easy_cleanup(p); });
558 if (!curl)
559 return std::string();
561 static const OUString kUserAgent
562 = u"LibreOffice UpdateChecker/1.0 (os_version)"_ustr.replaceFirst(
563 "os_version", Application::GetOSVersion());
565 ::InitCurl_easy(curl.get());
567 curl_easy_setopt(curl.get(), CURLOPT_URL, rURL.getStr());
568 curl_easy_setopt(curl.get(), CURLOPT_USERAGENT, kUserAgent.toUtf8().getStr());
569 bool bUseProxy = false;
570 if (bUseProxy)
573 curl_easy_setopt(curl, CURLOPT_PROXY, proxy.c_str());
574 curl_easy_setopt(curl, CURLOPT_PROXYUSERPWD, proxy_user_pwd.c_str());
578 char buf[] = "Expect:";
579 curl_slist* headerlist = nullptr;
580 headerlist = curl_slist_append(headerlist, buf);
581 curl_easy_setopt(curl.get(), CURLOPT_HTTPHEADER, headerlist);
582 curl_easy_setopt(curl.get(), CURLOPT_FOLLOWLOCATION, 1); // follow redirects
583 // only allow redirect to https://
584 #if (LIBCURL_VERSION_MAJOR > 7) || (LIBCURL_VERSION_MAJOR == 7 && LIBCURL_VERSION_MINOR >= 85)
585 curl_easy_setopt(curl.get(), CURLOPT_REDIR_PROTOCOLS_STR, "https");
586 #else
587 curl_easy_setopt(curl.get(), CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS);
588 #endif
590 std::string response_body;
591 utl::TempFileNamed aTempFile;
592 WriteDataFile aFile(aTempFile.GetStream(StreamMode::WRITE));
593 if (!bFile)
595 curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, WriteCallback);
596 curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA,
597 static_cast<void *>(&response_body));
599 aTempFile.EnableKillingFile(true);
601 else
603 OUString aTempFileURL = aTempFile.GetURL();
604 OString aTempFileURLOString = OUStringToOString(aTempFileURL, RTL_TEXTENCODING_UTF8);
605 response_body.append(aTempFileURLOString.getStr(), aTempFileURLOString.getLength());
607 aTempFile.EnableKillingFile(false);
609 curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, WriteCallbackFile);
610 curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA,
611 static_cast<void *>(&aFile));
614 // Fail if 400+ is returned from the web server.
615 curl_easy_setopt(curl.get(), CURLOPT_FAILONERROR, 1);
617 CURLcode cc = curl_easy_perform(curl.get());
618 long http_code = 0;
619 curl_easy_getinfo(curl.get(), CURLINFO_RESPONSE_CODE, &http_code);
620 if (http_code != 200)
622 SAL_WARN("desktop.updater", "download did not succeed. Error code: " << http_code);
623 throw error_updater("download did not succeed");
626 if (cc != CURLE_OK)
628 SAL_WARN("desktop.updater", "curl error: " << cc);
629 throw error_updater("curl error");
632 if (bFile)
633 rHash = aFile.getHash();
635 return response_body;
638 void handle_file_error(osl::FileBase::RC eError, const OUString& rMsg)
640 switch (eError)
642 case osl::FileBase::E_None:
643 break;
644 default:
645 SAL_WARN("desktop.updater", "file error code: " << eError << ", " << rMsg);
646 throw error_updater(OUStringToOString(rMsg, RTL_TEXTENCODING_UTF8));
650 void download_file(const OUString& rURL, size_t nFileSize, const OUString& rHash, const OUString& aFileName)
652 Updater::log("Download File: " + rURL + "; FileName: " + aFileName);
653 OString aURL = OUStringToOString(rURL, RTL_TEXTENCODING_UTF8);
654 OUString aHash;
655 std::string temp_file = download_content(aURL, true, aHash);
656 if (temp_file.empty())
657 throw error_updater("empty temp file string");
659 OUString aTempFile = OUString::fromUtf8(temp_file.c_str());
660 Updater::log("TempFile: " + aTempFile);
661 osl::File aDownloadedFile(aTempFile);
662 osl::FileBase::RC eError = aDownloadedFile.open(1);
663 handle_file_error(eError, "Could not open the download file: " + aTempFile);
665 sal_uInt64 nSize = 0;
666 eError = aDownloadedFile.getSize(nSize);
667 handle_file_error(eError, "Could not get the file size of the downloaded file: " + aTempFile);
668 if (nSize != nFileSize)
670 SAL_WARN("desktop.updater", "File sizes don't match. File might be corrupted.");
671 throw invalid_size(nFileSize, nSize);
674 if (aHash != rHash)
676 SAL_WARN("desktop.updater", "File hash don't match. File might be corrupted.");
677 throw invalid_hash(rHash, aHash);
680 OUString aPatchDirURL("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("bootstrap") ":UserInstallation}/updates/");
681 rtl::Bootstrap::expandMacros(aPatchDirURL);
682 osl::Directory::create(aPatchDirURL);
683 aPatchDirURL += "0/";
684 osl::Directory::create(aPatchDirURL);
686 OUString aDestFile = aPatchDirURL + aFileName;
687 Updater::log("Destination File: " + aDestFile);
688 aDownloadedFile.close();
689 eError = osl::File::move(aTempFile, aDestFile);
690 handle_file_error(eError, "Could not move the file from the Temp directory to the user config: TempFile: " + aTempFile + "; DestFile: " + aDestFile);
695 void update_checker()
697 OUString aBrandBaseDir("${BRAND_BASE_DIR}");
698 rtl::Bootstrap::expandMacros(aBrandBaseDir);
699 bool bUserWritable = isUserWritable(aBrandBaseDir);
700 if (!bUserWritable)
702 Updater::log("Can't update as the update location is not user writable");
703 return;
706 OUString aDownloadCheckBaseURL = officecfg::Office::Update::Update::URL::get();
707 static const char* pDownloadCheckBaseURLEnv = std::getenv("LIBO_UPDATER_URL");
708 if (pDownloadCheckBaseURLEnv)
710 aDownloadCheckBaseURL = OUString::createFromAscii(pDownloadCheckBaseURLEnv);
713 OUString aProductName = utl::ConfigManager::getProductName();
714 OUString aBuildID = Updater::getBuildID();
716 static const char* pBuildIdEnv = std::getenv("LIBO_UPDATER_BUILD");
717 if (pBuildIdEnv)
719 aBuildID = OUString::createFromAscii(pBuildIdEnv);
722 OUString aBuildTarget = "${_OS}_${_ARCH}";
723 rtl::Bootstrap::expandMacros(aBuildTarget);
724 OUString aChannel = Updater::getUpdateChannel();
725 static const char* pUpdateChannelEnv = std::getenv("LIBO_UPDATER_CHANNEL");
726 if (pUpdateChannelEnv)
728 aChannel = OUString::createFromAscii(pUpdateChannelEnv);
731 OUString aDownloadCheckURL = aDownloadCheckBaseURL + "update/check/1/" + aProductName +
732 "/" + aBuildID + "/" + aBuildTarget + "/" + aChannel;
733 OString aURL = OUStringToOString(aDownloadCheckURL, RTL_TEXTENCODING_UTF8);
734 Updater::log("Update check: " + OStringToOUString(aURL, osl_getThreadTextEncoding()));
738 OUString aHash;
739 std::string response_body = download_content(aURL, false, aHash);
740 if (!response_body.empty())
743 update_info aUpdateInfo = parse_response(response_body);
744 if (aUpdateInfo.aUpdateFile.aURL.isEmpty())
746 // No update currently available
747 // add entry to updating.log with the message
748 SAL_WARN("desktop.updater", "Message received from the updater: " << aUpdateInfo.aMessage);
749 Updater::log("Server response: " + aUpdateInfo.aMessage);
751 else
753 css::uno::Sequence<OUString> aInstalledLanguages(officecfg::Setup::Office::InstalledLocales::get()->getElementNames());
754 std::set<OUString> aInstalledLanguageSet(std::begin(aInstalledLanguages), std::end(aInstalledLanguages));
755 download_file(aUpdateInfo.aUpdateFile.aURL, aUpdateInfo.aUpdateFile.nSize, aUpdateInfo.aUpdateFile.aHash, "update.mar");
756 for (auto& lang_update : aUpdateInfo.aLanguageFiles)
758 // only download the language packs for installed languages
759 if (aInstalledLanguageSet.find(lang_update.aLangCode) != aInstalledLanguageSet.end())
761 OUString aFileName = "update_" + lang_update.aLangCode + ".mar";
762 download_file(lang_update.aUpdateFile.aURL, lang_update.aUpdateFile.nSize, lang_update.aUpdateFile.aHash, aFileName);
765 OUString aSeeAlsoURL = aUpdateInfo.aSeeAlsoURL;
766 std::shared_ptr< comphelper::ConfigurationChanges > batch(
767 comphelper::ConfigurationChanges::create());
768 officecfg::Office::Update::Update::SeeAlso::set(aSeeAlsoURL, batch);
769 batch->commit();
770 OUString const statUrl = Updater::getPatchDirURL() + "update.status";
771 SvFileStream stat(statUrl, StreamMode::WRITE | StreamMode::TRUNC);
772 stat.WriteOString("pending-service");
773 stat.Flush();
774 if (auto const e = stat.GetError()) {
775 Updater::log("Writing <" + statUrl + "> failed with " + e.toString());
777 stat.Close();
781 catch (const invalid_update_info&)
783 SAL_WARN("desktop.updater", "invalid update information");
784 Updater::log("warning: invalid update info");
786 catch (const error_updater& e)
788 SAL_WARN("desktop.updater", "error during the update check: " << e.what());
789 Updater::log("warning: error by the updater" + o3tl::runtimeToOUString(e.what()));
791 catch (const invalid_size& e)
793 SAL_WARN("desktop.updater", e.what());
794 Updater::log("warning: invalid size");
796 catch (const invalid_hash& e)
798 SAL_WARN("desktop.updater", e.what());
799 Updater::log("warning: invalid hash");
801 catch (...)
803 SAL_WARN("desktop.updater", "unknown error during the update check");
804 Updater::log("warning: unknown exception");
808 OUString Updater::getPatchDirURL()
810 OUString aPatchDirURL("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("bootstrap") ":UserInstallation}/updates/0/");
811 rtl::Bootstrap::expandMacros(aPatchDirURL);
813 return aPatchDirURL;
816 OUString Updater::getUpdateFileURL()
818 return getPatchDirURL() + "update.mar";
821 OUString Updater::getInstallationPath()
823 OUString aInstallDir( "$BRAND_BASE_DIR/");
824 rtl::Bootstrap::expandMacros(aInstallDir);
826 return getPathFromURL(aInstallDir);
829 OUString Updater::getExecutableDirURL()
831 OUString aExeDir( "$BRAND_BASE_DIR/" LIBO_BIN_FOLDER "/" );
832 rtl::Bootstrap::expandMacros(aExeDir);
834 return aExeDir;
837 void Updater::log(const OUString& rMessage)
839 SAL_INFO("desktop.updater", rMessage);
840 OUString dir("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("bootstrap") ":UserInstallation}/updates");
841 rtl::Bootstrap::expandMacros(dir);
842 osl::Directory::create(dir);
843 SvFileStream aLog(dir + "/updating.log", StreamMode::STD_READWRITE);
844 aLog.Seek(aLog.Tell() + aLog.remainingSize()); // make sure we are at the end
845 aLog.WriteLine(OUStringToOString(rMessage, RTL_TEXTENCODING_UTF8));
848 OUString Updater::getBuildID()
850 OUString aBuildID("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("version") ":buildid}");
851 rtl::Bootstrap::expandMacros(aBuildID);
853 return aBuildID;
856 OUString Updater::getUpdateChannel()
858 OUString aUpdateChannel("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("version") ":UpdateChannel}");
859 rtl::Bootstrap::expandMacros(aUpdateChannel);
861 return aUpdateChannel;
864 void Updater::removeUpdateFiles()
866 Updater::log("Removing: " + getUpdateFileURL());
867 osl::File::remove(getUpdateFileURL());
869 OUString aPatchDirURL = getPatchDirURL();
870 osl::Directory aDir(aPatchDirURL);
871 aDir.open();
873 osl::FileBase::RC eRC;
876 osl::DirectoryItem aItem;
877 eRC = aDir.getNextItem(aItem);
878 if (eRC == osl::FileBase::E_None)
880 osl::FileStatus aStatus(osl_FileStatus_Mask_All);
881 if (aItem.getFileStatus(aStatus) != osl::FileBase::E_None)
882 continue;
884 if (!aStatus.isRegular())
885 continue;
887 OUString aURL = aStatus.getFileURL();
888 if (!aURL.endsWith(".mar"))
889 continue;
891 Updater::log("Removing. " + aURL);
892 osl::File::remove(aURL);
895 while (eRC == osl::FileBase::E_None);
898 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */