docs: public PatchingResult rollbackPatch(final String patchId, final ContentVerificationPolicy contentPolicy, final boolean rollbackTo, final boolean resetConfiguration, InstallationManager.InstallationModification modification) throws PatchingException {
          if (Constants.BASE.equals(patchId)) {
              throw PatchLogger.ROOT_LOGGER.cannotRollbackPatch(patchId);
          }
          try {
              // Before rolling back the patch, validate that the state until that point is consistent
              validateRollbackState(patchId, modification.getUnmodifiedInstallationState());
          } catch (PatchingException e) {
              throw e;
          } catch (Exception e) {
              throw new PatchingException(e);
          }
          // Figure out what to do
          final List<String> patches = new ArrayList<String>();
          final List<String> oneOffs = modification.getPatchIDs();
          final int index = oneOffs.indexOf(patchId);
  
          if (index == -1) {
              if (patchId.equals(modification.getCumulativePatchID())) {
                  // Rollback all active
                  patches.addAll(oneOffs);
                  patches.add(modification.getCumulativePatchID());
              } else {
                  throw PatchLogger.ROOT_LOGGER.cannotRollbackPatch(patchId);
              }
          } else if (index == 0) {
              patches.add(patchId);
          } else {
              if (rollbackTo) {
                  // rollback one-offs up to the given patchId
                  for (int i = 0; i <= index; i++) {
                      patches.add(oneOffs.get(i));
                  }
              } else {
                  // TODO perhaps we can allow this as well?
                  throw PatchLogger.ROOT_LOGGER.cannotRollbackPatch(patchId);
              }
          }
  
          final File historyDir = installedImage.getPatchHistoryDir(patchId);
          assertExists(historyDir);
          final File rollbackXml = new File(historyDir, Constants.ROLLBACK_XML);
          assertExists(rollbackXml);
          final File workDir = createTempDir();
          final PatchContentProvider provider = PatchContentProvider.ROLLBACK_PROVIDER;
          final IdentityPatchContext context = new IdentityPatchContext(workDir, provider, contentPolicy, modification, ROLLBACK, installedImage);
          try {
              // Rollback patches
              for (final String rollback : patches) {
                  if (!Constants.BASE.equals(rollback)) {
                      rollback(rollback, context);
                      modification.removeInstalledPatch(rollback);
                  }
              }
              // Execute the tasks
              final IdentityPatchContext.PatchEntry identity = context.getIdentityEntry();
              final IdentityRollbackCallback callback = new IdentityRollbackCallback(patchId, patches, resetConfiguration, identity.getDirectoryStructure());
              try {
                  return executeTasks(context, callback);
              } catch (Exception e) {
                  context.cancel(callback);
                  PatchLogger.ROOT_LOGGER.debugf(e, "failed to rollback patch %s", patchId);
                  throw rethrowException(e);
              }
          } finally {
              if (workDir != null && !IoUtils.recursiveDelete(workDir)) {
                  PatchLogger.ROOT_LOGGER.cannotDeleteFile(workDir.getAbsolutePath());
              }
              context.cleanup();
          }
      }