source: josm/trunk/src/org/openstreetmap/josm/tools/PlatformHookUnixoid.java@ 12830

Last change on this file since 12830 was 12830, checked in by Don-vip, 7 years ago

fix #15297, fix #15298 - proper detection of java packages on rpm-based Linux systems

  • Property svn:eol-style set to native
File size: 17.0 KB
RevLine 
[6380]1// License: GPL. For details, see LICENSE file.
[1023]2package org.openstreetmap.josm.tools;
3
[4998]4import static org.openstreetmap.josm.tools.I18n.tr;
5
[6682]6import java.awt.Desktop;
[1023]7import java.awt.event.KeyEvent;
[5850]8import java.io.BufferedReader;
[4153]9import java.io.File;
[12241]10import java.io.FileInputStream;
[1023]11import java.io.IOException;
[6682]12import java.net.URI;
13import java.net.URISyntaxException;
[7082]14import java.nio.charset.StandardCharsets;
[7314]15import java.nio.file.Files;
[7315]16import java.nio.file.Path;
[7314]17import java.nio.file.Paths;
[12241]18import java.security.KeyStoreException;
19import java.security.NoSuchAlgorithmException;
20import java.security.cert.CertificateException;
21import java.security.cert.CertificateFactory;
22import java.security.cert.X509Certificate;
[6103]23import java.util.Arrays;
[11162]24import java.util.Locale;
[12830]25import java.util.concurrent.ExecutionException;
[1023]26
[6310]27import org.openstreetmap.josm.Main;
[12241]28import org.openstreetmap.josm.io.CertificateAmendment.CertAmend;
[6310]29
[1023]30/**
[12219]31 * {@code PlatformHook} implementation for Unix systems.
32 * @since 1023
[2376]33 */
[1023]34public class PlatformHookUnixoid implements PlatformHook {
[6070]35
[5994]36 private String osDescription;
[6070]37
[4897]38 @Override
[12776]39 public Platform getPlatform() {
40 return Platform.UNIXOID;
41 }
42
43 @Override
[6443]44 public void preStartupHook() {
[9750]45 // See #12022 - Disable GNOME ATK Java wrapper as it causes a lot of serious trouble
46 if ("org.GNOME.Accessibility.AtkWrapper".equals(System.getProperty("assistive_technologies"))) {
47 System.clearProperty("assistive_technologies");
48 }
[1169]49 }
[4897]50
51 @Override
[1169]52 public void openUrl(String url) throws IOException {
[6682]53 for (String program : Main.pref.getCollection("browser.unix",
54 Arrays.asList("xdg-open", "#DESKTOP#", "$BROWSER", "gnome-open", "kfmclient openURL", "firefox"))) {
[1169]55 try {
[6682]56 if ("#DESKTOP#".equals(program)) {
57 Desktop.getDesktop().browse(new URI(url));
58 } else if (program.startsWith("$")) {
59 program = System.getenv().get(program.substring(1));
60 Runtime.getRuntime().exec(new String[]{program, url});
61 } else {
62 Runtime.getRuntime().exec(new String[]{program, url});
63 }
[1169]64 return;
[7004]65 } catch (IOException | URISyntaxException e) {
[12620]66 Logging.warn(e);
[1169]67 }
68 }
69 }
[1023]70
[4897]71 @Override
[1169]72 public void initSystemShortcuts() {
[8518]73 // CHECKSTYLE.OFF: LineLength
[4897]74 // TODO: Insert system shortcuts here. See Windows and especially OSX to see how to.
[8513]75 for (int i = KeyEvent.VK_F1; i <= KeyEvent.VK_F12; ++i) {
[8518]76 Shortcut.registerSystemShortcut("screen:toogle"+i, tr("reserved"), i, KeyEvent.CTRL_DOWN_MASK | KeyEvent.ALT_DOWN_MASK)
77 .setAutomatic();
[8513]78 }
[8518]79 Shortcut.registerSystemShortcut("system:reset", tr("reserved"), KeyEvent.VK_DELETE, KeyEvent.CTRL_DOWN_MASK | KeyEvent.ALT_DOWN_MASK)
80 .setAutomatic();
81 Shortcut.registerSystemShortcut("system:resetX", tr("reserved"), KeyEvent.VK_BACK_SPACE, KeyEvent.CTRL_DOWN_MASK | KeyEvent.ALT_DOWN_MASK)
82 .setAutomatic();
83 // CHECKSTYLE.ON: LineLength
[1169]84 }
[6920]85
[4897]86 @Override
[2376]87 public String getDefaultStyle() {
[2371]88 return "javax.swing.plaf.metal.MetalLookAndFeel";
89 }
[4153]90
[6103]91 /**
[10166]92 * Determines if the distribution is Debian or Ubuntu, or a derivative.
93 * @return {@code true} if the distribution is Debian, Ubuntu or Mint, {@code false} otherwise
94 */
95 public static boolean isDebianOrUbuntu() {
96 try {
97 String dist = Utils.execOutput(Arrays.asList("lsb_release", "-i", "-s"));
98 return "Debian".equalsIgnoreCase(dist) || "Ubuntu".equalsIgnoreCase(dist) || "Mint".equalsIgnoreCase(dist);
[12830]99 } catch (IOException | ExecutionException | InterruptedException e) {
[10627]100 // lsb_release is not available on all Linux systems, so don't log at warning level
[12620]101 Logging.debug(e);
[10166]102 return false;
103 }
104 }
105
106 /**
[6850]107 * Get the package name including detailed version.
[7314]108 * @param packageNames The possible package names (when a package can have different names on different distributions)
[6850]109 * @return The package name and package version if it can be identified, null otherwise
[7314]110 * @since 7314
[6850]111 */
[11747]112 public static String getPackageDetails(String... packageNames) {
[6850]113 try {
[10378]114 // CHECKSTYLE.OFF: SingleSpaceSeparator
[11096]115 boolean dpkg = Paths.get("/usr/bin/dpkg-query").toFile().exists();
116 boolean eque = Paths.get("/usr/bin/equery").toFile().exists();
117 boolean rpm = Paths.get("/bin/rpm").toFile().exists();
[10378]118 // CHECKSTYLE.ON: SingleSpaceSeparator
[7349]119 if (dpkg || rpm || eque) {
[7314]120 for (String packageName : packageNames) {
[10308]121 String[] args;
[7314]122 if (dpkg) {
123 args = new String[] {"dpkg-query", "--show", "--showformat", "${Architecture}-${Version}", packageName};
[7349]124 } else if (eque) {
125 args = new String[] {"equery", "-q", "list", "-e", "--format=$fullversion", packageName};
[7314]126 } else {
127 args = new String[] {"rpm", "-q", "--qf", "%{arch}-%{version}", packageName};
128 }
[12830]129 try {
130 String version = Utils.execOutput(Arrays.asList(args));
131 if (version != null && !version.isEmpty()) {
132 return packageName + ':' + version;
[12239]133 }
[12830]134 } catch (ExecutionException e) {
135 // Package does not exist, continue
136 Logging.trace(e);
[7314]137 }
138 }
[6962]139 }
[12830]140 } catch (IOException | InterruptedException e) {
[12620]141 Logging.warn(e);
[6850]142 }
[6962]143 return null;
[6850]144 }
[6920]145
[6850]146 /**
[6103]147 * Get the Java package name including detailed version.
148 *
149 * Some Java bugs are specific to a certain security update, so in addition
150 * to the Java version, we also need the exact package version.
151 *
[7314]152 * @return The package name and package version if it can be identified, null otherwise
[6103]153 */
154 public String getJavaPackageDetails() {
[7318]155 String home = System.getProperty("java.home");
[10580]156 if (home.contains("java-8-openjdk") || home.contains("java-1.8.0-openjdk")) {
157 return getPackageDetails("openjdk-8-jre", "java-1_8_0-openjdk", "java-1.8.0-openjdk");
158 } else if (home.contains("java-9-openjdk") || home.contains("java-1.9.0-openjdk")) {
[12830]159 return getPackageDetails("openjdk-9-jre", "java-1_9_0-openjdk", "java-1.9.0-openjdk", "java-9-openjdk");
[7349]160 } else if (home.contains("icedtea")) {
161 return getPackageDetails("icedtea-bin");
162 } else if (home.contains("oracle")) {
163 return getPackageDetails("oracle-jdk-bin", "oracle-jre-bin");
[6103]164 }
165 return null;
166 }
[6920]167
[6850]168 /**
169 * Get the Web Start package name including detailed version.
170 *
[7314]171 * OpenJDK packages are shipped with icedtea-web package,
172 * but its version generally does not match main java package version.
[6920]173 *
[6850]174 * Simply return {@code null} if there's no separate package for Java WebStart.
175 *
176 * @return The package name and package version if it can be identified, null otherwise
177 */
178 public String getWebStartPackageDetails() {
[7314]179 if (isOpenJDK()) {
180 return getPackageDetails("icedtea-netx", "icedtea-web");
[6850]181 }
182 return null;
183 }
[6103]184
[10734]185 /**
186 * Get the Gnome ATK wrapper package name including detailed version.
187 *
188 * Debian and Ubuntu derivatives come with a pre-enabled accessibility software
189 * completely buggy that makes Swing crash in a lot of different ways.
190 *
191 * Simply return {@code null} if it's not found.
192 *
193 * @return The package name and package version if it can be identified, null otherwise
194 */
195 public String getAtkWrapperPackageDetails() {
196 if (isOpenJDK() && isDebianOrUbuntu()) {
197 return getPackageDetails("libatk-wrapper-java");
198 }
199 return null;
200 }
201
[12219]202 private String buildOSDescription() {
[5850]203 String osName = System.getProperty("os.name");
204 if ("Linux".equalsIgnoreCase(osName)) {
205 try {
[8540]206 // Try lsb_release (only available on LSB-compliant Linux systems,
207 // see https://www.linuxbase.org/lsb-cert/productdir.php?by_prod )
[12411]208 String line = exec("lsb_release", "-ds");
[12217]209 if (line != null && !line.isEmpty()) {
210 line = line.replaceAll("\"+", "");
211 line = line.replaceAll("NAME=", ""); // strange code for some Gentoo's
212 if (line.startsWith("Linux ")) // e.g. Linux Mint
213 return line;
214 else if (!line.isEmpty())
215 return "Linux " + line;
[5850]216 }
217 } catch (IOException e) {
[12620]218 Logging.debug(e);
[5877]219 // Non LSB-compliant Linux system. List of common fallback release files: http://linuxmafia.com/faq/Admin/release-files.html
220 for (LinuxReleaseInfo info : new LinuxReleaseInfo[]{
221 new LinuxReleaseInfo("/etc/lsb-release", "DISTRIB_DESCRIPTION", "DISTRIB_ID", "DISTRIB_RELEASE"),
222 new LinuxReleaseInfo("/etc/os-release", "PRETTY_NAME", "NAME", "VERSION"),
223 new LinuxReleaseInfo("/etc/arch-release"),
224 new LinuxReleaseInfo("/etc/debian_version", "Debian GNU/Linux "),
225 new LinuxReleaseInfo("/etc/fedora-release"),
226 new LinuxReleaseInfo("/etc/gentoo-release"),
[7314]227 new LinuxReleaseInfo("/etc/redhat-release"),
228 new LinuxReleaseInfo("/etc/SuSE-release")
[5877]229 }) {
230 String description = info.extractDescription();
231 if (description != null && !description.isEmpty()) {
232 return "Linux " + description;
233 }
234 }
[5850]235 }
236 }
237 return osName;
238 }
[6070]239
[5994]240 @Override
241 public String getOSDescription() {
242 if (osDescription == null) {
243 osDescription = buildOSDescription();
244 }
245 return osDescription;
246 }
[6070]247
[12219]248 private static class LinuxReleaseInfo {
[5877]249 private final String path;
250 private final String descriptionField;
251 private final String idField;
252 private final String releaseField;
253 private final boolean plainText;
254 private final String prefix;
[6070]255
[12219]256 LinuxReleaseInfo(String path, String descriptionField, String idField, String releaseField) {
[5877]257 this(path, descriptionField, idField, releaseField, false, null);
258 }
259
[12219]260 LinuxReleaseInfo(String path) {
[5877]261 this(path, null, null, null, true, null);
262 }
263
[12219]264 LinuxReleaseInfo(String path, String prefix) {
[5877]265 this(path, null, null, null, true, prefix);
266 }
[6070]267
[5877]268 private LinuxReleaseInfo(String path, String descriptionField, String idField, String releaseField, boolean plainText, String prefix) {
269 this.path = path;
270 this.descriptionField = descriptionField;
271 this.idField = idField;
272 this.releaseField = releaseField;
273 this.plainText = plainText;
274 this.prefix = prefix;
275 }
276
[12219]277 @Override
278 public String toString() {
[6070]279 return "ReleaseInfo [path=" + path + ", descriptionField=" + descriptionField +
[8846]280 ", idField=" + idField + ", releaseField=" + releaseField + ']';
[5877]281 }
[6070]282
[5877]283 /**
284 * Extracts OS detailed information from a Linux release file (/etc/xxx-release)
285 * @return The OS detailed information, or {@code null}
286 */
287 public String extractDescription() {
288 String result = null;
289 if (path != null) {
[7315]290 Path p = Paths.get(path);
[11096]291 if (p.toFile().exists()) {
[7315]292 try (BufferedReader reader = Files.newBufferedReader(p, StandardCharsets.UTF_8)) {
[5877]293 String id = null;
294 String release = null;
295 String line;
296 while (result == null && (line = reader.readLine()) != null) {
297 if (line.contains("=")) {
298 String[] tokens = line.split("=");
299 if (tokens.length >= 2) {
300 // Description, if available, contains exactly what we need
301 if (descriptionField != null && descriptionField.equalsIgnoreCase(tokens[0])) {
302 result = Utils.strip(tokens[1]);
303 } else if (idField != null && idField.equalsIgnoreCase(tokens[0])) {
304 id = Utils.strip(tokens[1]);
305 } else if (releaseField != null && releaseField.equalsIgnoreCase(tokens[0])) {
306 release = Utils.strip(tokens[1]);
307 }
308 }
309 } else if (plainText && !line.isEmpty()) {
310 // Files composed of a single line
311 result = Utils.strip(line);
312 }
313 }
314 // If no description has been found, try to rebuild it with "id" + "release" (i.e. "name" + "version")
315 if (result == null && id != null && release != null) {
[8846]316 result = id + ' ' + release;
[5877]317 }
318 } catch (IOException e) {
319 // Ignore
[12620]320 Logging.trace(e);
[5877]321 }
322 }
323 }
324 // Append prefix if any
325 if (result != null && !result.isEmpty() && prefix != null && !prefix.isEmpty()) {
326 result = prefix + result;
327 }
[8510]328 if (result != null)
329 result = result.replaceAll("\"+", "");
[5877]330 return result;
331 }
332 }
[6920]333
[11162]334 /**
335 * Get the dot directory <code>~/.josm</code>.
336 * @return the dot directory
337 */
[11218]338 private static File getDotDirectory() {
[11162]339 String dirName = "." + Main.pref.getJOSMDirectoryBaseName().toLowerCase(Locale.ENGLISH);
340 return new File(System.getProperty("user.home"), dirName);
341 }
342
343 /**
344 * Returns true if the dot directory should be used for storing preferences,
345 * cache and user data.
346 * Currently this is the case, if the dot directory already exists.
347 * @return true if the dot directory should be used
348 */
[11613]349 private static boolean useDotDirectory() {
[11162]350 return getDotDirectory().exists();
351 }
352
[7206]353 @Override
[7829]354 public File getDefaultCacheDirectory() {
[11162]355 if (useDotDirectory()) {
356 return new File(getDotDirectory(), "cache");
357 } else {
358 String xdgCacheDir = System.getenv("XDG_CACHE_HOME");
359 if (xdgCacheDir != null && !xdgCacheDir.isEmpty()) {
360 return new File(xdgCacheDir, Main.pref.getJOSMDirectoryBaseName());
361 } else {
362 return new File(System.getProperty("user.home") + File.separator +
363 ".cache" + File.separator + Main.pref.getJOSMDirectoryBaseName());
364 }
365 }
[7829]366 }
[7831]367
368 @Override
369 public File getDefaultPrefDirectory() {
[11162]370 if (useDotDirectory()) {
371 return getDotDirectory();
372 } else {
373 String xdgConfigDir = System.getenv("XDG_CONFIG_HOME");
374 if (xdgConfigDir != null && !xdgConfigDir.isEmpty()) {
375 return new File(xdgConfigDir, Main.pref.getJOSMDirectoryBaseName());
376 } else {
377 return new File(System.getProperty("user.home") + File.separator +
378 ".config" + File.separator + Main.pref.getJOSMDirectoryBaseName());
379 }
380 }
[7831]381 }
[7834]382
383 @Override
384 public File getDefaultUserDataDirectory() {
[11162]385 if (useDotDirectory()) {
386 return getDotDirectory();
387 } else {
388 String xdgDataDir = System.getenv("XDG_DATA_HOME");
389 if (xdgDataDir != null && !xdgDataDir.isEmpty()) {
390 return new File(xdgDataDir, Main.pref.getJOSMDirectoryBaseName());
391 } else {
392 return new File(System.getProperty("user.home") + File.separator +
393 ".local" + File.separator + "share" + File.separator + Main.pref.getJOSMDirectoryBaseName());
394 }
395 }
[7834]396 }
[8015]397
[11642]398 @Override
[12241]399 public X509Certificate getX509Certificate(CertAmend certAmend)
400 throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
401 File f = new File("/usr/share/ca-certificates/mozilla", certAmend.getFilename());
402 if (f.exists()) {
403 CertificateFactory fact = CertificateFactory.getInstance("X.509");
404 try (FileInputStream is = new FileInputStream(f)) {
405 return (X509Certificate) fact.generateCertificate(is);
406 }
407 }
408 return null;
409 }
[1023]410}
Note: See TracBrowser for help on using the repository browser.