LibreOffice Module desktop (master) 1
dp_misc.cxx
Go to the documentation of this file.
1/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2/*
3 * This file is part of the LibreOffice project.
4 *
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 *
9 * This file incorporates work covered by the following license notice:
10 *
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 .
18 */
19
20#include <config_folders.h>
21#include <config_features.h>
22#include <chrono>
23
24#include <dp_misc.h>
25#include <dp_interact.h>
26#include <dp_shared.hxx>
27#include <o3tl/string_view.hxx>
28#include <rtl/uri.hxx>
29#include <rtl/digest.h>
30#include <rtl/random.h>
31#include <rtl/bootstrap.hxx>
32#include <rtl/ustrbuf.hxx>
33#include <sal/log.hxx>
35#include <osl/file.hxx>
36#include <osl/pipe.hxx>
37#include <osl/security.hxx>
38#include <osl/thread.hxx>
39#include <com/sun/star/ucb/CommandAbortedException.hpp>
40#include <com/sun/star/task/XInteractionHandler.hpp>
41#include <com/sun/star/bridge/BridgeFactory.hpp>
42#include <com/sun/star/bridge/UnoUrlResolver.hpp>
43#include <com/sun/star/bridge/XUnoUrlResolver.hpp>
44#include <com/sun/star/deployment/ExtensionManager.hpp>
45#include <com/sun/star/lang/DisposedException.hpp>
46#include <com/sun/star/task/OfficeRestartManager.hpp>
47#include <memory>
48#include <string_view>
49#include <thread>
50#include <comphelper/lok.hxx>
53
54#ifdef _WIN32
55#include <prewin.h>
56#include <postwin.h>
57#endif
58
59using namespace ::com::sun::star;
60using namespace ::com::sun::star::uno;
61
62namespace dp_misc {
63namespace {
64
65std::shared_ptr<rtl::Bootstrap> & UnoRc()
66{
67 static std::shared_ptr<rtl::Bootstrap> theRc = []()
68 {
69 OUString unorc( "$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("louno") );
70 ::rtl::Bootstrap::expandMacros( unorc );
71 auto ret = std::make_shared<::rtl::Bootstrap>( unorc );
72 OSL_ASSERT( ret->getHandle() != nullptr );
73 return ret;
74 }();
75 return theRc;
76};
77
78OUString generateOfficePipeId()
79{
80 OUString userPath;
81 ::utl::Bootstrap::PathStatus aLocateResult =
83 if (aLocateResult != ::utl::Bootstrap::PATH_EXISTS &&
84 aLocateResult != ::utl::Bootstrap::PATH_VALID)
85 {
86 throw Exception("Extension Manager: Could not obtain path for UserInstallation.", nullptr);
87 }
88
89 rtlDigest digest = rtl_digest_create( rtl_Digest_AlgorithmMD5 );
90 if (!digest) {
91 throw RuntimeException("cannot get digest rtl_Digest_AlgorithmMD5!", nullptr );
92 }
93
94 sal_uInt8 const * data =
95 reinterpret_cast<sal_uInt8 const *>(userPath.getStr());
96 std::size_t size = userPath.getLength() * sizeof (sal_Unicode);
97 sal_uInt32 md5_key_len = rtl_digest_queryLength( digest );
98 std::unique_ptr<sal_uInt8[]> md5_buf( new sal_uInt8 [ md5_key_len ] );
99
100 rtl_digest_init( digest, data, static_cast<sal_uInt32>(size) );
101 rtl_digest_update( digest, data, static_cast<sal_uInt32>(size) );
102 rtl_digest_get( digest, md5_buf.get(), md5_key_len );
103 rtl_digest_destroy( digest );
104
105 // create hex-value string from the MD5 value to keep
106 // the string size minimal
107 OUStringBuffer buf;
108 buf.append( "SingleOfficeIPC_" );
109 for ( sal_uInt32 i = 0; i < md5_key_len; ++i ) {
110 buf.append( static_cast<sal_Int32>(md5_buf[ i ]), 0x10 );
111 }
112 return buf.makeStringAndClear();
113}
114
115bool existsOfficePipe()
116{
117 static OUString OfficePipeId = generateOfficePipeId();
118
119 OUString const & pipeId = OfficePipeId;
120 if (pipeId.isEmpty())
121 return false;
122 ::osl::Security sec;
123 ::osl::Pipe pipe( pipeId, osl_Pipe_OPEN, sec );
124 return pipe.is();
125}
126
127//get modification time
128bool getModifyTimeTargetFile(const OUString &rFileURL, TimeValue &rTime)
129{
130 salhelper::LinkResolver aResolver(osl_FileStatus_Mask_ModifyTime);
131
132 if (aResolver.fetchFileStatus(rFileURL) != osl::FileBase::E_None)
133 return false;
134
135 rTime = aResolver.m_aStatus.getModifyTime();
136
137 return true;
138}
139
140//Returns true if the Folder was more recently modified then
141//the lastsynchronized file. That is the repository needs to
142//be synchronized.
143bool compareExtensionFolderWithLastSynchronizedFile(
144 OUString const & folderURL, OUString const & fileURL)
145{
146 bool bNeedsSync = false;
147 ::osl::DirectoryItem itemExtFolder;
148 ::osl::File::RC err1 =
149 ::osl::DirectoryItem::get(folderURL, itemExtFolder);
150 //If it does not exist, then there is nothing to be done
151 if (err1 == ::osl::File::E_NOENT)
152 {
153 return false;
154 }
155 else if (err1 != ::osl::File::E_None)
156 {
157 OSL_FAIL("Cannot access extension folder");
158 return true; //sync just in case
159 }
160
161 //If last synchronized does not exist, then OOo is started for the first time
162 ::osl::DirectoryItem itemFile;
163 ::osl::File::RC err2 = ::osl::DirectoryItem::get(fileURL, itemFile);
164 if (err2 == ::osl::File::E_NOENT)
165 {
166 return true;
167
168 }
169 else if (err2 != ::osl::File::E_None)
170 {
171 OSL_FAIL("Cannot access file lastsynchronized");
172 return true; //sync just in case
173 }
174
175 //compare the modification time of the extension folder and the last
176 //modified file
177 TimeValue timeFolder;
178 if (getModifyTimeTargetFile(folderURL, timeFolder))
179 {
180 TimeValue timeFile;
181 if (getModifyTimeTargetFile(fileURL, timeFile))
182 {
183 if (timeFile.Seconds < timeFolder.Seconds)
184 bNeedsSync = true;
185 }
186 else
187 {
188 OSL_ASSERT(false);
189 bNeedsSync = true;
190 }
191 }
192 else
193 {
194 OSL_ASSERT(false);
195 bNeedsSync = true;
196 }
197
198 return bNeedsSync;
199}
200
201bool needToSyncRepository(std::u16string_view name)
202{
203 OUString folder;
204 OUString file;
205 if ( name == u"bundled" )
206 {
207 folder = "$BUNDLED_EXTENSIONS";
208 file = "$BUNDLED_EXTENSIONS_USER/lastsynchronized";
209 }
210 else if ( name == u"shared" )
211 {
212 folder = "$UNO_SHARED_PACKAGES_CACHE/uno_packages";
213 file = "$SHARED_EXTENSIONS_USER/lastsynchronized";
214 }
215 else
216 {
217 OSL_ASSERT(false);
218 return true;
219 }
220 ::rtl::Bootstrap::expandMacros(folder);
221 ::rtl::Bootstrap::expandMacros(file);
222 return compareExtensionFolderWithLastSynchronizedFile(
223 folder, file);
224}
225
226
227} // anon namespace
228
229
230namespace {
231OUString encodeForRcFile( std::u16string_view str )
232{
233 // escape $\{} (=> rtl bootstrap files)
234 OUStringBuffer buf(64);
235 size_t pos = 0;
236 const size_t len = str.size();
237 for ( ; pos < len; ++pos ) {
238 sal_Unicode c = str[ pos ];
239 switch (c) {
240 case '$':
241 case '\\':
242 case '{':
243 case '}':
244 buf.append( '\\' );
245 break;
246 }
247 buf.append( c );
248 }
249 return buf.makeStringAndClear();
250}
251}
252
253
254OUString makeURL( std::u16string_view baseURL, OUString const & relPath_ )
255{
256 OUStringBuffer buf(128);
257 if (baseURL.size() > 1 && baseURL[ baseURL.size() - 1 ] == '/')
258 buf.append( baseURL.substr(0, baseURL.size() - 1) );
259 else
260 buf.append( baseURL );
261 OUString relPath(relPath_);
262 if( relPath.startsWith("/") )
263 relPath = relPath.copy( 1 );
264 if (!relPath.isEmpty())
265 {
266 buf.append( '/' );
267 if (o3tl::starts_with(baseURL, u"vnd.sun.star.expand:" )) {
268 // encode for macro expansion: relPath is supposed to have no
269 // macros, so encode $, {} \ (bootstrap mimic)
270 relPath = encodeForRcFile(relPath);
271
272 // encode once more for vnd.sun.star.expand schema:
273 // vnd.sun.star.expand:$UNO_...
274 // will expand to file-url
275 relPath = ::rtl::Uri::encode( relPath, rtl_UriCharClassUric,
276 rtl_UriEncodeIgnoreEscapes,
277 RTL_TEXTENCODING_UTF8 );
278 }
279 buf.append( relPath );
280 }
281 return buf.makeStringAndClear();
282}
283
284OUString makeURLAppendSysPathSegment( std::u16string_view baseURL, OUString const & segment )
285{
286 OSL_ASSERT(segment.indexOf(u'/') == -1);
287
288 ::rtl::Uri::encode(
289 segment, rtl_UriCharClassPchar, rtl_UriEncodeIgnoreEscapes,
290 RTL_TEXTENCODING_UTF8);
291 return makeURL(baseURL, segment);
292}
293
294
295OUString expandUnoRcTerm( OUString const & term_ )
296{
297 OUString term(term_);
298 UnoRc()->expandMacrosFrom( term );
299 return term;
300}
301
302OUString makeRcTerm( OUString const & url )
303{
304 OSL_ASSERT( url.match( "vnd.sun.star.expand:" ));
305 if (OUString rcterm; url.startsWithIgnoreAsciiCase("vnd.sun.star.expand:", &rcterm)) {
306 // decode uric class chars:
307 rcterm = ::rtl::Uri::decode(
308 rcterm, rtl_UriDecodeWithCharset, RTL_TEXTENCODING_UTF8 );
309 return rcterm;
310 }
311 else
312 return url;
313}
314
315
316OUString expandUnoRcUrl( OUString const & url )
317{
318 if (OUString rcurl; url.startsWithIgnoreAsciiCase("vnd.sun.star.expand:", &rcurl)) {
319 // decode uric class chars:
320 rcurl = ::rtl::Uri::decode(
321 rcurl, rtl_UriDecodeWithCharset, RTL_TEXTENCODING_UTF8 );
322 // expand macro string:
323 UnoRc()->expandMacrosFrom( rcurl );
324 return rcurl;
325 }
326 else {
327 return url;
328 }
329}
330
331
333{
334 //We need to check if we run within the office process. Then we must not use the pipe, because
335 //this could cause a deadlock. This is actually a workaround for i82778
336 OUString sFile;
337 oslProcessError err = osl_getExecutableFile(& sFile.pData);
338 bool ret = false;
339 if (osl_Process_E_None == err)
340 {
341 sFile = sFile.copy(sFile.lastIndexOf('/') + 1);
342 if (
343#if defined _WIN32
344 //osl_getExecutableFile should deliver "soffice.bin" on windows
345 //even if swriter.exe, scalc.exe etc. was started. This is a bug
346 //in osl_getExecutableFile
347 sFile == "soffice.bin" || sFile == "soffice.exe" || sFile == "soffice.com"
348 || sFile == "soffice" || sFile == "swriter.exe" || sFile == "swriter"
349 || sFile == "scalc.exe" || sFile == "scalc" || sFile == "simpress.exe"
350 || sFile == "simpress" || sFile == "sdraw.exe" || sFile == "sdraw"
351 || sFile == "sbase.exe" || sFile == "sbase"
352#elif defined MACOSX
353 sFile == "soffice"
354#elif defined UNIX
355 sFile == "soffice.bin"
356#else
357#error "Unsupported platform"
358#endif
359
360 )
361 ret = true;
362 else
363 ret = existsOfficePipe();
364 }
365 else
366 {
367 OSL_FAIL("NOT osl_Process_E_None ");
368 //if osl_getExecutable file then we take the risk of creating a pipe
369 ret = existsOfficePipe();
370 }
371 return ret;
372}
373
374
375oslProcess raiseProcess(
376 OUString const & appURL, Sequence<OUString> const & args )
377{
378 ::osl::Security sec;
379 oslProcess hProcess = nullptr;
380 oslProcessError rc = osl_executeProcess(
381 appURL.pData,
382 reinterpret_cast<rtl_uString **>(
383 const_cast<OUString *>(args.getConstArray()) ),
384 args.getLength(),
385 osl_Process_DETACHED,
386 sec.getHandle(),
387 nullptr, // => current working dir
388 nullptr, 0, // => no env vars
389 &hProcess );
390
391 switch (rc) {
392 case osl_Process_E_None:
393 break;
394 case osl_Process_E_NotFound:
395 throw RuntimeException( "image not found!", nullptr );
396 case osl_Process_E_TimedOut:
397 throw RuntimeException( "timeout occurred!", nullptr );
398 case osl_Process_E_NoPermission:
399 throw RuntimeException( "permission denied!", nullptr );
400 case osl_Process_E_Unknown:
401 throw RuntimeException( "unknown error!", nullptr );
402 case osl_Process_E_InvalidError:
403 default:
404 throw RuntimeException( "unmapped error!", nullptr );
405 }
406
407 return hProcess;
408}
409
410
412{
413 // compute some good pipe id:
414 static rtlRandomPool s_hPool = rtl_random_createPool();
415 if (s_hPool == nullptr)
416 throw RuntimeException( "cannot create random pool!?", nullptr );
417 sal_uInt8 bytes[ 32 ];
418 if (rtl_random_getBytes(
419 s_hPool, bytes, std::size(bytes) ) != rtl_Random_E_None) {
420 throw RuntimeException( "random pool error!?", nullptr );
421 }
422 OUStringBuffer buf;
423 for (unsigned char byte : bytes) {
424 buf.append( static_cast<sal_Int32>(byte), 0x10 );
425 }
426 return buf.makeStringAndClear();
427}
428
429
430Reference<XInterface> resolveUnoURL(
431 OUString const & connectString,
432 Reference<XComponentContext> const & xLocalContext,
433 AbortChannel const * abortChannel )
434{
435 Reference<bridge::XUnoUrlResolver> xUnoUrlResolver(
436 bridge::UnoUrlResolver::create( xLocalContext ) );
437
438 for (int i = 0; i <= 40; ++i) // 20 seconds
439 {
440 if (abortChannel != nullptr && abortChannel->isAborted()) {
441 throw ucb::CommandAbortedException( "abort!" );
442 }
443 try {
444 return xUnoUrlResolver->resolve( connectString );
445 }
446 catch (const connection::NoConnectException &) {
447 if (i < 40)
448 {
449 std::this_thread::sleep_for( std::chrono::milliseconds(500) );
450 }
451 else throw;
452 }
453 }
454 return nullptr; // warning C4715
455}
456
457static void writeConsoleWithStream(std::u16string_view sText, FILE * stream)
458{
459 OString s = OUStringToOString(sText, osl_getThreadTextEncoding());
460 fprintf(stream, "%s", s.getStr());
461 fflush(stream);
462}
463
464void writeConsole(std::u16string_view sText)
465{
466 writeConsoleWithStream(sText, stdout);
467}
468
469void writeConsoleError(std::u16string_view sText)
470{
471 writeConsoleWithStream(sText, stderr);
472}
473
474OUString readConsole()
475{
476 char buf[1024];
477 memset(buf, 0, 1024);
478 // read one char less so that the last char in buf is always zero
479 if (fgets(buf, 1024, stdin) != nullptr)
480 {
481 OUString value = OStringToOUString(std::string_view(buf), osl_getThreadTextEncoding());
482 return value.trim();
483 }
484 throw css::uno::RuntimeException("reading from stdin failed");
485}
486
487void TRACE(OUString const & sText)
488{
489 SAL_INFO("desktop.deployment", sText);
490}
491
493 bool force, Reference<ucb::XCommandEnvironment> const & xCmdEnv)
494{
495 OUString sDisable;
496 ::rtl::Bootstrap::get( "DISABLE_EXTENSION_SYNCHRONIZATION", sDisable, OUString() );
497 if (!sDisable.isEmpty())
498 return;
499
500 Reference<deployment::XExtensionManager> xExtensionManager;
501 //synchronize shared before bundled otherwise there are
502 //more revoke and registration calls.
503 bool bModified = false;
504 if (force || needToSyncRepository(u"shared") || needToSyncRepository(u"bundled"))
505 {
506 xExtensionManager =
507 deployment::ExtensionManager::get(
509
510 if (xExtensionManager.is())
511 {
512 bModified = xExtensionManager->synchronize(
513 Reference<task::XAbortChannel>(), xCmdEnv);
514 }
515 }
516#if HAVE_FEATURE_MACOSX_SANDBOX
517 (void) bModified;
518#else
519 if (bModified && !comphelper::LibreOfficeKit::isActive())
520 {
521 Reference<task::XRestartManager> restarter(task::OfficeRestartManager::get(comphelper::getProcessComponentContext()));
522 if (restarter.is())
523 {
524 restarter->requestRestart(xCmdEnv.is() ? xCmdEnv->getInteractionHandler() :
525 Reference<task::XInteractionHandler>());
526 }
527 }
528#endif
529}
530
531void disposeBridges(Reference<css::uno::XComponentContext> const & ctx)
532{
533 if (!ctx.is())
534 return;
535
536 Reference<css::bridge::XBridgeFactory2> bridgeFac( css::bridge::BridgeFactory::create(ctx) );
537
538 const Sequence< Reference<css::bridge::XBridge> >seqBridges = bridgeFac->getExistingBridges();
539 for (const Reference<css::bridge::XBridge>& bridge : seqBridges)
540 {
541 Reference<css::lang::XComponent> comp(bridge, UNO_QUERY);
542 if (comp.is())
543 {
544 try {
545 comp->dispose();
546 }
547 catch ( const css::lang::DisposedException& )
548 {
549 }
550 }
551 }
552}
553
554}
555
556OUString DpResId(TranslateId aId)
557{
558 static std::locale SINGLETON = Translate::Create("dkt");
559 return Translate::get(aId, SINGLETON);
560}
561
562
563/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
void * rtlRandomPool
static PathStatus locateUserInstallation(OUString &_rURL)
Any value
#define SAL_CONFIGFILE(name)
Reference< XOutputStream > stream
OUString DpResId(TranslateId aId)
Definition: dp_misc.cxx:556
float u
#define SAL_INFO(area, stream)
err
std::locale Create(std::string_view aPrefixName, const LanguageTag &rLocale)
OUString get(TranslateId sContextAndId, const std::locale &loc)
size
@ Exception
Reference< XComponentContext > getProcessComponentContext()
OUString makeURL(std::u16string_view baseURL, OUString const &relPath_)
appends a relative path to a url.
Definition: dp_misc.cxx:254
void syncRepositories(bool force, Reference< ucb::XCommandEnvironment > const &xCmdEnv)
Definition: dp_misc.cxx:492
OUString makeRcTerm(OUString const &url)
Definition: dp_misc.cxx:302
OUString expandUnoRcUrl(OUString const &url)
Definition: dp_misc.cxx:316
OUString readConsole()
reads from the console.
Definition: dp_misc.cxx:474
void writeConsoleError(std::u16string_view sText)
writes the argument to the console using the error stream.
Definition: dp_misc.cxx:469
bool office_is_running()
Definition: dp_misc.cxx:332
void TRACE(OUString const &sText)
print the text to the console in a debug build.
Definition: dp_misc.cxx:487
Reference< XInterface > resolveUnoURL(OUString const &connectString, Reference< XComponentContext > const &xLocalContext, AbortChannel const *abortChannel)
Definition: dp_misc.cxx:430
static void writeConsoleWithStream(std::u16string_view sText, FILE *stream)
Definition: dp_misc.cxx:457
OUString generateRandomPipeId()
Definition: dp_misc.cxx:411
OUString expandUnoRcTerm(OUString const &term_)
Definition: dp_misc.cxx:295
void writeConsole(std::u16string_view sText)
writes the argument string to the console.
Definition: dp_misc.cxx:464
oslProcess raiseProcess(OUString const &appURL, Sequence< OUString > const &args)
Definition: dp_misc.cxx:375
void disposeBridges(Reference< css::uno::XComponentContext > const &ctx)
Definition: dp_misc.cxx:531
OUString makeURLAppendSysPathSegment(std::u16string_view baseURL, OUString const &segment)
appends a relative path to a url.
Definition: dp_misc.cxx:284
css::uno::Reference< css::deployment::XPackageRegistry > create(css::uno::Reference< css::deployment::XPackageRegistry > const &xRootRegistry, OUString const &context, OUString const &cachePath, css::uno::Reference< css::uno::XComponentContext > const &xComponentContext)
int i
ctx
constexpr bool starts_with(std::basic_string_view< charT, traits > sv, std::basic_string_view< charT, traits > x) noexcept
args
OString OUStringToOString(std::u16string_view str, ConnectionSettings const *settings)
comp
std::vector< sal_uInt8 > bytes
unsigned char sal_uInt8
sal_uInt16 sal_Unicode
size_t pos