1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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>
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>
45 #include <string_view>
46 #include <comphelper/lok.hxx>
47 #include <comphelper/processfactory.hxx>
48 #include <salhelper/linkhelper.hxx>
51 #define WIN32_LEAN_AND_MEAN
55 using namespace ::com::sun::star
;
56 using namespace ::com::sun::star::uno
;
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"
68 #define SOFFICE2 "soffice"
70 #define SOFFICE2 "soffice.bin"
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 );
88 struct OfficePipeId
: public rtl::StaticWithInit
<OUString
, OfficePipeId
> {
89 OUString
operator () ();
92 OUString
OfficePipeId::operator () ()
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
);
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
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())
135 ::osl::Pipe
pipe( pipeId
, osl_Pipe_OPEN
, sec
);
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
)
147 rTime
= aResolver
.m_aStatus
.getModifyTime();
152 //Returns true if the Folder was more recently modified then
153 //the lastsynchronized file. That is the repository needs to
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
)
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
)
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
189 TimeValue timeFolder
;
190 if (getModifyTimeTargetFile(folderURL
, timeFolder
))
193 if (getModifyTimeTargetFile(fileURL
, timeFile
))
195 if (timeFile
.Seconds
< timeFolder
.Seconds
)
213 bool needToSyncRepository(OUString
const & name
)
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";
232 ::rtl::Bootstrap::expandMacros(folder
);
233 ::rtl::Bootstrap::expandMacros(file
);
234 return compareExtensionFolderWithLastSynchronizedFile(
243 OUString
encodeForRcFile( OUString
const & str
)
245 // escape $\{} (=> rtl bootstrap files)
246 OUStringBuffer
buf(64);
248 const sal_Int32 len
= str
.getLength();
249 for ( ; pos
< len
; ++pos
) {
250 sal_Unicode c
= str
[ pos
];
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) );
272 buf
.append( baseURL
);
273 OUString
relPath(relPath_
);
274 if( relPath
.startsWith("/") )
275 relPath
= relPath
.copy( 1 );
276 if (!relPath
.isEmpty())
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);
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
);
314 OUString
makeRcTerm( OUString
const & url
)
316 OSL_ASSERT( url
.match( "vnd.sun.star.expand:" ));
317 if (url
.match( "vnd.sun.star.expand:" )) {
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
);
330 OUString
expandUnoRcUrl( OUString
const & url
)
332 if (url
.match( "vnd.sun.star.expand:" )) {
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
);
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
353 oslProcessError err
= osl_getExecutableFile(& sFile
.pData
);
355 if (osl_Process_E_None
== err
)
357 sFile
= sFile
.copy(sFile
.lastIndexOf('/') + 1);
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
368 #error "Unsupported platform"
374 ret
= existsOfficePipe();
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();
386 oslProcess
raiseProcess(
387 OUString
const & appURL
, Sequence
<OUString
> const & args
)
390 oslProcess hProcess
= nullptr;
391 oslProcessError rc
= osl_executeProcess(
393 reinterpret_cast<rtl_uString
**>(
394 const_cast<OUString
*>(args
.getConstArray()) ),
396 osl_Process_DETACHED
,
398 nullptr, // => current working dir
399 nullptr, 0, // => no env vars
403 case osl_Process_E_None
:
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
:
415 throw RuntimeException( "unmapped error!", nullptr );
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 );
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!" );
455 return xUnoUrlResolver
->resolve( connectString
);
457 catch (const connection::NoConnectException
&) {
460 ::osl::Thread::wait( std::chrono::milliseconds(500) );
465 return nullptr; // warning C4715
469 static void writeConsoleWithStream(OUString
const & sText
, HANDLE stream
)
471 DWORD nWrittenChars
= 0;
472 WriteFile(stream
, sText
.getStr(),
473 sText
.getLength() * 2, &nWrittenChars
, nullptr);
476 static void writeConsoleWithStream(OUString
const & sText
, FILE * stream
)
478 OString s
= OUStringToOString(sText
, osl_getThreadTextEncoding());
479 fprintf(stream
, "%s", s
.getStr());
484 void writeConsole(OUString
const & sText
)
487 writeConsoleWithStream(sText
, GetStdHandle(STD_OUTPUT_HANDLE
));
489 writeConsoleWithStream(sText
, stdout
);
493 void writeConsoleError(OUString
const & sText
)
496 writeConsoleWithStream(sText
, GetStdHandle(STD_ERROR_HANDLE
));
498 writeConsoleWithStream(sText
, stderr
);
502 OUString
readConsole()
505 sal_Unicode aBuffer
[1024];
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);
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());
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
)
536 ::rtl::Bootstrap::get( "DISABLE_EXTENSION_SYNCHRONIZATION", sDisable
, OUString() );
537 if (!sDisable
.isEmpty())
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"))
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()));
562 restarter
->requestRestart(xCmdEnv
.is() ? xCmdEnv
->getInteractionHandler() :
563 Reference
<task::XInteractionHandler
>());
569 void disposeBridges(Reference
<css::uno::XComponentContext
> const & ctx
)
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
);
585 catch ( const css::lang::DisposedException
& )
594 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */