From c13fdc044d188d2266b2a96c4d1803b014a00633 Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Tue, 3 Mar 2026 19:09:03 +0000 Subject: [PATCH] 8378877: jpackage: improve rebranding of exe files on Windows Reviewed-by: almatvee --- .../internal/ExecutableRebrander.java | 142 +++++++++++------- .../jpackage/internal/WindowsDefender.java | 72 --------- .../jpackage/internal/WindowsRegistry.java | 130 ---------------- .../resources/WinResources.properties | 1 - 4 files changed, 87 insertions(+), 258 deletions(-) delete mode 100644 src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WindowsDefender.java delete mode 100644 src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WindowsRegistry.java diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/ExecutableRebrander.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/ExecutableRebrander.java index 05b87e6f449..ec2a96d4c7d 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/ExecutableRebrander.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/ExecutableRebrander.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -40,9 +40,11 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Properties; +import java.util.concurrent.TimeUnit; import java.util.function.Function; import java.util.stream.Stream; import jdk.jpackage.internal.model.DottedVersion; +import jdk.jpackage.internal.model.JPackageException; import jdk.jpackage.internal.model.WinApplication; import jdk.jpackage.internal.model.WinExePackage; import jdk.jpackage.internal.model.WinLauncher; @@ -74,11 +76,11 @@ final class ExecutableRebrander { this.props = new HashMap<>(); - validateValueAndPut(this.props, Map.entry("COMPANY_NAME", props.vendor), "vendor"); - validateValueAndPut(this.props, Map.entry("FILE_DESCRIPTION",props.description), "description"); - validateValueAndPut(this.props, Map.entry("FILE_VERSION", props.version.toString()), "version"); - validateValueAndPut(this.props, Map.entry("LEGAL_COPYRIGHT", props.copyright), "copyright"); - validateValueAndPut(this.props, Map.entry("PRODUCT_NAME", props.name), "name"); + this.props.put("COMPANY_NAME", validateSingleLine(props.vendor)); + this.props.put("FILE_DESCRIPTION", validateSingleLine(props.description)); + this.props.put("FILE_VERSION", validateSingleLine(props.version.toString())); + this.props.put("LEGAL_COPYRIGHT", validateSingleLine(props.copyright)); + this.props.put("PRODUCT_NAME", validateSingleLine(props.name)); this.props.put("FIXEDFILEINFO_FILE_VERSION", toFixedFileVersion(props.version)); this.props.put("INTERNAL_NAME", props.executableName); @@ -90,7 +92,7 @@ final class ExecutableRebrander { UpdateResourceAction versionSwapper = resourceLock -> { if (versionSwap(resourceLock, propsArray) != 0) { - throw I18N.buildException().message("error.version-swap", target).create(RuntimeException::new); + throw new JPackageException(I18N.format("error.version-swap", target)); } }; @@ -100,7 +102,7 @@ final class ExecutableRebrander { .map(absIcon -> { return resourceLock -> { if (iconSwap(resourceLock, absIcon.toString()) != 0) { - throw I18N.buildException().message("error.icon-swap", absIcon).create(RuntimeException::new); + throw new JPackageException(I18N.format("error.icon-swap", absIcon)); } }; }); @@ -118,43 +120,58 @@ final class ExecutableRebrander { private static void rebrandExecutable(BuildEnv env, final Path target, List actions) throws IOException { + + Objects.requireNonNull(env); + Objects.requireNonNull(target); Objects.requireNonNull(actions); actions.forEach(Objects::requireNonNull); - String tempDirectory = env.buildRoot().toAbsolutePath().toString(); - if (WindowsDefender.isThereAPotentialWindowsDefenderIssue(tempDirectory)) { - Log.verbose(I18N.format("message.potential.windows.defender.issue", tempDirectory)); - } - - var shortTargetPath = ShortPathUtils.toShortPath(target); - long resourceLock = lockResource(shortTargetPath.orElse(target).toString()); - if (resourceLock == 0) { - throw I18N.buildException().message("error.lock-resource", shortTargetPath.orElse(target)).create(RuntimeException::new); - } - - final boolean resourceUnlockedSuccess; try { - for (var action : actions) { - action.editResource(resourceLock); - } - } finally { - if (resourceLock == 0) { - resourceUnlockedSuccess = true; - } else { - resourceUnlockedSuccess = unlockResource(resourceLock); - if (shortTargetPath.isPresent()) { - // Windows will rename the executable in the unlock operation. - // Should restore executable's name. - var tmpPath = target.getParent().resolve( - target.getFileName().toString() + ".restore"); - Files.move(shortTargetPath.get(), tmpPath); - Files.move(tmpPath, target); - } - } - } + Globals.instance().objectFactory().retryExecutor(RuntimeException.class).setExecutable(() -> { - if (!resourceUnlockedSuccess) { - throw I18N.buildException().message("error.unlock-resource", target).create(RuntimeException::new); + var shortTargetPath = ShortPathUtils.toShortPath(target); + long resourceLock = lockResource(shortTargetPath.orElse(target).toString()); + if (resourceLock == 0) { + throw new JPackageException(I18N.format("error.lock-resource", shortTargetPath.orElse(target))); + } + + final boolean resourceUnlockedSuccess; + try { + for (var action : actions) { + try { + action.editResource(resourceLock); + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + } + } finally { + if (resourceLock == 0) { + resourceUnlockedSuccess = true; + } else { + resourceUnlockedSuccess = unlockResource(resourceLock); + if (shortTargetPath.isPresent()) { + // Windows will rename the executable in the unlock operation. + // Should restore executable's name. + var tmpPath = target.getParent().resolve( + target.getFileName().toString() + ".restore"); + try { + Files.move(shortTargetPath.get(), tmpPath); + Files.move(tmpPath, target); + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + } + } + } + + if (!resourceUnlockedSuccess) { + throw new JPackageException(I18N.format("error.unlock-resource", shortTargetPath.orElse(target))); + } + + return null; + }).setMaxAttemptsCount(5).setAttemptTimeout(3, TimeUnit.SECONDS).execute(); + } catch (UncheckedIOException ex) { + throw ex.getCause(); } } @@ -197,14 +214,13 @@ final class ExecutableRebrander { } } - private static void validateValueAndPut(Map target, - Map.Entry e, String label) { - if (e.getValue().contains("\r") || e.getValue().contains("\n")) { - Log.error("Configuration parameter " + label - + " contains multiple lines of text, ignore it"); - e = Map.entry(e.getKey(), ""); + private static String validateSingleLine(String v) { + Objects.requireNonNull(v); + if (v.contains("\r") || v.contains("\n")) { + throw new IllegalArgumentException("Configuration parameter contains multiple lines of text"); + } else { + return v; } - target.put(e.getKey(), e.getValue()); } @FunctionalInterface @@ -212,19 +228,35 @@ final class ExecutableRebrander { public void editResource(long resourceLock) throws IOException; } - private static record ExecutableProperties(String vendor, String description, + private record ExecutableProperties(String vendor, String description, DottedVersion version, String copyright, String name, String executableName) { - static ExecutableProperties create(WinApplication app, - WinLauncher launcher) { - return new ExecutableProperties(app.vendor(), launcher.description(), - app.winVersion(), app.copyright(), launcher.name(), + + ExecutableProperties { + Objects.requireNonNull(vendor); + Objects.requireNonNull(description); + Objects.requireNonNull(version); + Objects.requireNonNull(copyright); + Objects.requireNonNull(name); + Objects.requireNonNull(executableName); + } + + static ExecutableProperties create(WinApplication app, WinLauncher launcher) { + return new ExecutableProperties( + app.vendor(), + launcher.description(), + app.winVersion(), + app.copyright(), + launcher.name(), launcher.executableNameWithSuffix()); } static ExecutableProperties create(WinExePackage pkg) { - return new ExecutableProperties(pkg.app().vendor(), - pkg.description(), DottedVersion.lazy(pkg.version()), - pkg.app().copyright(), pkg.packageName(), + return new ExecutableProperties( + pkg.app().vendor(), + pkg.description(), + DottedVersion.lazy(pkg.version()), + pkg.app().copyright(), + pkg.packageName(), pkg.packageFileNameWithSuffix()); } } diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WindowsDefender.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WindowsDefender.java deleted file mode 100644 index 075d87bcbca..00000000000 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WindowsDefender.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (c) 2012, 2023, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package jdk.jpackage.internal; - -import java.util.List; -import jdk.internal.util.OperatingSystem; -import jdk.internal.util.OSVersion; - -final class WindowsDefender { - - private WindowsDefender() {} - - static final boolean isThereAPotentialWindowsDefenderIssue(String dir) { - boolean result = false; - - if (OperatingSystem.isWindows() && - OSVersion.current().major() == 10) { - - // If DisableRealtimeMonitoring is not enabled then there - // may be a problem. - if (!WindowsRegistry.readDisableRealtimeMonitoring() && - !isDirectoryInExclusionPath(dir)) { - result = true; - } - } - - return result; - } - - private static boolean isDirectoryInExclusionPath(String dir) { - boolean result = false; - // If the user temp directory is not found in the exclusion - // list then there may be a problem. - List paths = WindowsRegistry.readExclusionsPaths(); - for (String s : paths) { - if (WindowsRegistry.comparePaths(s, dir)) { - result = true; - break; - } - } - - return result; - } - - static final String getUserTempDirectory() { - String tempDirectory = System.getProperty("java.io.tmpdir"); - return tempDirectory; - } -} diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WindowsRegistry.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WindowsRegistry.java deleted file mode 100644 index 7eb7b922667..00000000000 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WindowsRegistry.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package jdk.jpackage.internal; - -import java.util.ArrayList; -import java.util.List; - -@SuppressWarnings("restricted") -final class WindowsRegistry { - - // Currently we only support HKEY_LOCAL_MACHINE. Native implementation will - // require support for additinal HKEY if needed. - private static final int HKEY_LOCAL_MACHINE = 1; - - static { - System.loadLibrary("jpackage"); - } - - private WindowsRegistry() {} - - /** - * Reads the registry value for DisableRealtimeMonitoring. - * @return true if DisableRealtimeMonitoring is set to 0x1, - * false otherwise. - */ - static final boolean readDisableRealtimeMonitoring() { - final String subKey = "Software\\Microsoft\\" - + "Windows Defender\\Real-Time Protection"; - final String value = "DisableRealtimeMonitoring"; - int result = readDwordValue(HKEY_LOCAL_MACHINE, subKey, value, 0); - return (result == 1); - } - - static final List readExclusionsPaths() { - List result = new ArrayList<>(); - final String subKey = "Software\\Microsoft\\" - + "Windows Defender\\Exclusions\\Paths"; - long lKey = openRegistryKey(HKEY_LOCAL_MACHINE, subKey); - if (lKey == 0) { - return result; - } - - String valueName; - int index = 0; - do { - valueName = enumRegistryValue(lKey, index); - if (valueName != null) { - result.add(valueName); - index++; - } - } while (valueName != null); - - closeRegistryKey(lKey); - - return result; - } - - /** - * Reads DWORD registry value. - * - * @param key one of HKEY predefine value - * @param subKey registry sub key - * @param value value to read - * @param defaultValue default value in case if subKey or value not found - * or any other errors occurred - * @return value's data only if it was read successfully, otherwise - * defaultValue - */ - private static native int readDwordValue(int key, String subKey, - String value, int defaultValue); - - /** - * Open registry key. - * - * @param key one of HKEY predefine value - * @param subKey registry sub key - * @return native handle to open key - */ - private static native long openRegistryKey(int key, String subKey); - - /** - * Enumerates the values for registry key. - * - * @param lKey native handle to open key returned by openRegistryKey - * @param index index of value starting from 0. Increment until this - * function returns NULL which means no more values. - * @return returns value or NULL if error or no more data - */ - private static native String enumRegistryValue(long lKey, int index); - - /** - * Close registry key. - * - * @param lKey native handle to open key returned by openRegistryKey - */ - private static native void closeRegistryKey(long lKey); - - /** - * Compares two Windows paths regardless case and if paths - * are short or long. - * - * @param path1 path to compare - * @param path2 path to compare - * @return true if paths point to same location - */ - public static native boolean comparePaths(String path1, String path2); -} diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources.properties b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources.properties index 38d0bd02bbb..13a6c989324 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources.properties +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources.properties @@ -55,7 +55,6 @@ error.missing-service-installer='service-installer.exe' service installer not fo error.missing-service-installer.advice=Add 'service-installer.exe' service installer to the resource directory message.icon-not-ico=The specified icon "{0}" is not an ICO file and will not be used. The default icon will be used in it's place. -message.potential.windows.defender.issue=Warning: Windows Defender may prevent jpackage from functioning. If there is an issue, it can be addressed by either disabling realtime monitoring, or adding an exclusion for the directory "{0}". message.tool-version=Detected [{0}] version [{1}]. message.wrong-tool-version=Detected [{0}] version {1} but version {2} is required. message.use-wix36-features=WiX {0} detected. Enabling advanced cleanup action.