blob: ef952b904ac6c8825c2db76e7b72e11030307848 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009, 2018 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
* Ericsson AB - Ongoing development
* Red Hat Inc. - Bug 460967
*******************************************************************************/
package org.eclipse.equinox.internal.p2.tests.verifier;
import java.io.*;
import java.net.URI;
import java.util.*;
import java.util.Map.Entry;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.equinox.app.IApplication;
import org.eclipse.equinox.app.IApplicationContext;
import org.eclipse.equinox.internal.p2.core.helpers.LogHelper;
import org.eclipse.equinox.internal.p2.core.helpers.ServiceHelper;
import org.eclipse.equinox.internal.p2.engine.ProfilePreferences;
import org.eclipse.equinox.internal.p2.ui.sdk.scheduler.migration.MigrationWizard;
import org.eclipse.equinox.p2.core.IProvisioningAgent;
import org.eclipse.equinox.p2.engine.IProfile;
import org.eclipse.equinox.p2.engine.IProfileRegistry;
import org.eclipse.equinox.p2.metadata.IInstallableUnit;
import org.eclipse.equinox.p2.query.IQueryResult;
import org.eclipse.equinox.p2.query.QueryUtil;
import org.eclipse.osgi.report.resolution.ResolutionReport;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.widgets.Display;
import org.osgi.framework.*;
import org.osgi.framework.hooks.resolver.ResolverHook;
import org.osgi.framework.hooks.resolver.ResolverHookFactory;
import org.osgi.framework.namespace.IdentityNamespace;
import org.osgi.framework.wiring.*;
import org.osgi.resource.*;
/**
* Application which verifies an install.
*
* @since 1.0
*/
public class VerifierApplication implements IApplication {
private static final File DEFAULT_PROPERTIES_FILE = new File("verifier.properties"); //$NON-NLS-1$
private static final String ARG_PROPERTIES = "-verifier.properties"; //$NON-NLS-1$
private IProvisioningAgent agent;
private Properties properties = null;
private List<String> ignoreResolved = null;
/*
* Create and return an error status with the given message.
*/
private static IStatus createError(String message) {
return new Status(IStatus.ERROR, Activator.PLUGIN_ID, message);
}
@Override
public Object start(IApplicationContext context) throws Exception {
String[] args = (String[]) context.getArguments().get(IApplicationContext.APPLICATION_ARGS);
processArguments(args);
agent = ServiceHelper.getService(Activator.getBundleContext(), IProvisioningAgent.class);
IStatus result = verify();
if (!result.isOK()) {
// PrintWriter out = new PrintWriter(new FileWriter(new File("c:/tmp/dropins-debug.txt")));
try (PrintWriter out = new PrintWriter(new OutputStreamWriter(System.err))) {
out.println("Error from dropin verifier application: " + result.getMessage()); //$NON-NLS-1$
Throwable t = result.getException();
if (t != null)
t.printStackTrace(out);
}
LogHelper.log(result);
}
return result.isOK() ? IApplication.EXIT_OK : Integer.valueOf(13);
}
/*
* Go through the command-line args and pull out interesting ones
* for later consumption.
*/
private void processArguments(String[] args) {
if (args == null)
return;
for (int i = 1; i < args.length; i++) {
if (ARG_PROPERTIES.equals(args[i - 1])) {
String filename = args[i];
if (filename.startsWith("-")) //$NON-NLS-1$
continue;
try {
properties = readProperties(new File(filename));
} catch (IOException e) {
// TODO
e.printStackTrace();
// fall through to load default
}
continue;
}
}
// problems loading properties file or none specified so look for a default
if (properties == null) {
try {
if (DEFAULT_PROPERTIES_FILE.exists())
properties = readProperties(DEFAULT_PROPERTIES_FILE);
} catch (IOException e) {
// TODO
e.printStackTrace();
}
}
if (properties == null)
properties = new Properties();
}
/*
* Read and return a properties file at the given location.
*/
private Properties readProperties(File file) throws IOException {
Properties result = new Properties();
try (InputStream input = new BufferedInputStream(new FileInputStream(file))) {
result.load(input);
return result;
}
}
@Override
public void stop() {
// nothing to do
}
/*
* Return a boolean value indicating whether or not the bundle with the given symbolic name
* should be considered when looking at bundles which are not resolved in the system.
* TODO the call to this method was removed. we should add it back
*/
protected boolean shouldCheckResolved(String bundle) {
if (ignoreResolved == null) {
ignoreResolved = new ArrayList<>();
String list = properties.getProperty("ignore.unresolved");
if (list == null)
return true;
for (StringTokenizer tokenizer = new StringTokenizer(list, ","); tokenizer.hasMoreTokens();)
ignoreResolved.add(tokenizer.nextToken().trim());
}
for (String string : ignoreResolved) {
if (bundle.equals(string))
return false;
}
return true;
}
/*
* Check to ensure all of the bundles in the system are resolved.
*
* Copied and modified from EclipseStarter#logUnresolvedBundles.
* This method prints out all the reasons while asking the resolver directly
* will only print out the first reason.
*/
private IStatus checkResolved() {
List<IStatus> allProblems = new ArrayList<>();
List<Bundle> unresolved = new ArrayList<>();
for (Bundle b : Activator.getBundleContext().getBundles()) {
BundleRevision revision = b.adapt(BundleRevision.class);
if (revision != null && revision.getWiring() == null) {
unresolved.add(b);
}
}
ResolutionReport report = getResolutionReport(unresolved);
Map<Resource, List<ResolutionReport.Entry>> entries = report.getEntries();
Collection<Resource> unresolvedResources = new HashSet<>(entries.keySet());
Collection<Resource> leafResources = new HashSet<>();
for (Map.Entry<Resource, List<ResolutionReport.Entry>> resourceEntry : entries.entrySet()) {
for (ResolutionReport.Entry reportEntry : resourceEntry.getValue()) {
if (!reportEntry.getType().equals(ResolutionReport.Entry.Type.UNRESOLVED_PROVIDER)) {
leafResources.add(resourceEntry.getKey());
unresolvedResources.remove(resourceEntry.getKey());
}
}
}
// first lets look for missing leaf constraints (bug 114120)
for (Resource leafResource : leafResources) {
BundleRevision revision = (BundleRevision) leafResource;
String message = NLS.bind(EclipseAdaptorMsg.ECLIPSE_STARTUP_ERROR_BUNDLE_NOT_RESOLVED, revision.getBundle().getLocation()) + '\n';
message += report.getResolutionReportMessage(leafResource);
allProblems.add(createError(message));
}
// now report all others
for (Resource unresolvedResource : unresolvedResources) {
BundleRevision revision = (BundleRevision) unresolvedResource;
String message = NLS.bind(EclipseAdaptorMsg.ECLIPSE_STARTUP_ERROR_BUNDLE_NOT_RESOLVED, revision.getBundle().getLocation()) + '\n';
message += report.getResolutionReportMessage(unresolvedResource);
allProblems.add(createError(message));
}
MultiStatus result = new MultiStatus(Activator.PLUGIN_ID, IStatus.OK, "Problems checking resolved bundles.", null); //$NON-NLS-1$
for (IStatus status : allProblems)
result.add(status);
return result;
}
private static ResolutionReport getResolutionReport(Collection<Bundle> bundles) {
BundleContext context = Activator.getBundleContext();
DiagReportListener reportListener = new DiagReportListener(bundles);
ServiceRegistration<ResolverHookFactory> hookReg = context.registerService(ResolverHookFactory.class, reportListener, null);
try {
Bundle systemBundle = context.getBundle(Constants.SYSTEM_BUNDLE_LOCATION);
FrameworkWiring frameworkWiring = systemBundle.adapt(FrameworkWiring.class);
frameworkWiring.resolveBundles(bundles);
return reportListener.getReport();
} finally {
hookReg.unregister();
}
}
private static class DiagReportListener implements ResolverHookFactory {
private final Collection<BundleRevision> targetTriggers = new ArrayList<>();
public DiagReportListener(Collection<Bundle> bundles) {
for (Bundle bundle : bundles) {
BundleRevision revision = bundle.adapt(BundleRevision.class);
if (revision != null && revision.getWiring() == null) {
targetTriggers.add(revision);
}
}
}
volatile ResolutionReport report = null;
class DiagResolverHook implements ResolverHook, ResolutionReport.Listener {
@Override
public void handleResolutionReport(ResolutionReport handleReport) {
DiagReportListener.this.report = handleReport;
}
@Override
public void filterResolvable(Collection<BundleRevision> candidates) {
// nothing
}
@Override
public void filterSingletonCollisions(BundleCapability singleton, Collection<BundleCapability> collisionCandidates) {
// nothing
}
@Override
public void filterMatches(BundleRequirement requirement, Collection<BundleCapability> candidates) {
// nothing
}
@Override
public void end() {
// nothing
}
}
@Override
public ResolverHook begin(Collection<BundleRevision> triggers) {
if (triggers.containsAll(targetTriggers)) {
return new DiagResolverHook();
}
return null;
}
ResolutionReport getReport() {
return report;
}
}
/*
* Ensure we have a profile registry and can access the SELF profile.
*/
private IStatus checkProfileRegistry() {
IProfileRegistry registry = agent.getService(IProfileRegistry.class);
if (registry == null)
return createError("Profile registry service not available."); //$NON-NLS-1$
IProfile profile = registry.getProfile(IProfileRegistry.SELF);
if (profile == null)
return createError("SELF profile not available in profile registry."); //$NON-NLS-1$
if (properties.get("checkPresenceOfVerifier") != null && !Boolean.FALSE.toString().equals(properties.get("checkPresenceOfVerifier"))) {
IQueryResult<IInstallableUnit> results = profile.query(QueryUtil.createIUQuery(Activator.PLUGIN_ID), null);
if (results.isEmpty())
return createError(NLS.bind("IU for {0} not found in SELF profile.", Activator.PLUGIN_ID)); //$NON-NLS-1$
}
return Status.OK_STATUS;
}
/*
* Perform all of the verification checks.
*/
public IStatus verify() {
String message = "Problems occurred during verification."; //$NON-NLS-1$
MultiStatus result = new MultiStatus(Activator.PLUGIN_ID, IStatus.OK, message, null);
// ensure all the bundles are resolved
IStatus temp = checkResolved();
if (!temp.isOK())
result.merge(temp);
// ensure we have a profile registry
temp = checkProfileRegistry();
if (!temp.isOK())
result.merge(temp);
temp = hasProfileFlag();
if (!temp.isOK())
result.merge(temp);
temp = checkAbsenceOfBundles();
if (!temp.isOK())
result.merge(temp);
temp = checkPresenceOfBundles();
if (!temp.isOK())
result.merge(temp);
temp = checkSystemProperties();
if (!temp.isOK())
result.merge(temp);
temp = checkMigrationWizard();
if (!temp.isOK())
result.merge(temp);
assumeMigrated();
handleWizardCancellation();
return result;
}
private void handleWizardCancellation() {
if (properties.getProperty("checkMigration.cancelAnswer") == null)
return;
new Display();
IProfileRegistry reg = agent.getService(IProfileRegistry.class);
IProfile profile = reg.getProfile(IProfileRegistry.SELF);
MigrationWizard wizardPage = new MigrationWizard(profile, Collections.emptyList(), new URI[0], false);
int cancelAnswer = Integer.parseInt(properties.getProperty("checkMigration.cancelAnswer"));
wizardPage.rememberCancellationDecision(cancelAnswer);
// The preferences are saved by:
// org.eclipse.equinox.internal.p2.engine.ProfilePreferences.SaveJob
// This job is scheduled with a delay: saveJob.schedule(SAVE_SCHEDULE_DELAY).
// We'd best wait for that job to complete before existing.
try {
Job.getJobManager().join(ProfilePreferences.PROFILE_SAVE_JOB_FAMILY, null);
} catch (InterruptedException e) {
throw new RuntimeException();
}
}
private IStatus checkSystemProperties() {
final String ABSENT_SYS_PROPERTY = "not.sysprop.";
final String PRESENT_SYS_PROPERTY = "sysprop.";
MultiStatus result = new MultiStatus(Activator.PLUGIN_ID, IStatus.ERROR, "System properties validation", null);
Set<Entry<Object, Object>> entries = properties.entrySet();
for (Entry<Object, Object> entry : entries) {
String key = (String) entry.getKey();
if (key.startsWith(ABSENT_SYS_PROPERTY)) {
String property = key.substring(ABSENT_SYS_PROPERTY.length());
if (System.getProperty(property) != null)
result.add(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Property " + property + " should not be set."));
}
if (key.startsWith(PRESENT_SYS_PROPERTY)) {
String property = key.substring(PRESENT_SYS_PROPERTY.length());
String foundValue = System.getProperty(property);
if (!entry.getValue().equals(foundValue))
result.add(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Property " + property + " should be set to " + entry.getValue() + " and is set to " + foundValue + "."));
}
}
if (result.getChildren().length == 0)
return Status.OK_STATUS;
return result;
}
private IStatus checkAbsenceOfBundles() {
MultiStatus result = new MultiStatus(Activator.PLUGIN_ID, IStatus.ERROR, "Some bundles should not be there", null);
String unexpectedBundlesString = properties.getProperty("unexpectedBundleList");
if (unexpectedBundlesString == null)
return Status.OK_STATUS;
String[] unexpectedBundles = unexpectedBundlesString.split(",");
for (String bsn : unexpectedBundles) {
if (containsBundle(bsn)) {
result.add(new Status(IStatus.ERROR, Activator.PLUGIN_ID, bsn + " should not have been found in the install"));
}
}
if (result.getChildren().length == 0)
return Status.OK_STATUS;
return result;
}
private boolean containsBundle(final String bsn) {
FrameworkWiring fWiring = Activator.getBundleContext().getBundle(Constants.SYSTEM_BUNDLE_LOCATION).adapt(FrameworkWiring.class);
Collection<BundleCapability> existing = fWiring.findProviders(new Requirement() {
@Override
public String getNamespace() {
return IdentityNamespace.IDENTITY_NAMESPACE;
}
@Override
public Map<String, String> getDirectives() {
return Collections.singletonMap(Namespace.REQUIREMENT_FILTER_DIRECTIVE, "(" + IdentityNamespace.IDENTITY_NAMESPACE + "=" + bsn + ")");
}
@Override
public Map<String, Object> getAttributes() {
return Collections.EMPTY_MAP;
}
@Override
public Resource getResource() {
return null;
}
});
return !existing.isEmpty();
}
private IStatus checkPresenceOfBundles() {
MultiStatus result = new MultiStatus(Activator.PLUGIN_ID, IStatus.ERROR, "Some bundles should not be there", null);
String expectedBundlesString = properties.getProperty("expectedBundleList");
if (expectedBundlesString == null)
return Status.OK_STATUS;
String[] expectedBundles = expectedBundlesString.split(",");
for (String bsn : expectedBundles) {
if (!containsBundle(bsn)) {
result.add(new Status(IStatus.ERROR, Activator.PLUGIN_ID, bsn + " is missing from the install"));
}
}
if (result.getChildren().length == 0)
return Status.OK_STATUS;
return result;
}
private IStatus hasProfileFlag() {
if (properties.getProperty("checkProfileResetFlag") == null || "false".equals(properties.getProperty("checkProfileResetFlag")))
return Status.OK_STATUS;
//Make sure that the profile is already loaded
IProfileRegistry reg = agent.getService(IProfileRegistry.class);
IProfile profile = reg.getProfile(IProfileRegistry.SELF);
String profileId = profile.getProfileId();
long history[] = reg.listProfileTimestamps(profileId);
long lastTimestamp = history[history.length - 1];
if (IProfile.STATE_SHARED_INSTALL_VALUE_NEW.equals(reg.getProfileStateProperties(profileId, lastTimestamp).get(IProfile.STATE_PROP_SHARED_INSTALL))) {
return Status.OK_STATUS;
}
if (history.length == 1) {
return new Status(IStatus.ERROR, Activator.PLUGIN_ID, "The flag indicating that a profile has been reset is incorrectly setup");
}
long previousToLastTimestamp = history[history.length - 2];
if (IProfile.STATE_SHARED_INSTALL_VALUE_NEW.equals(reg.getProfileStateProperties(profileId, previousToLastTimestamp).get(IProfile.STATE_PROP_SHARED_INSTALL))) {
return Status.OK_STATUS;
}
return new Status(IStatus.ERROR, Activator.PLUGIN_ID, "The flag indicating that a profile has been reset is incorrectly setup");
}
private IStatus checkMigrationWizard() {
if (properties.getProperty("checkMigrationWizard") == null)
return Status.OK_STATUS;
IProfileRegistry reg = agent.getService(IProfileRegistry.class);
IProfile profile = reg.getProfile(IProfileRegistry.SELF);
//Fake the opening of the wizard
MigrationWizardTestHelper migrationSupport = new MigrationWizardTestHelper();
migrationSupport.performMigration(agent, reg, profile);
boolean wizardExpectedToOpen = Boolean.parseBoolean(properties.getProperty("checkMigrationWizard.open"));
if (migrationSupport.wizardOpened == wizardExpectedToOpen)
return Status.OK_STATUS;
return new Status(IStatus.ERROR, Activator.PLUGIN_ID, "The migration wizard did " + (wizardExpectedToOpen ? "not" : "") + " open");
}
private void assumeMigrated() {
if (properties.getProperty("checkMigrationWizard.simulate.reinstall") == null)
return;
new MigrationWizardTestHelper().rememberMigrationCompleted();
}
}