/**
  * Internally undo recorded changes we did so far.
  *
  * @return whether the state required undo actions
  */
 boolean undoChanges() {
   final State state = stateUpdater.getAndSet(this, State.ROLLBACK_ONLY);
   if (state == State.COMPLETED || state == State.ROLLBACK_ONLY) {
     // Was actually completed already
     return false;
   }
   PatchingTaskContext.Mode currentMode = this.mode;
   mode = PatchingTaskContext.Mode.UNDO;
   final PatchContentLoader loader = PatchContentLoader.create(miscBackup, null, null);
   // Undo changes for the identity
   undoChanges(identityEntry, loader);
   // TODO maybe check if we need to do something for the layers too !?
   if (state == State.INVALIDATE || currentMode == PatchingTaskContext.Mode.ROLLBACK) {
     // For apply the state needs to be invalidate
     // For rollback the files are invalidated as part of the tasks
     final PatchingTaskContext.Mode mode =
         currentMode == PatchingTaskContext.Mode.APPLY
             ? PatchingTaskContext.Mode.ROLLBACK
             : PatchingTaskContext.Mode.APPLY;
     for (final File file : moduleInvalidations) {
       try {
         PatchModuleInvalidationUtils.processFile(file, mode);
       } catch (Exception e) {
         PatchLogger.ROOT_LOGGER.debugf(e, "failed to restore state for %s", file);
       }
     }
   }
   return true;
 }
 /**
  * Complete the current operation and persist the current state to the disk. This will also
  * trigger the invalidation of outdated modules.
  *
  * @param modification the current modification
  * @param callback the completion callback
  */
 private void complete(
     final InstallationManager.InstallationModification modification,
     final FinalizeCallback callback) {
   final List<File> processed = new ArrayList<File>();
   try {
     try {
       // Update the state to invalidate and process module resources
       if (stateUpdater.compareAndSet(this, State.PREPARED, State.INVALIDATE)
           && mode == PatchingTaskContext.Mode.APPLY) {
         // Only invalidate modules when applying patches; on rollback files are immediately
         // restored
         for (final File invalidation : moduleInvalidations) {
           processed.add(invalidation);
           PatchModuleInvalidationUtils.processFile(invalidation, mode);
         }
       }
       modification.complete();
       callback.completed(this);
       state = State.COMPLETED;
     } catch (Exception e) {
       this.moduleInvalidations.clear();
       this.moduleInvalidations.addAll(processed);
       throw new RuntimeException(e);
     }
   } finally {
     if (state != State.COMPLETED) {
       try {
         modification.cancel();
       } finally {
         try {
           undoChanges();
         } finally {
           callback.operationCancelled(this);
         }
       }
     } else {
       try {
         if (checkForGarbageOnRestart) {
           final File cleanupMarker =
               new File(installedImage.getInstallationMetadata(), "cleanup-patching-dirs");
           cleanupMarker.createNewFile();
         }
       } catch (IOException e) {
         PatchLogger.ROOT_LOGGER.infof(e, "failed to create cleanup marker");
       }
     }
   }
 }
 /**
  * Undo changes for a single patch entry.
  *
  * @param entry the patch entry
  * @param loader the content loader
  */
 static void undoChanges(final PatchEntry entry, final PatchContentLoader loader) {
   final List<ContentModification> modifications =
       new ArrayList<ContentModification>(entry.rollbackActions);
   for (final ContentModification modification : modifications) {
     final ContentItem item = modification.getItem();
     if (item.getContentType() != ContentType.MISC) {
       // Skip modules and bundles they should be removed as part of the {@link FinalizeCallback}
       continue;
     }
     final PatchingTaskDescription description =
         new PatchingTaskDescription(entry.applyPatchId, modification, loader, false, false);
     try {
       final PatchingTask task = PatchingTask.Factory.create(description, entry);
       task.execute(entry);
     } catch (Exception e) {
       PatchLogger.ROOT_LOGGER.warnf(e, "failed to undo change (%s)", modification);
     }
   }
 }
 /**
  * Update the central directory signature of a .jar.
  *
  * @param file the file to process
  * @param searchPattern the search patter to use
  * @param badSkipBytes the bad bytes skip table
  * @param newSig the new signature
  * @param endSig the expected signature
  * @throws IOException
  */
 private static void updateJar(
     final File file,
     final byte[] searchPattern,
     final int[] badSkipBytes,
     final int newSig,
     final int endSig)
     throws IOException {
   final RandomAccessFile raf = new RandomAccessFile(file, "rw");
   try {
     final FileChannel channel = raf.getChannel();
     try {
       long pos = channel.size() - ENDLEN;
       if (!validateEndRecord(file, channel, pos, endSig)) {
         pos = scanForEndSig(file, channel, searchPattern, badSkipBytes, endSig);
       }
       if (pos == -1) {
         // Don't fail patching if we cannot validate a valid zip
         PatchLogger.ROOT_LOGGER.cannotInvalidateZip(file.getAbsolutePath());
         return;
       }
       // Update the central directory record
       channel.position(pos);
       final ByteBuffer buffer = ByteBuffer.allocate(4);
       buffer.order(ByteOrder.LITTLE_ENDIAN);
       buffer.putInt(newSig);
       buffer.flip();
       while (buffer.hasRemaining()) {
         channel.write(buffer);
       }
     } finally {
       safeClose(channel);
     }
   } finally {
     safeClose(raf);
   }
 }
 /**
  * In case we cannot delete a directory create a marker to recheck whether we can garbage collect
  * some not referenced directories and files.
  *
  * @param file the directory
  */
 protected void failedToCleanupDir(final File file) {
   checkForGarbageOnRestart = true;
   PatchLogger.ROOT_LOGGER.cannotDeleteFile(file.getAbsolutePath());
 }