/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "updater.hxx" #if UNX #include #include #endif #ifdef _WIN32 #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { class error_updater : public std::exception { OString maStr; public: error_updater(const OString& rStr): maStr(rStr) { } virtual const char* what() const throw() override { return maStr.getStr(); } }; #ifdef UNX static const char kUserAgent[] = "LibreOffice UpdateChecker/1.0 (Linux)"; #else static const char kUserAgent[] = "LibreOffice UpdateChecker/1.0 (unknown platform)"; #endif #ifdef UNX const char* const pUpdaterName = "updater"; const char* const pSofficeExeName = "soffice"; #elif defined(_WIN32) const char* pUpdaterName = "updater.exe"; const char* pSofficeExeName = "soffice.exe"; #else #error "Need implementation" #endif OUString normalizePath(const OUString& rPath) { OUString aPath = rPath.replaceAll("//", "/"); // remove final / if (aPath.endsWith("/")) { aPath = aPath.copy(0, aPath.getLength() - 1); } while (aPath.indexOf("/..") != -1) { sal_Int32 nIndex = aPath.indexOf("/.."); sal_Int32 i = nIndex - 1; for (; i > 0; --i) { if (aPath[i] == '/') break; } OUString aTempPath = aPath; aPath = aTempPath.copy(0, i) + aPath.copy(nIndex + 3); } return aPath.replaceAll("\\", "/"); } void CopyFileToDir(const OUString& rTempDirURL, const OUString & rFileName, const OUString& rOldDir) { OUString aSourceURL = rOldDir + "/" + rFileName; OUString aDestURL = rTempDirURL + "/" + rFileName; osl::File::RC eError = osl::File::copy(aSourceURL, aDestURL); if (eError != osl::File::E_None) { SAL_WARN("desktop.updater", "could not copy the file to a temp directory: " << rFileName); throw std::exception(); } } OUString getPathFromURL(const OUString& rURL) { OUString aPath; osl::FileBase::getSystemPathFromFileURL(rURL, aPath); return normalizePath(aPath); } void CopyUpdaterToTempDir(const OUString& rInstallDirURL, const OUString& rTempDirURL) { OUString aUpdaterName = OUString::fromUtf8(pUpdaterName); CopyFileToDir(rTempDirURL, aUpdaterName, rInstallDirURL); } #ifdef UNX typedef char CharT; #define tstrncpy std::strncpy #elif defined(_WIN32) typedef wchar_t CharT; #define tstrncpy std::wcsncpy #else #error "Need an implementation" #endif void createStr(const OUString& rStr, CharT** pArgs, size_t i) { #ifdef UNX OString aStr = OUStringToOString(rStr, RTL_TEXTENCODING_UTF8); #elif defined(_WIN32) OUString aStr = rStr; #else #error "Need an implementation" #endif CharT* pStr = new CharT[aStr.getLength() + 1]; tstrncpy(pStr, (CharT*)aStr.getStr(), aStr.getLength()); pStr[aStr.getLength()] = '\0'; pArgs[i] = pStr; } CharT** createCommandLine() { OUString aInstallDir = Updater::getInstallationPath(); size_t nCommandLineArgs = rtl_getAppCommandArgCount(); size_t nArgs = 8 + nCommandLineArgs; CharT** pArgs = new CharT*[nArgs]; { OUString aUpdaterName = OUString::fromUtf8(pUpdaterName); createStr(aUpdaterName, pArgs, 0); } { // directory with the patch log OUString aPatchDir = Updater::getPatchDirURL(); rtl::Bootstrap::expandMacros(aPatchDir); OUString aTempDirPath = getPathFromURL(aPatchDir); Updater::log("Patch Dir: " + aTempDirPath); createStr(aTempDirPath, pArgs, 1); } { // the actual update directory Updater::log("Install Dir: " + aInstallDir); createStr(aInstallDir, pArgs, 2); } { // the temporary updated build Updater::log("Working Dir: " + aInstallDir); createStr(aInstallDir, pArgs, 3); } { #ifdef UNX OUString aPID("0"); #elif defined(_WIN32) oslProcessInfo aInfo; aInfo.Size = sizeof(oslProcessInfo); osl_getProcessInfo(nullptr, osl_Process_IDENTIFIER, &aInfo); OUString aPID = OUString::number(aInfo.Ident); #else #error "Need an implementation" #endif createStr(aPID, pArgs, 4); } { OUString aExeDir = Updater::getExecutableDirURL(); OUString aSofficePath = getPathFromURL(aExeDir); Updater::log("soffice Path: " + aSofficePath); createStr(aSofficePath, pArgs, 5); } { // the executable to start after the successful update OUString aExeDir = Updater::getExecutableDirURL(); OUString aSofficePathURL = aExeDir + OUString::fromUtf8(pSofficeExeName); OUString aSofficePath = getPathFromURL(aSofficePathURL); createStr(aSofficePath, pArgs, 6); } // add the command line arguments from the soffice list for (size_t i = 0; i < nCommandLineArgs; ++i) { OUString aCommandLineArg; rtl_getAppCommandArg(i, &aCommandLineArg.pData); createStr(aCommandLineArg, pArgs, 7 + i); } pArgs[nArgs - 1] = nullptr; return pArgs; } struct update_file { OUString aURL; OUString aHash; size_t nSize; }; struct language_file { update_file aUpdateFile; OUString aLangCode; }; struct update_info { OUString aFromBuildID; OUString aSeeAlsoURL; OUString aMessage; update_file aUpdateFile; std::vector aLanguageFiles; }; bool isUserWritable(const OUString& rFileURL) { osl::FileStatus aStatus(osl_FileStatus_Mask_Attributes); osl::DirectoryItem aDirectoryItem; osl::FileBase::RC eRes = osl::DirectoryItem::get(rFileURL, aDirectoryItem); if (eRes != osl::FileBase::E_None) { Updater::log("Could not get the directory item for: " + rFileURL); return false; } osl::FileBase::RC eResult = aDirectoryItem.getFileStatus(aStatus); if (eResult != osl::FileBase::E_None) { Updater::log("Could not get the file status for: " + rFileURL); return false; } bool bReadOnly = (aStatus.getAttributes() & static_cast(osl_File_Attribute_ReadOnly)) != 0; if (bReadOnly) { Updater::log("Update location as determined by: " + rFileURL + " is read-only."); return false; } return true; } } bool update() { utl::TempFileNamed aTempDir(nullptr, true); OUString aTempDirURL = aTempDir.GetURL(); CopyUpdaterToTempDir(Updater::getExecutableDirURL(), aTempDirURL); OUString aUpdaterPath = getPathFromURL(aTempDirURL + "/" + OUString::fromUtf8(pUpdaterName)); Updater::log("Calling the updater with parameters: "); CharT** pArgs = createCommandLine(); bool bSuccess = true; const char* pUpdaterTestReplace = std::getenv("LIBO_UPDATER_TEST_REPLACE"); if (!pUpdaterTestReplace) { #if UNX OString aPath = OUStringToOString(aUpdaterPath, RTL_TEXTENCODING_UTF8); if (execv(aPath.getStr(), pArgs)) { printf("execv failed with error %d %s\n",errno,strerror(errno)); bSuccess = false; } #elif defined(_WIN32) bSuccess = WinLaunchChild((wchar_t*)aUpdaterPath.getStr(), 8, pArgs); #endif } else { SAL_WARN("desktop.updater", "Updater executable path: " << aUpdaterPath); for (size_t i = 0; i < 8 + rtl_getAppCommandArgCount(); ++i) { SAL_WARN("desktop.updater", pArgs[i]); } bSuccess = false; } for (size_t i = 0; i < 8 + rtl_getAppCommandArgCount(); ++i) { delete[] pArgs[i]; } delete[] pArgs; return bSuccess; } namespace { // Callback to get the response data from server. size_t WriteCallback(void *ptr, size_t size, size_t nmemb, void *userp) { if (!userp) return 0; std::string* response = static_cast(userp); size_t real_size = size * nmemb; response->append(static_cast(ptr), real_size); return real_size; } class invalid_update_info : public std::exception { }; class invalid_hash : public std::exception { OString maMessage; public: invalid_hash(const OUString& rExpectedHash, const OUString& rReceivedHash) : maMessage( OUStringToOString( OUString("Invalid hash found.\nExpected: " + rExpectedHash + ";\nReceived: " + rReceivedHash), RTL_TEXTENCODING_UTF8) ) { } const char* what() const noexcept override { return maMessage.getStr(); } }; class invalid_size : public std::exception { OString maMessage; public: invalid_size(const size_t nExpectedSize, const size_t nReceivedSize) : maMessage( OUStringToOString( OUString("Invalid file size found.\nExpected: " + OUString::number(nExpectedSize) + ";\nReceived: " + OUString::number(nReceivedSize)), RTL_TEXTENCODING_UTF8) ) { } const char* what() const noexcept override { return maMessage.getStr(); } }; OUString toOUString(const std::string& rStr) { return OUString::fromUtf8(rStr.c_str()); } update_file parse_update_file(orcus::json::node& rNode) { if (rNode.type() != orcus::json::node_t::object) { SAL_WARN("desktop.updater", "invalid update or language file entry"); throw invalid_update_info(); } if (rNode.child_count() < 4) { SAL_WARN("desktop.updater", "invalid update or language file entry"); throw invalid_update_info(); } orcus::json::node aURLNode = rNode.child("url"); orcus::json::node aHashNode = rNode.child("hash"); orcus::json::node aHashTypeNode = rNode.child("hash_function"); orcus::json::node aSizeNode = rNode.child("size"); if (aHashTypeNode.string_value() != "sha512") { SAL_WARN("desktop.updater", "invalid hash type"); throw invalid_update_info(); } update_file aUpdateFile; aUpdateFile.aURL = toOUString(aURLNode.string_value().str()); if (aUpdateFile.aURL.isEmpty()) throw invalid_update_info(); aUpdateFile.aHash = toOUString(aHashNode.string_value().str()); aUpdateFile.nSize = static_cast(aSizeNode.numeric_value()); return aUpdateFile; } update_info parse_response(const std::string& rResponse) { orcus::json::document_tree aJsonDoc; orcus::json_config aConfig; aJsonDoc.load(rResponse, aConfig); auto aDocumentRoot = aJsonDoc.get_document_root(); if (aDocumentRoot.type() != orcus::json::node_t::object) { SAL_WARN("desktop.updater", "invalid root entries: " << rResponse); throw invalid_update_info(); } auto aRootKeys = aDocumentRoot.keys(); if (std::find(aRootKeys.begin(), aRootKeys.end(), "error") != aRootKeys.end()) { throw invalid_update_info(); } else if (std::find(aRootKeys.begin(), aRootKeys.end(), "response") != aRootKeys.end()) { update_info aUpdateInfo; auto aMsgNode = aDocumentRoot.child("response"); aUpdateInfo.aMessage = toOUString(aMsgNode.string_value().str()); return aUpdateInfo; } orcus::json::node aFromNode = aDocumentRoot.child("from"); if (aFromNode.type() != orcus::json::node_t::string) { throw invalid_update_info(); } orcus::json::node aSeeAlsoNode = aDocumentRoot.child("see also"); if (aSeeAlsoNode.type() != orcus::json::node_t::string) { throw invalid_update_info(); } orcus::json::node aUpdateNode = aDocumentRoot.child("update"); if (aUpdateNode.type() != orcus::json::node_t::object) { throw invalid_update_info(); } orcus::json::node aLanguageNode = aDocumentRoot.child("languages"); if (aUpdateNode.type() != orcus::json::node_t::object) { throw invalid_update_info(); } update_info aUpdateInfo; aUpdateInfo.aFromBuildID = toOUString(aFromNode.string_value().str()); aUpdateInfo.aSeeAlsoURL = toOUString(aSeeAlsoNode.string_value().str()); aUpdateInfo.aUpdateFile = parse_update_file(aUpdateNode); std::vector aLanguages = aLanguageNode.keys(); for (auto const& language : aLanguages) { language_file aLanguageFile; auto aLangEntry = aLanguageNode.child(language); aLanguageFile.aLangCode = toOUString(language.str()); aLanguageFile.aUpdateFile = parse_update_file(aLangEntry); aUpdateInfo.aLanguageFiles.push_back(aLanguageFile); } return aUpdateInfo; } struct WriteDataFile { comphelper::Hash maHash; SvStream* mpStream; WriteDataFile(SvStream* pStream): maHash(comphelper::HashType::SHA512), mpStream(pStream) { } OUString getHash() { auto final_hash = maHash.finalize(); std::stringstream aStrm; for (auto& i: final_hash) { aStrm << std::setw(2) << std::setfill('0') << std::hex << (int)i; } return toOUString(aStrm.str()); } }; // Callback to get the response data from server to a file. size_t WriteCallbackFile(void *ptr, size_t size, size_t nmemb, void *userp) { if (!userp) return 0; WriteDataFile* response = static_cast(userp); size_t real_size = size * nmemb; response->mpStream->WriteBytes(ptr, real_size); response->maHash.update(static_cast(ptr), real_size); return real_size; } std::string download_content(const OString& rURL, bool bFile, OUString& rHash) { Updater::log("Download: " + rURL); std::unique_ptr> curl( curl_easy_init(), [](CURL * p) { curl_easy_cleanup(p); }); if (!curl) return std::string(); curl_easy_setopt(curl.get(), CURLOPT_URL, rURL.getStr()); curl_easy_setopt(curl.get(), CURLOPT_USERAGENT, kUserAgent); bool bUseProxy = false; if (bUseProxy) { /* curl_easy_setopt(curl, CURLOPT_PROXY, proxy.c_str()); curl_easy_setopt(curl, CURLOPT_PROXYUSERPWD, proxy_user_pwd.c_str()); */ } char buf[] = "Expect:"; curl_slist* headerlist = nullptr; headerlist = curl_slist_append(headerlist, buf); curl_easy_setopt(curl.get(), CURLOPT_HTTPHEADER, headerlist); curl_easy_setopt(curl.get(), CURLOPT_FOLLOWLOCATION, 1); // follow redirects // only allow redirect to https:// #if (LIBCURL_VERSION_MAJOR > 7) || (LIBCURL_VERSION_MAJOR == 7 && LIBCURL_VERSION_MINOR >= 85) curl_easy_setopt(curl.get(), CURLOPT_REDIR_PROTOCOLS_STR, "https"); #else curl_easy_setopt(curl.get(), CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS); #endif std::string response_body; utl::TempFileNamed aTempFile; WriteDataFile aFile(aTempFile.GetStream(StreamMode::WRITE)); if (!bFile) { curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, WriteCallback); curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, static_cast(&response_body)); aTempFile.EnableKillingFile(true); } else { OUString aTempFileURL = aTempFile.GetURL(); OString aTempFileURLOString = OUStringToOString(aTempFileURL, RTL_TEXTENCODING_UTF8); response_body.append(aTempFileURLOString.getStr(), aTempFileURLOString.getLength()); aTempFile.EnableKillingFile(false); curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, WriteCallbackFile); curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, static_cast(&aFile)); } // Fail if 400+ is returned from the web server. curl_easy_setopt(curl.get(), CURLOPT_FAILONERROR, 1); CURLcode cc = curl_easy_perform(curl.get()); long http_code = 0; curl_easy_getinfo(curl.get(), CURLINFO_RESPONSE_CODE, &http_code); if (http_code != 200) { SAL_WARN("desktop.updater", "download did not succeed. Error code: " << http_code); throw error_updater("download did not succeed"); } if (cc != CURLE_OK) { SAL_WARN("desktop.updater", "curl error: " << cc); throw error_updater("curl error"); } if (bFile) rHash = aFile.getHash(); return response_body; } void handle_file_error(osl::FileBase::RC eError, const OUString& rMsg) { switch (eError) { case osl::FileBase::E_None: break; default: SAL_WARN("desktop.updater", "file error code: " << eError << ", " << rMsg); throw error_updater(OUStringToOString(rMsg, RTL_TEXTENCODING_UTF8)); } } void download_file(const OUString& rURL, size_t nFileSize, const OUString& rHash, const OUString& aFileName) { Updater::log("Download File: " + rURL + "; FileName: " + aFileName); OString aURL = OUStringToOString(rURL, RTL_TEXTENCODING_UTF8); OUString aHash; std::string temp_file = download_content(aURL, true, aHash); if (temp_file.empty()) throw error_updater("empty temp file string"); OUString aTempFile = OUString::fromUtf8(temp_file.c_str()); Updater::log("TempFile: " + aTempFile); osl::File aDownloadedFile(aTempFile); osl::FileBase::RC eError = aDownloadedFile.open(1); handle_file_error(eError, "Could not open the download file: " + aTempFile); sal_uInt64 nSize = 0; eError = aDownloadedFile.getSize(nSize); handle_file_error(eError, "Could not get the file size of the downloaded file: " + aTempFile); if (nSize != nFileSize) { SAL_WARN("desktop.updater", "File sizes don't match. File might be corrupted."); throw invalid_size(nFileSize, nSize); } if (aHash != rHash) { SAL_WARN("desktop.updater", "File hash don't match. File might be corrupted."); throw invalid_hash(rHash, aHash); } OUString aPatchDirURL("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("bootstrap") ":UserInstallation}/patch/"); rtl::Bootstrap::expandMacros(aPatchDirURL); osl::Directory::create(aPatchDirURL); OUString aDestFile = aPatchDirURL + aFileName; Updater::log("Destination File: " + aDestFile); aDownloadedFile.close(); eError = osl::File::move(aTempFile, aDestFile); handle_file_error(eError, "Could not move the file from the Temp directory to the user config: TempFile: " + aTempFile + "; DestFile: " + aDestFile); } } void update_checker() { OUString aBrandBaseDir("${BRAND_BASE_DIR}"); rtl::Bootstrap::expandMacros(aBrandBaseDir); bool bUserWritable = isUserWritable(aBrandBaseDir); if (!bUserWritable) { Updater::log("Can't update as the update location is not user writable"); return; } OUString aDownloadCheckBaseURL = officecfg::Office::Update::Update::URL::get(); static const char* pDownloadCheckBaseURLEnv = std::getenv("LIBO_UPDATER_URL"); if (pDownloadCheckBaseURLEnv) { aDownloadCheckBaseURL = OUString::createFromAscii(pDownloadCheckBaseURLEnv); } OUString aProductName = utl::ConfigManager::getProductName(); OUString aBuildID = Updater::getBuildID(); static const char* pBuildIdEnv = std::getenv("LIBO_UPDATER_BUILD"); if (pBuildIdEnv) { aBuildID = OUString::createFromAscii(pBuildIdEnv); } OUString aBuildTarget = "${_OS}_${_ARCH}"; rtl::Bootstrap::expandMacros(aBuildTarget); OUString aChannel = Updater::getUpdateChannel(); static const char* pUpdateChannelEnv = std::getenv("LIBO_UPDATER_CHANNEL"); if (pUpdateChannelEnv) { aChannel = OUString::createFromAscii(pUpdateChannelEnv); } OUString aDownloadCheckURL = aDownloadCheckBaseURL + "update/check/1/" + aProductName + "/" + aBuildID + "/" + aBuildTarget + "/" + aChannel; OString aURL = OUStringToOString(aDownloadCheckURL, RTL_TEXTENCODING_UTF8); Updater::log("Update check: " + aURL); try { OUString aHash; std::string response_body = download_content(aURL, false, aHash); if (!response_body.empty()) { update_info aUpdateInfo = parse_response(response_body); if (aUpdateInfo.aUpdateFile.aURL.isEmpty()) { // No update currently available // add entry to updating.log with the message SAL_WARN("desktop.updater", "Message received from the updater: " << aUpdateInfo.aMessage); Updater::log("Server response: " + aUpdateInfo.aMessage); } else { css::uno::Sequence aInstalledLanguages(officecfg::Setup::Office::InstalledLocales::get()->getElementNames()); std::set aInstalledLanguageSet(std::begin(aInstalledLanguages), std::end(aInstalledLanguages)); download_file(aUpdateInfo.aUpdateFile.aURL, aUpdateInfo.aUpdateFile.nSize, aUpdateInfo.aUpdateFile.aHash, "update.mar"); for (auto& lang_update : aUpdateInfo.aLanguageFiles) { // only download the language packs for installed languages if (aInstalledLanguageSet.find(lang_update.aLangCode) != aInstalledLanguageSet.end()) { OUString aFileName = "update_" + lang_update.aLangCode + ".mar"; download_file(lang_update.aUpdateFile.aURL, lang_update.aUpdateFile.nSize, lang_update.aUpdateFile.aHash, aFileName); } } OUString aSeeAlsoURL = aUpdateInfo.aSeeAlsoURL; std::shared_ptr< comphelper::ConfigurationChanges > batch( comphelper::ConfigurationChanges::create()); officecfg::Office::Update::Update::SeeAlso::set(aSeeAlsoURL, batch); batch->commit(); } } } catch (const invalid_update_info&) { SAL_WARN("desktop.updater", "invalid update information"); Updater::log(OString("warning: invalid update info")); } catch (const error_updater& e) { SAL_WARN("desktop.updater", "error during the update check: " << e.what()); Updater::log(OString("warning: error by the updater") + e.what()); } catch (const invalid_size& e) { SAL_WARN("desktop.updater", e.what()); Updater::log(OString("warning: invalid size")); } catch (const invalid_hash& e) { SAL_WARN("desktop.updater", e.what()); Updater::log(OString("warning: invalid hash")); } catch (...) { SAL_WARN("desktop.updater", "unknown error during the update check"); Updater::log(OString("warning: unknown exception")); } } OUString Updater::getUpdateInfoLog() { OUString aUpdateInfoURL("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("bootstrap") ":UserInstallation}/patch/updating.log"); rtl::Bootstrap::expandMacros(aUpdateInfoURL); return aUpdateInfoURL; } OUString Updater::getPatchDirURL() { OUString aPatchDirURL("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("bootstrap") ":UserInstallation}/patch/"); rtl::Bootstrap::expandMacros(aPatchDirURL); return aPatchDirURL; } OUString Updater::getUpdateFileURL() { return getPatchDirURL() + "update.mar"; } OUString Updater::getInstallationPath() { OUString aInstallDir( "$BRAND_BASE_DIR/"); rtl::Bootstrap::expandMacros(aInstallDir); return getPathFromURL(aInstallDir); } OUString Updater::getExecutableDirURL() { OUString aExeDir( "$BRAND_BASE_DIR/" LIBO_BIN_FOLDER "/" ); rtl::Bootstrap::expandMacros(aExeDir); return aExeDir; } void Updater::log(const OUString& rMessage) { SAL_INFO("desktop.updater", rMessage); OUString aUpdateLog = getUpdateInfoLog(); SvFileStream aLog(aUpdateLog, StreamMode::STD_READWRITE); aLog.Seek(aLog.Tell() + aLog.remainingSize()); // make sure we are at the end aLog.WriteLine(OUStringToOString(rMessage, RTL_TEXTENCODING_UTF8)); } void Updater::log(const OString& rMessage) { SAL_INFO("desktop.updater", rMessage); OUString aUpdateLog = getUpdateInfoLog(); SvFileStream aLog(aUpdateLog, StreamMode::STD_READWRITE); aLog.Seek(aLog.Tell() + aLog.remainingSize()); // make sure we are at the end aLog.WriteLine(rMessage); } void Updater::log(const char* pMessage) { SAL_INFO("desktop.updater", pMessage); OUString aUpdateLog = getUpdateInfoLog(); SvFileStream aLog(aUpdateLog, StreamMode::STD_READWRITE); aLog.Seek(aLog.Tell() + aLog.remainingSize()); // make sure we are at the end aLog.WriteOString(pMessage); } OUString Updater::getBuildID() { OUString aBuildID("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("version") ":buildid}"); rtl::Bootstrap::expandMacros(aBuildID); return aBuildID; } OUString Updater::getUpdateChannel() { OUString aUpdateChannel("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("version") ":UpdateChannel}"); rtl::Bootstrap::expandMacros(aUpdateChannel); return aUpdateChannel; } void Updater::removeUpdateFiles() { Updater::log("Removing: " + getUpdateFileURL()); osl::File::remove(getUpdateFileURL()); OUString aPatchDirURL = getPatchDirURL(); osl::Directory aDir(aPatchDirURL); aDir.open(); osl::FileBase::RC eRC; do { osl::DirectoryItem aItem; eRC = aDir.getNextItem(aItem); if (eRC == osl::FileBase::E_None) { osl::FileStatus aStatus(osl_FileStatus_Mask_All); if (aItem.getFileStatus(aStatus) != osl::FileBase::E_None) continue; if (!aStatus.isRegular()) continue; OUString aURL = aStatus.getFileURL(); if (!aURL.endsWith(".mar")) continue; Updater::log("Removing. " + aURL); osl::File::remove(aURL); } } while (eRC == osl::FileBase::E_None); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */