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