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