Version 6.4.0.3, tag libreoffice-6.4.0.3
[LibreOffice.git] / desktop / source / deployment / misc / dp_misc.cxx
blob7b4aafb3b0839a070117ea7cf71c9c31383df890
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/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <config_folders.h>
21 #include <config_features.h>
22 #include <chrono>
24 #include <dp_misc.h>
25 #include <dp_version.hxx>
26 #include <dp_interact.h>
27 #include <rtl/uri.hxx>
28 #include <rtl/digest.h>
29 #include <rtl/random.h>
30 #include <rtl/bootstrap.hxx>
31 #include <sal/log.hxx>
32 #include <unotools/bootstrap.hxx>
33 #include <osl/file.hxx>
34 #include <osl/pipe.hxx>
35 #include <osl/security.hxx>
36 #include <osl/thread.hxx>
37 #include <com/sun/star/ucb/CommandAbortedException.hpp>
38 #include <com/sun/star/task/XInteractionHandler.hpp>
39 #include <com/sun/star/bridge/BridgeFactory.hpp>
40 #include <com/sun/star/bridge/UnoUrlResolver.hpp>
41 #include <com/sun/star/bridge/XUnoUrlResolver.hpp>
42 #include <com/sun/star/deployment/ExtensionManager.hpp>
43 #include <com/sun/star/task/OfficeRestartManager.hpp>
44 #include <memory>
45 #include <string_view>
46 #include <comphelper/lok.hxx>
47 #include <comphelper/processfactory.hxx>
48 #include <salhelper/linkhelper.hxx>
50 #ifdef _WIN32
51 #define WIN32_LEAN_AND_MEAN
52 #include <windows.h>
53 #endif
55 using namespace ::com::sun::star;
56 using namespace ::com::sun::star::uno;
58 #if defined(_WIN32)
59 #define SOFFICE1 "soffice.exe"
60 #define SBASE "sbase.exe"
61 #define SCALC "scalc.exe"
62 #define SDRAW "sdraw.exe"
63 #define SIMPRESS "simpress.exe"
64 #define SWRITER "swriter.exe"
65 #endif
67 #ifdef MACOSX
68 #define SOFFICE2 "soffice"
69 #else
70 #define SOFFICE2 "soffice.bin"
71 #endif
73 namespace dp_misc {
74 namespace {
76 struct UnoRc : public rtl::StaticWithInit<
77 std::shared_ptr<rtl::Bootstrap>, UnoRc> {
78 std::shared_ptr<rtl::Bootstrap> operator () () {
79 OUString unorc( "$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("louno") );
80 ::rtl::Bootstrap::expandMacros( unorc );
81 std::shared_ptr< ::rtl::Bootstrap > ret(
82 new ::rtl::Bootstrap( unorc ) );
83 OSL_ASSERT( ret->getHandle() != nullptr );
84 return ret;
88 struct OfficePipeId : public rtl::StaticWithInit<OUString, OfficePipeId> {
89 OUString operator () ();
92 OUString OfficePipeId::operator () ()
94 OUString userPath;
95 ::utl::Bootstrap::PathStatus aLocateResult =
96 ::utl::Bootstrap::locateUserInstallation( userPath );
97 if (!(aLocateResult == ::utl::Bootstrap::PATH_EXISTS ||
98 aLocateResult == ::utl::Bootstrap::PATH_VALID))
100 throw Exception("Extension Manager: Could not obtain path for UserInstallation.", nullptr);
103 rtlDigest digest = rtl_digest_create( rtl_Digest_AlgorithmMD5 );
104 if (!digest) {
105 throw RuntimeException("cannot get digest rtl_Digest_AlgorithmMD5!", nullptr );
108 sal_uInt8 const * data =
109 reinterpret_cast<sal_uInt8 const *>(userPath.getStr());
110 std::size_t size = userPath.getLength() * sizeof (sal_Unicode);
111 sal_uInt32 md5_key_len = rtl_digest_queryLength( digest );
112 std::unique_ptr<sal_uInt8[]> md5_buf( new sal_uInt8 [ md5_key_len ] );
114 rtl_digest_init( digest, data, static_cast<sal_uInt32>(size) );
115 rtl_digest_update( digest, data, static_cast<sal_uInt32>(size) );
116 rtl_digest_get( digest, md5_buf.get(), md5_key_len );
117 rtl_digest_destroy( digest );
119 // create hex-value string from the MD5 value to keep
120 // the string size minimal
121 OUStringBuffer buf;
122 buf.append( "SingleOfficeIPC_" );
123 for ( sal_uInt32 i = 0; i < md5_key_len; ++i ) {
124 buf.append( static_cast<sal_Int32>(md5_buf[ i ]), 0x10 );
126 return buf.makeStringAndClear();
129 bool existsOfficePipe()
131 OUString const & pipeId = OfficePipeId::get();
132 if (pipeId.isEmpty())
133 return false;
134 ::osl::Security sec;
135 ::osl::Pipe pipe( pipeId, osl_Pipe_OPEN, sec );
136 return pipe.is();
139 //get modification time
140 bool getModifyTimeTargetFile(const OUString &rFileURL, TimeValue &rTime)
142 salhelper::LinkResolver aResolver(osl_FileStatus_Mask_ModifyTime);
144 if (aResolver.fetchFileStatus(rFileURL) != osl::FileBase::E_None)
145 return false;
147 rTime = aResolver.m_aStatus.getModifyTime();
149 return true;
152 //Returns true if the Folder was more recently modified then
153 //the lastsynchronized file. That is the repository needs to
154 //be synchronized.
155 bool compareExtensionFolderWithLastSynchronizedFile(
156 OUString const & folderURL, OUString const & fileURL)
158 bool bNeedsSync = false;
159 ::osl::DirectoryItem itemExtFolder;
160 ::osl::File::RC err1 =
161 ::osl::DirectoryItem::get(folderURL, itemExtFolder);
162 //If it does not exist, then there is nothing to be done
163 if (err1 == ::osl::File::E_NOENT)
165 return false;
167 else if (err1 != ::osl::File::E_None)
169 OSL_FAIL("Cannot access extension folder");
170 return true; //sync just in case
173 //If last synchronized does not exist, then OOo is started for the first time
174 ::osl::DirectoryItem itemFile;
175 ::osl::File::RC err2 = ::osl::DirectoryItem::get(fileURL, itemFile);
176 if (err2 == ::osl::File::E_NOENT)
178 return true;
181 else if (err2 != ::osl::File::E_None)
183 OSL_FAIL("Cannot access file lastsynchronized");
184 return true; //sync just in case
187 //compare the modification time of the extension folder and the last
188 //modified file
189 TimeValue timeFolder;
190 if (getModifyTimeTargetFile(folderURL, timeFolder))
192 TimeValue timeFile;
193 if (getModifyTimeTargetFile(fileURL, timeFile))
195 if (timeFile.Seconds < timeFolder.Seconds)
196 bNeedsSync = true;
198 else
200 OSL_ASSERT(false);
201 bNeedsSync = true;
204 else
206 OSL_ASSERT(false);
207 bNeedsSync = true;
210 return bNeedsSync;
213 bool needToSyncRepository(OUString const & name)
215 OUString folder;
216 OUString file;
217 if ( name == "bundled" )
219 folder = "$BUNDLED_EXTENSIONS";
220 file = "$BUNDLED_EXTENSIONS_USER/lastsynchronized";
222 else if ( name == "shared" )
224 folder = "$UNO_SHARED_PACKAGES_CACHE/uno_packages";
225 file = "$SHARED_EXTENSIONS_USER/lastsynchronized";
227 else
229 OSL_ASSERT(false);
230 return true;
232 ::rtl::Bootstrap::expandMacros(folder);
233 ::rtl::Bootstrap::expandMacros(file);
234 return compareExtensionFolderWithLastSynchronizedFile(
235 folder, file);
239 } // anon namespace
242 namespace {
243 OUString encodeForRcFile( OUString const & str )
245 // escape $\{} (=> rtl bootstrap files)
246 OUStringBuffer buf(64);
247 sal_Int32 pos = 0;
248 const sal_Int32 len = str.getLength();
249 for ( ; pos < len; ++pos ) {
250 sal_Unicode c = str[ pos ];
251 switch (c) {
252 case '$':
253 case '\\':
254 case '{':
255 case '}':
256 buf.append( '\\' );
257 break;
259 buf.append( c );
261 return buf.makeStringAndClear();
266 OUString makeURL( OUString const & baseURL, OUString const & relPath_ )
268 OUStringBuffer buf(128);
269 if (baseURL.getLength() > 1 && baseURL[ baseURL.getLength() - 1 ] == '/')
270 buf.append( std::u16string_view(baseURL).substr(0, baseURL.getLength() - 1) );
271 else
272 buf.append( baseURL );
273 OUString relPath(relPath_);
274 if( relPath.startsWith("/") )
275 relPath = relPath.copy( 1 );
276 if (!relPath.isEmpty())
278 buf.append( '/' );
279 if (baseURL.match( "vnd.sun.star.expand:" )) {
280 // encode for macro expansion: relPath is supposed to have no
281 // macros, so encode $, {} \ (bootstrap mimic)
282 relPath = encodeForRcFile(relPath);
284 // encode once more for vnd.sun.star.expand schema:
285 // vnd.sun.star.expand:$UNO_...
286 // will expand to file-url
287 relPath = ::rtl::Uri::encode( relPath, rtl_UriCharClassUric,
288 rtl_UriEncodeIgnoreEscapes,
289 RTL_TEXTENCODING_UTF8 );
291 buf.append( relPath );
293 return buf.makeStringAndClear();
296 OUString makeURLAppendSysPathSegment( OUString const & baseURL, OUString const & segment )
298 OSL_ASSERT(segment.indexOf(u'/') == -1);
300 ::rtl::Uri::encode(
301 segment, rtl_UriCharClassPchar, rtl_UriEncodeIgnoreEscapes,
302 RTL_TEXTENCODING_UTF8);
303 return makeURL(baseURL, segment);
307 OUString expandUnoRcTerm( OUString const & term_ )
309 OUString term(term_);
310 UnoRc::get()->expandMacrosFrom( term );
311 return term;
314 OUString makeRcTerm( OUString const & url )
316 OSL_ASSERT( url.match( "vnd.sun.star.expand:" ));
317 if (url.match( "vnd.sun.star.expand:" )) {
318 // cut protocol:
319 OUString rcterm( url.copy( sizeof ("vnd.sun.star.expand:") - 1 ) );
320 // decode uric class chars:
321 rcterm = ::rtl::Uri::decode(
322 rcterm, rtl_UriDecodeWithCharset, RTL_TEXTENCODING_UTF8 );
323 return rcterm;
325 else
326 return url;
330 OUString expandUnoRcUrl( OUString const & url )
332 if (url.match( "vnd.sun.star.expand:" )) {
333 // cut protocol:
334 OUString rcurl( url.copy( sizeof ("vnd.sun.star.expand:") - 1 ) );
335 // decode uric class chars:
336 rcurl = ::rtl::Uri::decode(
337 rcurl, rtl_UriDecodeWithCharset, RTL_TEXTENCODING_UTF8 );
338 // expand macro string:
339 UnoRc::get()->expandMacrosFrom( rcurl );
340 return rcurl;
342 else {
343 return url;
348 bool office_is_running()
350 //We need to check if we run within the office process. Then we must not use the pipe, because
351 //this could cause a deadlock. This is actually a workaround for i82778
352 OUString sFile;
353 oslProcessError err = osl_getExecutableFile(& sFile.pData);
354 bool ret = false;
355 if (osl_Process_E_None == err)
357 sFile = sFile.copy(sFile.lastIndexOf('/') + 1);
358 if (
359 #if defined UNIX
360 sFile == SOFFICE2
361 #elif defined WNT
362 //osl_getExecutableFile should deliver "soffice.bin" on windows
363 //even if swriter.exe, scalc.exe etc. was started. This is a bug
364 //in osl_getExecutableFile
365 sFile == SOFFICE1 || sFile == SOFFICE2 || sFile == SBASE || sFile == SCALC
366 || sFile == SDRAW || sFile == SIMPRESS || sFile == SWRITER
367 #else
368 #error "Unsupported platform"
369 #endif
372 ret = true;
373 else
374 ret = existsOfficePipe();
376 else
378 OSL_FAIL("NOT osl_Process_E_None ");
379 //if osl_getExecutable file then we take the risk of creating a pipe
380 ret = existsOfficePipe();
382 return ret;
386 oslProcess raiseProcess(
387 OUString const & appURL, Sequence<OUString> const & args )
389 ::osl::Security sec;
390 oslProcess hProcess = nullptr;
391 oslProcessError rc = osl_executeProcess(
392 appURL.pData,
393 reinterpret_cast<rtl_uString **>(
394 const_cast<OUString *>(args.getConstArray()) ),
395 args.getLength(),
396 osl_Process_DETACHED,
397 sec.getHandle(),
398 nullptr, // => current working dir
399 nullptr, 0, // => no env vars
400 &hProcess );
402 switch (rc) {
403 case osl_Process_E_None:
404 break;
405 case osl_Process_E_NotFound:
406 throw RuntimeException( "image not found!", nullptr );
407 case osl_Process_E_TimedOut:
408 throw RuntimeException( "timeout occurred!", nullptr );
409 case osl_Process_E_NoPermission:
410 throw RuntimeException( "permission denied!", nullptr );
411 case osl_Process_E_Unknown:
412 throw RuntimeException( "unknown error!", nullptr );
413 case osl_Process_E_InvalidError:
414 default:
415 throw RuntimeException( "unmapped error!", nullptr );
418 return hProcess;
422 OUString generateRandomPipeId()
424 // compute some good pipe id:
425 static rtlRandomPool s_hPool = rtl_random_createPool();
426 if (s_hPool == nullptr)
427 throw RuntimeException( "cannot create random pool!?", nullptr );
428 sal_uInt8 bytes[ 32 ];
429 if (rtl_random_getBytes(
430 s_hPool, bytes, SAL_N_ELEMENTS(bytes) ) != rtl_Random_E_None) {
431 throw RuntimeException( "random pool error!?", nullptr );
433 OUStringBuffer buf;
434 for (unsigned char byte : bytes) {
435 buf.append( static_cast<sal_Int32>(byte), 0x10 );
437 return buf.makeStringAndClear();
441 Reference<XInterface> resolveUnoURL(
442 OUString const & connectString,
443 Reference<XComponentContext> const & xLocalContext,
444 AbortChannel const * abortChannel )
446 Reference<bridge::XUnoUrlResolver> xUnoUrlResolver(
447 bridge::UnoUrlResolver::create( xLocalContext ) );
449 for (int i = 0; i <= 40; ++i) // 20 seconds
451 if (abortChannel != nullptr && abortChannel->isAborted()) {
452 throw ucb::CommandAbortedException( "abort!" );
454 try {
455 return xUnoUrlResolver->resolve( connectString );
457 catch (const connection::NoConnectException &) {
458 if (i < 40)
460 ::osl::Thread::wait( std::chrono::milliseconds(500) );
462 else throw;
465 return nullptr; // warning C4715
468 #ifdef _WIN32
469 static void writeConsoleWithStream(OUString const & sText, HANDLE stream)
471 DWORD nWrittenChars = 0;
472 WriteFile(stream, sText.getStr(),
473 sText.getLength() * 2, &nWrittenChars, nullptr);
475 #else
476 static void writeConsoleWithStream(OUString const & sText, FILE * stream)
478 OString s = OUStringToOString(sText, osl_getThreadTextEncoding());
479 fprintf(stream, "%s", s.getStr());
480 fflush(stream);
482 #endif
484 void writeConsole(OUString const & sText)
486 #ifdef _WIN32
487 writeConsoleWithStream(sText, GetStdHandle(STD_OUTPUT_HANDLE));
488 #else
489 writeConsoleWithStream(sText, stdout);
490 #endif
493 void writeConsoleError(OUString const & sText)
495 #ifdef _WIN32
496 writeConsoleWithStream(sText, GetStdHandle(STD_ERROR_HANDLE));
497 #else
498 writeConsoleWithStream(sText, stderr);
499 #endif
502 OUString readConsole()
504 #ifdef _WIN32
505 sal_Unicode aBuffer[1024];
506 DWORD dwRead = 0;
507 //unopkg.com feeds unopkg.exe with wchar_t|s
508 if (ReadFile( GetStdHandle(STD_INPUT_HANDLE), &aBuffer, sizeof(aBuffer), &dwRead, nullptr ) )
510 OSL_ASSERT((dwRead % 2) == 0);
511 OUString value( aBuffer, dwRead / 2);
512 return value.trim();
514 #else
515 char buf[1024];
516 memset(buf, 0, 1024);
517 // read one char less so that the last char in buf is always zero
518 if (fgets(buf, 1024, stdin) != nullptr)
520 OUString value = OStringToOUString(OString(buf), osl_getThreadTextEncoding());
521 return value.trim();
523 #endif
524 throw css::uno::RuntimeException("reading from stdin failed");
527 void TRACE(OUString const & sText)
529 SAL_INFO("desktop.deployment", sText);
532 void syncRepositories(
533 bool force, Reference<ucb::XCommandEnvironment> const & xCmdEnv)
535 OUString sDisable;
536 ::rtl::Bootstrap::get( "DISABLE_EXTENSION_SYNCHRONIZATION", sDisable, OUString() );
537 if (!sDisable.isEmpty())
538 return;
540 Reference<deployment::XExtensionManager> xExtensionManager;
541 //synchronize shared before bundled otherwise there are
542 //more revoke and registration calls.
543 bool bModified = false;
544 if (force || needToSyncRepository("shared") || needToSyncRepository("bundled"))
546 xExtensionManager =
547 deployment::ExtensionManager::get(
548 comphelper::getProcessComponentContext());
550 if (xExtensionManager.is())
552 bModified = xExtensionManager->synchronize(
553 Reference<task::XAbortChannel>(), xCmdEnv);
556 #if !HAVE_FEATURE_MACOSX_SANDBOX
557 if (bModified && !comphelper::LibreOfficeKit::isActive())
559 Reference<task::XRestartManager> restarter(task::OfficeRestartManager::get(comphelper::getProcessComponentContext()));
560 if (restarter.is())
562 restarter->requestRestart(xCmdEnv.is() ? xCmdEnv->getInteractionHandler() :
563 Reference<task::XInteractionHandler>());
566 #endif
569 void disposeBridges(Reference<css::uno::XComponentContext> const & ctx)
571 if (!ctx.is())
572 return;
574 Reference<css::bridge::XBridgeFactory2> bridgeFac( css::bridge::BridgeFactory::create(ctx) );
576 const Sequence< Reference<css::bridge::XBridge> >seqBridges = bridgeFac->getExistingBridges();
577 for (sal_Int32 i = 0; i < seqBridges.getLength(); i++)
579 Reference<css::lang::XComponent> comp(seqBridges[i], UNO_QUERY);
580 if (comp.is())
582 try {
583 comp->dispose();
585 catch ( const css::lang::DisposedException& )
594 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */