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 <rtl/uri.hxx>
28 #include <rtl/digest.h>
29 #include <rtl/random.h>
30 #include <rtl/bootstrap.hxx>
31 #include <rtl/ustrbuf.hxx>
32 #include <sal/log.hxx>
33 #include <unotools/bootstrap.hxx>
34 #include <osl/file.hxx>
35 #include <osl/pipe.hxx>
36 #include <osl/security.hxx>
37 #include <osl/thread.hxx>
38 #include <com/sun/star/ucb/CommandAbortedException.hpp>
39 #include <com/sun/star/task/XInteractionHandler.hpp>
40 #include <com/sun/star/bridge/BridgeFactory.hpp>
41 #include <com/sun/star/bridge/UnoUrlResolver.hpp>
42 #include <com/sun/star/bridge/XUnoUrlResolver.hpp>
43 #include <com/sun/star/deployment/ExtensionManager.hpp>
44 #include <com/sun/star/lang/DisposedException.hpp>
45 #include <com/sun/star/task/OfficeRestartManager.hpp>
46 #include <memory>
47 #include <string_view>
48 #include <comphelper/lok.hxx>
50 #include <salhelper/linkhelper.hxx>
51 
52 #ifdef _WIN32
53 #include <prewin.h>
54 #include <postwin.h>
55 #endif
56 
57 using namespace ::com::sun::star;
58 using namespace ::com::sun::star::uno;
59 
60 namespace dp_misc {
61 namespace {
62 
63 struct UnoRc : public rtl::StaticWithInit<
64  std::shared_ptr<rtl::Bootstrap>, UnoRc> {
65  std::shared_ptr<rtl::Bootstrap> operator () () {
66  OUString unorc( "$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("louno") );
67  ::rtl::Bootstrap::expandMacros( unorc );
68  auto ret = std::make_shared<::rtl::Bootstrap>( unorc );
69  OSL_ASSERT( ret->getHandle() != nullptr );
70  return ret;
71  }
72 };
73 
74 OUString generateOfficePipeId()
75 {
76  OUString userPath;
77  ::utl::Bootstrap::PathStatus aLocateResult =
79  if (aLocateResult != ::utl::Bootstrap::PATH_EXISTS &&
80  aLocateResult != ::utl::Bootstrap::PATH_VALID)
81  {
82  throw Exception("Extension Manager: Could not obtain path for UserInstallation.", nullptr);
83  }
84 
85  rtlDigest digest = rtl_digest_create( rtl_Digest_AlgorithmMD5 );
86  if (!digest) {
87  throw RuntimeException("cannot get digest rtl_Digest_AlgorithmMD5!", nullptr );
88  }
89 
90  sal_uInt8 const * data =
91  reinterpret_cast<sal_uInt8 const *>(userPath.getStr());
92  std::size_t size = userPath.getLength() * sizeof (sal_Unicode);
93  sal_uInt32 md5_key_len = rtl_digest_queryLength( digest );
94  std::unique_ptr<sal_uInt8[]> md5_buf( new sal_uInt8 [ md5_key_len ] );
95 
96  rtl_digest_init( digest, data, static_cast<sal_uInt32>(size) );
97  rtl_digest_update( digest, data, static_cast<sal_uInt32>(size) );
98  rtl_digest_get( digest, md5_buf.get(), md5_key_len );
99  rtl_digest_destroy( digest );
100 
101  // create hex-value string from the MD5 value to keep
102  // the string size minimal
103  OUStringBuffer buf;
104  buf.append( "SingleOfficeIPC_" );
105  for ( sal_uInt32 i = 0; i < md5_key_len; ++i ) {
106  buf.append( static_cast<sal_Int32>(md5_buf[ i ]), 0x10 );
107  }
108  return buf.makeStringAndClear();
109 }
110 
111 bool existsOfficePipe()
112 {
113  static OUString OfficePipeId = generateOfficePipeId();
114 
115  OUString const & pipeId = OfficePipeId;
116  if (pipeId.isEmpty())
117  return false;
118  ::osl::Security sec;
119  ::osl::Pipe pipe( pipeId, osl_Pipe_OPEN, sec );
120  return pipe.is();
121 }
122 
123 //get modification time
124 bool getModifyTimeTargetFile(const OUString &rFileURL, TimeValue &rTime)
125 {
126  salhelper::LinkResolver aResolver(osl_FileStatus_Mask_ModifyTime);
127 
128  if (aResolver.fetchFileStatus(rFileURL) != osl::FileBase::E_None)
129  return false;
130 
131  rTime = aResolver.m_aStatus.getModifyTime();
132 
133  return true;
134 }
135 
136 //Returns true if the Folder was more recently modified then
137 //the lastsynchronized file. That is the repository needs to
138 //be synchronized.
139 bool compareExtensionFolderWithLastSynchronizedFile(
140  OUString const & folderURL, OUString const & fileURL)
141 {
142  bool bNeedsSync = false;
143  ::osl::DirectoryItem itemExtFolder;
144  ::osl::File::RC err1 =
145  ::osl::DirectoryItem::get(folderURL, itemExtFolder);
146  //If it does not exist, then there is nothing to be done
147  if (err1 == ::osl::File::E_NOENT)
148  {
149  return false;
150  }
151  else if (err1 != ::osl::File::E_None)
152  {
153  OSL_FAIL("Cannot access extension folder");
154  return true; //sync just in case
155  }
156 
157  //If last synchronized does not exist, then OOo is started for the first time
158  ::osl::DirectoryItem itemFile;
159  ::osl::File::RC err2 = ::osl::DirectoryItem::get(fileURL, itemFile);
160  if (err2 == ::osl::File::E_NOENT)
161  {
162  return true;
163 
164  }
165  else if (err2 != ::osl::File::E_None)
166  {
167  OSL_FAIL("Cannot access file lastsynchronized");
168  return true; //sync just in case
169  }
170 
171  //compare the modification time of the extension folder and the last
172  //modified file
173  TimeValue timeFolder;
174  if (getModifyTimeTargetFile(folderURL, timeFolder))
175  {
176  TimeValue timeFile;
177  if (getModifyTimeTargetFile(fileURL, timeFile))
178  {
179  if (timeFile.Seconds < timeFolder.Seconds)
180  bNeedsSync = true;
181  }
182  else
183  {
184  OSL_ASSERT(false);
185  bNeedsSync = true;
186  }
187  }
188  else
189  {
190  OSL_ASSERT(false);
191  bNeedsSync = true;
192  }
193 
194  return bNeedsSync;
195 }
196 
197 bool needToSyncRepository(std::u16string_view name)
198 {
199  OUString folder;
200  OUString file;
201  if ( name == u"bundled" )
202  {
203  folder = "$BUNDLED_EXTENSIONS";
204  file = "$BUNDLED_EXTENSIONS_USER/lastsynchronized";
205  }
206  else if ( name == u"shared" )
207  {
208  folder = "$UNO_SHARED_PACKAGES_CACHE/uno_packages";
209  file = "$SHARED_EXTENSIONS_USER/lastsynchronized";
210  }
211  else
212  {
213  OSL_ASSERT(false);
214  return true;
215  }
216  ::rtl::Bootstrap::expandMacros(folder);
217  ::rtl::Bootstrap::expandMacros(file);
218  return compareExtensionFolderWithLastSynchronizedFile(
219  folder, file);
220 }
221 
222 
223 } // anon namespace
224 
225 
226 namespace {
227 OUString encodeForRcFile( OUString const & str )
228 {
229  // escape $\{} (=> rtl bootstrap files)
230  OUStringBuffer buf(64);
231  sal_Int32 pos = 0;
232  const sal_Int32 len = str.getLength();
233  for ( ; pos < len; ++pos ) {
234  sal_Unicode c = str[ pos ];
235  switch (c) {
236  case '$':
237  case '\\':
238  case '{':
239  case '}':
240  buf.append( '\\' );
241  break;
242  }
243  buf.append( c );
244  }
245  return buf.makeStringAndClear();
246 }
247 }
248 
249 
250 OUString makeURL( OUString const & baseURL, OUString const & relPath_ )
251 {
252  OUStringBuffer buf(128);
253  if (baseURL.getLength() > 1 && baseURL[ baseURL.getLength() - 1 ] == '/')
254  buf.append( baseURL.subView(0, baseURL.getLength() - 1) );
255  else
256  buf.append( baseURL );
257  OUString relPath(relPath_);
258  if( relPath.startsWith("/") )
259  relPath = relPath.copy( 1 );
260  if (!relPath.isEmpty())
261  {
262  buf.append( '/' );
263  if (baseURL.match( "vnd.sun.star.expand:" )) {
264  // encode for macro expansion: relPath is supposed to have no
265  // macros, so encode $, {} \ (bootstrap mimic)
266  relPath = encodeForRcFile(relPath);
267 
268  // encode once more for vnd.sun.star.expand schema:
269  // vnd.sun.star.expand:$UNO_...
270  // will expand to file-url
271  relPath = ::rtl::Uri::encode( relPath, rtl_UriCharClassUric,
272  rtl_UriEncodeIgnoreEscapes,
273  RTL_TEXTENCODING_UTF8 );
274  }
275  buf.append( relPath );
276  }
277  return buf.makeStringAndClear();
278 }
279 
280 OUString makeURLAppendSysPathSegment( OUString const & baseURL, OUString const & segment )
281 {
282  OSL_ASSERT(segment.indexOf(u'/') == -1);
283 
284  ::rtl::Uri::encode(
285  segment, rtl_UriCharClassPchar, rtl_UriEncodeIgnoreEscapes,
286  RTL_TEXTENCODING_UTF8);
287  return makeURL(baseURL, segment);
288 }
289 
290 
291 OUString expandUnoRcTerm( OUString const & term_ )
292 {
293  OUString term(term_);
294  UnoRc::get()->expandMacrosFrom( term );
295  return term;
296 }
297 
298 OUString makeRcTerm( OUString const & url )
299 {
300  OSL_ASSERT( url.match( "vnd.sun.star.expand:" ));
301  if (url.match( "vnd.sun.star.expand:" )) {
302  // cut protocol:
303  OUString rcterm( url.copy( sizeof ("vnd.sun.star.expand:") - 1 ) );
304  // decode uric class chars:
305  rcterm = ::rtl::Uri::decode(
306  rcterm, rtl_UriDecodeWithCharset, RTL_TEXTENCODING_UTF8 );
307  return rcterm;
308  }
309  else
310  return url;
311 }
312 
313 
314 OUString expandUnoRcUrl( OUString const & url )
315 {
316  if (url.match( "vnd.sun.star.expand:" )) {
317  // cut protocol:
318  OUString rcurl( url.copy( sizeof ("vnd.sun.star.expand:") - 1 ) );
319  // decode uric class chars:
320  rcurl = ::rtl::Uri::decode(
321  rcurl, rtl_UriDecodeWithCharset, RTL_TEXTENCODING_UTF8 );
322  // expand macro string:
323  UnoRc::get()->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 
375 oslProcess 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, SAL_N_ELEMENTS(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 
430 Reference<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  ::osl::Thread::wait( std::chrono::milliseconds(500) );
450  }
451  else throw;
452  }
453  }
454  return nullptr; // warning C4715
455 }
456 
457 static 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 
464 void writeConsole(std::u16string_view sText)
465 {
466  writeConsoleWithStream(sText, stdout);
467 }
468 
469 void writeConsoleError(std::u16string_view sText)
470 {
471  writeConsoleWithStream(sText, stderr);
472 }
473 
474 OUString 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 
487 void 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  if (bModified && !comphelper::LibreOfficeKit::isActive())
518  {
519  Reference<task::XRestartManager> restarter(task::OfficeRestartManager::get(comphelper::getProcessComponentContext()));
520  if (restarter.is())
521  {
522  restarter->requestRestart(xCmdEnv.is() ? xCmdEnv->getInteractionHandler() :
523  Reference<task::XInteractionHandler>());
524  }
525  }
526 #endif
527 }
528 
529 void disposeBridges(Reference<css::uno::XComponentContext> const & ctx)
530 {
531  if (!ctx.is())
532  return;
533 
534  Reference<css::bridge::XBridgeFactory2> bridgeFac( css::bridge::BridgeFactory::create(ctx) );
535 
536  const Sequence< Reference<css::bridge::XBridge> >seqBridges = bridgeFac->getExistingBridges();
537  for (const Reference<css::bridge::XBridge>& bridge : seqBridges)
538  {
539  Reference<css::lang::XComponent> comp(bridge, UNO_QUERY);
540  if (comp.is())
541  {
542  try {
543  comp->dispose();
544  }
545  catch ( const css::lang::DisposedException& )
546  {
547  }
548  }
549  }
550 }
551 
552 }
553 
554 OUString DpResId(TranslateId aId)
555 {
556  static std::locale SINGLETON = Translate::Create("dkt");
557  return Translate::get(aId, SINGLETON);
558 }
559 
560 
561 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
static PathStatus locateUserInstallation(OUString &_rURL)
OUString generateRandomPipeId()
Definition: dp_misc.cxx:411
std::vector< sal_uInt8 > bytes
OUString expandUnoRcTerm(OUString const &term_)
Definition: dp_misc.cxx:291
bool office_is_running()
Definition: dp_misc.cxx:332
void syncRepositories(bool force, Reference< ucb::XCommandEnvironment > const &xCmdEnv)
Definition: dp_misc.cxx:492
OUString makeURL(OUString const &baseURL, OUString const &relPath_)
appends a relative path to a url.
Definition: dp_misc.cxx:250
OUString makeURLAppendSysPathSegment(OUString const &baseURL, OUString const &segment)
appends a relative path to a url.
Definition: dp_misc.cxx:280
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:430
sal_uInt16 sal_Unicode
size_t pos
OString OUStringToOString(std::u16string_view str, ConnectionSettings const *settings)
tuple comp
#define SAL_N_ELEMENTS(arr)
std::locale Create(std::string_view aPrefixName, const LanguageTag &rLocale)
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:469
int i
void writeConsole(std::u16string_view sText)
writes the argument string to the console.
Definition: dp_misc.cxx:464
float u
oslProcess raiseProcess(OUString const &appURL, Sequence< OUString > const &args)
Definition: dp_misc.cxx:375
OUString get(TranslateId sContextAndId, const std::locale &loc)
size
OUString makeRcTerm(OUString const &url)
Definition: dp_misc.cxx:298
exports com.sun.star.chart2. data
OUString expandUnoRcUrl(OUString const &url)
Definition: dp_misc.cxx:314
void * rtlRandomPool
unsigned char byte
void TRACE(OUString const &sText)
print the text to the console in a debug build.
Definition: dp_misc.cxx:487
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:457
void disposeBridges(Reference< css::uno::XComponentContext > const &ctx)
Definition: dp_misc.cxx:529
Reference< XComponentContext > getProcessComponentContext()
Any value
OUString readConsole()
reads from the console.
Definition: dp_misc.cxx:474
OUString DpResId(TranslateId aId)
Definition: dp_misc.cxx:554