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