1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
22 #include "unopkg_main.h"
23 #include "unopkg_shared.h"
24 #include <dp_identifier.hxx>
25 #include <tools/extendapplicationenvironment.hxx>
26 #include <rtl/bootstrap.hxx>
27 #include <rtl/textenc.h>
28 #include <rtl/ustring.hxx>
29 #include <osl/process.h>
30 #include <osl/conditn.hxx>
31 #include <unotools/tempfile.hxx>
32 #include <cppuhelper/implbase.hxx>
33 #include <cppuhelper/exc_hlp.hxx>
34 #include <comphelper/anytostring.hxx>
35 #include <comphelper/logging.hxx>
36 #include <comphelper/sequence.hxx>
37 #include <com/sun/star/deployment/DeploymentException.hpp>
38 #include <com/sun/star/deployment/ExtensionManager.hpp>
40 #include <com/sun/star/deployment/ui/PackageManagerDialog.hpp>
41 #include <com/sun/star/lang/IllegalArgumentException.hpp>
42 #include <com/sun/star/logging/ConsoleHandler.hpp>
43 #include <com/sun/star/logging/FileHandler.hpp>
44 #include <com/sun/star/logging/LogLevel.hpp>
45 #include <com/sun/star/logging/SimpleTextFormatter.hpp>
46 #include <com/sun/star/logging/XLogger.hpp>
47 #include <com/sun/star/ucb/CommandAbortedException.hpp>
48 #include <com/sun/star/ucb/CommandFailedException.hpp>
49 #include <com/sun/star/ui/dialogs/XDialogClosedListener.hpp>
58 using namespace ::com::sun::star
;
59 using namespace ::com::sun::star::logging
;
60 using namespace ::com::sun::star::uno
;
61 using namespace ::unopkg
;
68 explicit ExtensionName( OUString str
) : m_str(std::move( str
)) {}
69 bool operator () ( Reference
<deployment::XPackage
> const & e
) const
71 return m_str
== dp_misc::getIdentifier(e
)
72 || m_str
== e
->getName();
77 const char16_t s_usingText
[] =
79 "using: " APP_NAME
" add <options> extension-path...\n"
80 " " APP_NAME
" validate <options> extension-identifier...\n"
81 " " APP_NAME
" remove <options> extension-identifier...\n"
82 " " APP_NAME
" list <options> extension-identifier...\n"
83 " " APP_NAME
" reinstall <options>\n"
89 " add add extension\n"
90 " validate checks the prerequisites of an installed extension and\n"
91 " registers it if possible\n"
92 " remove remove extensions by identifier\n"
93 " reinstall expert feature: reinstall all deployed extensions\n"
94 " list list information about deployed extensions\n"
95 " gui raise Extensions dialog\n"
98 " -h, --help this help\n"
99 " -V, --version version information\n"
100 " -v, --verbose verbose output\n"
101 " -f, --force force overwriting existing extensions\n"
102 " -s, --suppress-license prevents showing the license\n"
103 " --log-file <file> custom log file; default: <cache-dir>/log.txt\n"
104 " --shared expert feature: operate on shared installation\n"
105 " deployment context;\n"
106 " run only when no concurrent Office\n"
107 " process(es) are running!\n"
108 " --bundled expert feature: operate on bundled extensions. Only\n"
109 " works with list, validate, reinstall;\n"
110 " --deployment-context expert feature: explicit deployment context\n"
113 "To learn more about extensions, see:\n"
114 "https://wiki.documentfoundation.org/Documentation/DevGuide/Extensions\n\n";
117 const OptionInfo s_option_infos
[] = {
118 { RTL_CONSTASCII_STRINGPARAM("help"), 'h', false },
119 { RTL_CONSTASCII_STRINGPARAM("version"), 'V', false },
120 { RTL_CONSTASCII_STRINGPARAM("verbose"), 'v', false },
121 { RTL_CONSTASCII_STRINGPARAM("force"), 'f', false },
122 { RTL_CONSTASCII_STRINGPARAM("log-file"), '\0', true },
123 { RTL_CONSTASCII_STRINGPARAM("shared"), '\0', false },
124 { RTL_CONSTASCII_STRINGPARAM("deployment-context"), '\0', true },
125 { RTL_CONSTASCII_STRINGPARAM("bundled"), '\0', false},
126 { RTL_CONSTASCII_STRINGPARAM("suppress-license"), 's', false},
128 { nullptr, 0, '\0', false }
132 comphelper::EventLogger
const * logger
, sal_Int32 level
, OUString
const & message
,
133 OUString
const & argument
)
135 if (logger
== nullptr) {
136 // Best effort; potentially loses data due to conversion failures (stray surrogate halves)
137 // and embedded null characters:
139 << OUStringToOString(message
.replaceFirst("$1$", argument
), RTL_TEXTENCODING_UTF8
)
142 logger
->log(level
, message
, argument
);
146 class DialogClosedListenerImpl
:
147 public ::cppu::WeakImplHelper
< ui::dialogs::XDialogClosedListener
>
149 osl::Condition
& m_rDialogClosedCondition
;
152 explicit DialogClosedListenerImpl( osl::Condition
& rDialogClosedCondition
)
153 : m_rDialogClosedCondition( rDialogClosedCondition
) {}
155 // XEventListener (base of XDialogClosedListener)
156 virtual void SAL_CALL
disposing( lang::EventObject
const & Source
) override
;
158 // XDialogClosedListener
159 virtual void SAL_CALL
dialogClosed(
160 ui::dialogs::DialogClosedEvent
const & aEvent
) override
;
163 // XEventListener (base of XDialogClosedListener)
164 void DialogClosedListenerImpl::disposing( lang::EventObject
const & )
169 // XDialogClosedListener
170 void DialogClosedListenerImpl::dialogClosed(
171 ui::dialogs::DialogClosedEvent
const & )
173 m_rDialogClosedCondition
.set();
176 // If a package had been installed with a pre OOo 2.2, it could not normally be
177 // found via its identifier; similarly (and for ease of use), a package
178 // installed with OOo 2.2 or later could not normally be found via its file
180 Reference
<deployment::XPackage
> findPackage(
181 OUString
const & repository
,
182 Reference
<deployment::XExtensionManager
> const & manager
,
183 Reference
<ucb::XCommandEnvironment
> const & environment
,
184 std::u16string_view idOrFileName
)
186 const Sequence
< Reference
<deployment::XPackage
> > ps(
187 manager
->getDeployedExtensions(repository
,
188 Reference
<task::XAbortChannel
>(), environment
) );
189 for ( auto const & package
: ps
)
190 if ( dp_misc::getIdentifier( package
) == idOrFileName
)
192 for ( auto const & package
: ps
)
193 if ( package
->getName() == idOrFileName
)
195 return Reference
<deployment::XPackage
>();
200 extern "C" int unopkg_main()
202 tools::extendApplicationEnvironment();
203 bool bShowFailedMsg
= true;
205 bool option_shared
= false;
206 bool option_force
= false;
207 bool option_verbose
= false;
208 bool option_bundled
= false;
209 bool option_suppressLicense
= false;
210 bool option_help
= false;
211 bool subcmd_gui
= false;
215 std::vector
<OUString
> cmdPackages
;
216 Reference
<XLogHandler
> xFileHandler
;
217 Reference
<XLogHandler
> xConsoleHandler
;
218 std::unique_ptr
<comphelper::EventLogger
> logger
;
219 std::unique_ptr
<utl::TempFileNamed
> pUserProfileTempDir
;
221 OptionInfo
const * info_shared
= getOptionInfo(
222 s_option_infos
, "shared" );
223 OptionInfo
const * info_force
= getOptionInfo(
224 s_option_infos
, "force" );
225 OptionInfo
const * info_verbose
= getOptionInfo(
226 s_option_infos
, "verbose" );
227 OptionInfo
const * info_log
= getOptionInfo(
228 s_option_infos
, "log-file" );
229 OptionInfo
const * info_context
= getOptionInfo(
230 s_option_infos
, "deployment-context" );
231 OptionInfo
const * info_help
= getOptionInfo(
232 s_option_infos
, "help" );
233 OptionInfo
const * info_version
= getOptionInfo(
234 s_option_infos
, "version" );
235 OptionInfo
const * info_bundled
= getOptionInfo(
236 s_option_infos
, "bundled" );
237 OptionInfo
const * info_suppressLicense
= getOptionInfo(
238 s_option_infos
, "suppress-license" );
241 Reference
<XComponentContext
> xComponentContext
;
242 Reference
<XComponentContext
> xLocalComponentContext
;
246 sal_uInt32 nCount
= osl_getCommandArgCount();
247 if (nCount
== 0 || isOption( info_help
, &nPos
))
249 dp_misc::writeConsole(s_usingText
);
252 else if (isOption( info_version
, &nPos
)) {
253 dp_misc::writeConsole(u
"\n" APP_NAME
" Version 3.3\n");
256 //consume all bootstrap variables which may occur before the sub-command
257 while(isBootstrapVariable(&nPos
))
262 //get the sub-command
263 osl_getCommandArg( nPos
, &subCommand
.pData
);
265 subCommand
= subCommand
.trim();
266 bool subcmd_add
= subCommand
== "add";
267 subcmd_gui
= subCommand
== "gui";
269 // sub-command options and packages:
270 while (nPos
< nCount
)
272 if (readArgument( &cmdArg
, info_log
, &nPos
)) {
273 logFile
= makeAbsoluteFileUrl(
274 cmdArg
.trim(), getProcessWorkingDir() );
276 else if (!readOption( &option_verbose
, info_verbose
, &nPos
) &&
277 !readOption( &option_shared
, info_shared
, &nPos
) &&
278 !readOption( &option_force
, info_force
, &nPos
) &&
279 !readOption( &option_bundled
, info_bundled
, &nPos
) &&
280 !readOption( &option_suppressLicense
, info_suppressLicense
, &nPos
) &&
281 !readOption( &option_help
, info_help
, &nPos
) &&
282 !readArgument( &repository
, info_context
, &nPos
) &&
283 !isBootstrapVariable(&nPos
))
285 osl_getCommandArg( nPos
, &cmdArg
.pData
);
287 cmdArg
= cmdArg
.trim();
288 if (!cmdArg
.isEmpty())
290 if (cmdArg
[ 0 ] == '-')
293 dp_misc::writeConsoleError(Concat2View(
294 "\nERROR: unexpected option " +
296 "!\n Use " APP_NAME
" " +
297 toString(info_help
) +
298 " to print all options.\n"));
304 cmdPackages
.push_back(
305 subcmd_add
|| subcmd_gui
306 ? makeAbsoluteFileUrl(
307 cmdArg
, getProcessWorkingDir() )
314 // tdf#129917 Use temp user profile when installing shared extensions
317 pUserProfileTempDir
.reset(new utl::TempFileNamed(nullptr, true));
318 pUserProfileTempDir
->EnableKillingFile();
321 xComponentContext
= getUNO(option_verbose
, subcmd_gui
,
322 pUserProfileTempDir
? pUserProfileTempDir
->GetURL() : "",
323 xLocalComponentContext
);
325 // Initialize logging. This will log errors to the console and
326 // also to file if the --log-file parameter was provided.
327 logger
.reset(new comphelper::EventLogger(xLocalComponentContext
, "unopkg"));
328 const Reference
<XLogger
> xLogger(logger
->getLogger());
329 xLogger
->setLevel(LogLevel::WARNING
);
330 Reference
<XLogFormatter
> xLogFormatter(SimpleTextFormatter::create(xLocalComponentContext
));
331 Sequence
< beans::NamedValue
> aSeq
{ { "Formatter", Any(xLogFormatter
) } };
333 xConsoleHandler
.set(ConsoleHandler::createWithSettings(xLocalComponentContext
, aSeq
));
334 xLogger
->addLogHandler(xConsoleHandler
);
335 xConsoleHandler
->setLevel(LogLevel::WARNING
);
336 xLogger
->setLevel(LogLevel::WARNING
);
339 if (!logFile
.isEmpty())
341 Sequence
< beans::NamedValue
> aSeq2
{ { "Formatter", Any(xLogFormatter
) }, {"FileURL", Any(logFile
)} };
342 xFileHandler
.set(css::logging::FileHandler::createWithSettings(xLocalComponentContext
, aSeq2
));
343 xFileHandler
->setLevel(LogLevel::WARNING
);
344 xLogger
->addLogHandler(xFileHandler
);
349 xLogger
->setLevel(LogLevel::INFO
);
350 xConsoleHandler
->setLevel(LogLevel::INFO
);
351 if (xFileHandler
.is())
352 xFileHandler
->setLevel(LogLevel::INFO
);
355 if (repository
.isEmpty())
358 repository
= "shared";
359 else if (option_bundled
)
360 repository
= "bundled";
366 if ( repository
== "shared" ) {
367 option_shared
= true;
369 else if (option_shared
)
371 logger
->log(LogLevel::WARNING
, "Explicit context given! Ignoring option '$1$'", toString(info_shared
));
375 if ( geteuid() == 0 )
377 if ( !(option_shared
|| option_bundled
|| option_help
) )
379 logger
->log(LogLevel::SEVERE
, "Cannot run $1$ as root without $2$ or $3$ option.",
380 APP_NAME
, toString(info_shared
), toString(info_bundled
));
386 if (subCommand
== "reinstall")
388 //We must prevent that services and types are loaded by UNO,
389 //otherwise we cannot delete the registry data folder.
390 OUString extensionUnorc
;
391 if (repository
== "user")
392 extensionUnorc
= "$UNO_USER_PACKAGES_CACHE/registry/com.sun.star.comp.deployment.component.PackageRegistryBackend/unorc";
393 else if (repository
== "shared")
394 extensionUnorc
= "$SHARED_EXTENSIONS_USER/registry/com.sun.star.comp.deployment.component.PackageRegistryBackend/unorc";
395 else if (repository
== "bundled")
396 extensionUnorc
= "$BUNDLED_EXTENSIONS_USER/registry/com.sun.star.comp.deployment.component.PackageRegistryBackend/unorc";
400 ::rtl::Bootstrap::expandMacros(extensionUnorc
);
401 oslFileError e
= osl_removeFile(extensionUnorc
.pData
);
402 if (e
!= osl_File_E_None
&& e
!= osl_File_E_NOENT
)
403 throw Exception("Could not delete " + extensionUnorc
, nullptr);
406 Reference
<deployment::XExtensionManager
> xExtensionManager(
407 deployment::ExtensionManager::get( xComponentContext
) );
409 Reference
<css::ucb::XCommandEnvironment
> xCmdEnv(
410 createCmdEnv(xComponentContext
, option_force
, option_verbose
, option_suppressLicense
));
412 //synchronize bundled/shared extensions
413 //Do not synchronize when command is "reinstall". This could add types and services to UNO and
414 //prevent the deletion of the registry data folder
415 //syncing is done in XExtensionManager.reinstall
416 if (!subcmd_gui
&& subCommand
!= "reinstall"
417 && ! dp_misc::office_is_running())
418 dp_misc::syncRepositories(false, xCmdEnv
);
420 if ( subcmd_add
|| subCommand
== "remove" )
422 for (const OUString
& cmdPackage
: cmdPackages
)
426 beans::NamedValue
nvSuppress(
427 "SUPPRESS_LICENSE", option_suppressLicense
?
428 Any(OUString("1")):Any(OUString("0")));
429 xExtensionManager
->addExtension(
430 cmdPackage
, Sequence
<beans::NamedValue
>(&nvSuppress
, 1),
431 repository
, Reference
<task::XAbortChannel
>(), xCmdEnv
);
437 xExtensionManager
->removeExtension(
438 cmdPackage
, cmdPackage
, repository
,
439 Reference
<task::XAbortChannel
>(), xCmdEnv
);
441 catch (const lang::IllegalArgumentException
&)
443 Reference
<deployment::XPackage
> p(
444 findPackage(repository
,
445 xExtensionManager
, xCmdEnv
, cmdPackage
) );
449 xExtensionManager
->removeExtension(
450 ::dp_misc::getIdentifier(p
), p
->getName(),
452 Reference
<task::XAbortChannel
>(), xCmdEnv
);
457 else if ( subCommand
== "reinstall" )
459 xExtensionManager
->reinstallDeployedExtensions(
460 false, repository
, Reference
<task::XAbortChannel
>(), xCmdEnv
);
462 else if ( subCommand
== "list" )
464 std::vector
<Reference
<deployment::XPackage
> > vecExtUnaccepted
;
465 ::comphelper::sequenceToContainer(vecExtUnaccepted
,
466 xExtensionManager
->getExtensionsWithUnacceptedLicenses(
467 repository
, xCmdEnv
));
469 //This vector tells what XPackage in allExtensions has an
470 //unaccepted license.
471 std::vector
<bool> vecUnaccepted
;
472 std::vector
<Reference
<deployment::XPackage
> > allExtensions
;
473 if (cmdPackages
.empty())
475 Sequence
< Reference
<deployment::XPackage
> >
476 packages
= xExtensionManager
->getDeployedExtensions(
477 repository
, Reference
<task::XAbortChannel
>(), xCmdEnv
);
479 std::vector
<Reference
<deployment::XPackage
> > vec_packages
;
480 ::comphelper::sequenceToContainer(vec_packages
, packages
);
482 //First copy the extensions with the unaccepted license
483 //to vector allExtensions.
484 allExtensions
.resize(vecExtUnaccepted
.size() + vec_packages
.size());
486 std::vector
<Reference
<deployment::XPackage
> >::iterator i_all_ext
=
487 std::copy(vecExtUnaccepted
.begin(), vecExtUnaccepted
.end(),
488 allExtensions
.begin());
489 //Now copy those we got from getDeployedExtensions
490 std::copy(vec_packages
.begin(), vec_packages
.end(), i_all_ext
);
492 //Now prepare the vector which tells what extension has an
494 vecUnaccepted
.resize(vecExtUnaccepted
.size() + vec_packages
.size());
495 std::fill_n(vecUnaccepted
.begin(), vecExtUnaccepted
.size(), true);
496 std::fill_n(vecUnaccepted
.begin() + vecExtUnaccepted
.size(),
497 vec_packages
.size(), false);
499 dp_misc::writeConsole(
500 Concat2View("All deployed " + repository
+ " extensions:\n\n"));
504 //The user provided the names (ids or file names) of the extensions
505 //which shall be listed
506 for (const OUString
& cmdPackage
: cmdPackages
)
508 Reference
<deployment::XPackage
> extension
;
511 extension
= xExtensionManager
->getDeployedExtension(
512 repository
, cmdPackage
, cmdPackage
, xCmdEnv
);
514 catch (const lang::IllegalArgumentException
&)
516 extension
= findPackage(repository
,
517 xExtensionManager
, xCmdEnv
, cmdPackage
);
520 //Now look if the requested extension has an unaccepted license
521 bool bUnacceptedLic
= false;
524 std::vector
<Reference
<deployment::XPackage
> >::const_iterator
526 vecExtUnaccepted
.begin(),
527 vecExtUnaccepted
.end(), ExtensionName(cmdPackage
));
528 if (i
!= vecExtUnaccepted
.end())
531 bUnacceptedLic
= true;
536 throw lang::IllegalArgumentException(
537 "There is no such extension deployed: " +
538 cmdPackage
,nullptr,-1);
539 allExtensions
.push_back(extension
);
540 vecUnaccepted
.push_back(bUnacceptedLic
);
545 printf_packages(allExtensions
, vecUnaccepted
, xCmdEnv
);
547 else if ( subCommand
== "validate" )
549 std::vector
<Reference
<deployment::XPackage
> > vecExtUnaccepted
;
550 ::comphelper::sequenceToContainer(
551 vecExtUnaccepted
, xExtensionManager
->getExtensionsWithUnacceptedLicenses(
552 repository
, xCmdEnv
));
554 for (const OUString
& cmdPackage
: cmdPackages
)
556 Reference
<deployment::XPackage
> extension
;
559 extension
= xExtensionManager
->getDeployedExtension(
560 repository
, cmdPackage
, cmdPackage
, xCmdEnv
);
562 catch (const lang::IllegalArgumentException
&)
564 extension
= findPackage(
565 repository
, xExtensionManager
, xCmdEnv
, cmdPackage
);
570 std::vector
<Reference
<deployment::XPackage
> >::const_iterator
572 vecExtUnaccepted
.begin(),
573 vecExtUnaccepted
.end(), ExtensionName(cmdPackage
));
574 if (i
!= vecExtUnaccepted
.end())
581 xExtensionManager
->checkPrerequisitesAndEnable(
582 extension
, Reference
<task::XAbortChannel
>(), xCmdEnv
);
585 else if ( subCommand
== "gui" )
587 Reference
<ui::dialogs::XAsynchronousExecutableDialog
> xDialog(
588 deployment::ui::PackageManagerDialog::createAndInstall(
590 !cmdPackages
.empty() ? cmdPackages
[0] : OUString() ));
592 osl::Condition dialogEnded
;
595 Reference
< ui::dialogs::XDialogClosedListener
> xListener(
596 new DialogClosedListenerImpl( dialogEnded
) );
598 xDialog
->startExecuteModal(xListener
);
604 logger
->log(LogLevel::SEVERE
,
605 "Unknown sub-command: '$1$'. Use $2$ $3$ to print all options.",
606 subCommand
, APP_NAME
, toString(info_help
));
610 logger
->log(LogLevel::INFO
, "$1$ done.", APP_NAME
);
611 //Force to release all bridges which connect us to the child processes
612 dp_misc::disposeBridges(xLocalComponentContext
);
613 css::uno::Reference
<css::lang::XComponent
>(
614 xLocalComponentContext
, css::uno::UNO_QUERY_THROW
)->dispose();
617 catch (const ucb::CommandFailedException
&e
)
619 logFatal(logger
.get(), LogLevel::SEVERE
, "Exception occurred: $1$", e
.Message
);
621 catch (const ucb::CommandAbortedException
&)
623 logFatal(logger
.get(), LogLevel::SEVERE
, "$1$ aborted.", APP_NAME
);
624 bShowFailedMsg
= false;
626 catch (const deployment::DeploymentException
& exc
)
628 logFatal(logger
.get(), LogLevel::SEVERE
, "Exception occurred: $1$", exc
.Message
);
630 logger
.get(), LogLevel::INFO
, " Cause: $1$", comphelper::anyToString(exc
.Cause
));
632 catch (const LockFileException
& e
)
634 // No logger since it requires UNO which we don't have here
635 dp_misc::writeConsoleError(Concat2View(e
.Message
+ "\n"));
636 bShowFailedMsg
= false;
638 catch (const css::uno::Exception
& e
) {
639 Any
exc( ::cppu::getCaughtException() );
641 logFatal(logger
.get(), LogLevel::SEVERE
, "Exception occurred: $1$", e
.Message
);
642 logFatal(logger
.get(), LogLevel::INFO
, " Cause: $1$", comphelper::anyToString(exc
));
645 logFatal(logger
.get(), LogLevel::SEVERE
, "$1$ failed.", APP_NAME
);
646 dp_misc::disposeBridges(xLocalComponentContext
);
647 if (xLocalComponentContext
.is()) {
648 css::uno::Reference
<css::lang::XComponent
>(
649 xLocalComponentContext
, css::uno::UNO_QUERY_THROW
)->dispose();
655 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */