| /******************************************************************************* |
| * 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(); |
| } |
| } |