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