| /******************************************************************************* |
| * Copyright (c) 2005, 2021 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 |
| * Hannes Wellmann - Bug 577432 - Speed up and improve file processing in Storage |
| *******************************************************************************/ |
| |
| package org.eclipse.osgi.storage; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.net.MalformedURLException; |
| import java.net.URL; |
| import java.nio.file.FileVisitResult; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| import java.nio.file.SimpleFileVisitor; |
| import java.nio.file.StandardCopyOption; |
| import java.nio.file.attribute.BasicFileAttributes; |
| import java.util.Arrays; |
| import java.util.Dictionary; |
| import java.util.HashSet; |
| import java.util.Hashtable; |
| import java.util.Set; |
| import java.util.stream.Stream; |
| import org.eclipse.osgi.internal.debug.Debug; |
| import org.osgi.framework.BundleContext; |
| import org.osgi.framework.Constants; |
| import org.osgi.framework.ServiceRegistration; |
| |
| /** |
| * A utility class with some generally useful static methods for adaptor hook implementations |
| */ |
| public class StorageUtil { |
| |
| /** |
| * Copies the content of the given path (file or directory) to the specified |
| * target. If the source is a directory all contained elements are copied |
| * recursively. |
| * @param inFile input directory to copy. |
| * @param outFile output directory to copy to. |
| * @throws IOException if any error occurs during the copy. |
| */ |
| public static void copy(File inFile, File outFile) throws IOException { |
| Path source = inFile.toPath(); |
| Path target = outFile.toPath(); |
| if (Files.exists(source)) { |
| Files.createDirectories(target.getParent()); |
| try (Stream<Path> walk = Files.walk(source)) { |
| for (Path s : (Iterable<Path>) walk::iterator) { |
| Path t = target.resolve(source.relativize(s)); |
| Files.copy(s, t, StandardCopyOption.REPLACE_EXISTING); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Read a file from an InputStream and write it to the file system. |
| * |
| * @param in InputStream from which to read. This stream will be closed by this method. |
| * @param file output file to create. |
| * @exception IOException |
| */ |
| public static void readFile(InputStream in, File file) throws IOException { |
| Files.copy(in, file.toPath(), StandardCopyOption.REPLACE_EXISTING); |
| } |
| |
| /** |
| * This function performs the equivalent of "rm -r" on a file or directory. |
| * |
| * @param file file or directory to delete |
| * @return false is the specified files still exists, true otherwise. |
| */ |
| public static boolean rm(File file, boolean DEBUG) { |
| Path path = file.toPath(); |
| if (!Files.exists(path)) { |
| return true; |
| } |
| try { |
| Files.walkFileTree(path, new SimpleFileVisitor<Path>() { |
| @Override |
| public FileVisitResult visitFile(Path f, BasicFileAttributes attrs) { |
| return delete(f, DEBUG); |
| } |
| |
| @Override |
| public FileVisitResult postVisitDirectory(Path dir, IOException exc) { |
| return delete(dir, DEBUG); |
| } |
| |
| private FileVisitResult delete(Path pathToDelete, boolean debug) { |
| try { |
| if (debug) { |
| Debug.println("rm " + pathToDelete); //$NON-NLS-1$ |
| } |
| Files.delete(pathToDelete); |
| } catch (IOException e) { |
| if (debug) { |
| Debug.println(" rm failed:" + e.getMessage()); //$NON-NLS-1$ |
| } |
| } |
| return FileVisitResult.CONTINUE; |
| } |
| }); |
| return !Files.exists(path); |
| } catch (IOException e) { |
| return false; |
| } |
| } |
| |
| /** |
| * Register a service object. |
| * @param name the service class name |
| * @param service the service object |
| * @param context the registering bundle context |
| * @return the service registration object |
| */ |
| public static ServiceRegistration<?> register(String name, Object service, BundleContext context) { |
| Dictionary<String, Object> properties = new Hashtable<>(); |
| properties.put(Constants.SERVICE_RANKING, Integer.valueOf(Integer.MAX_VALUE)); |
| properties.put(Constants.SERVICE_PID, context.getBundle().getBundleId() + "." + service.getClass().getName()); //$NON-NLS-1$ |
| return context.registerService(name, service, properties); |
| } |
| |
| public static boolean canWrite(File installDir) { |
| if (!installDir.isDirectory()) |
| return false; |
| |
| if (Files.isWritable(installDir.toPath())) |
| return true; |
| |
| File fileTest = null; |
| try { |
| // we use the .dll suffix to properly test on Vista virtual directories |
| // on Vista you are not allowed to write executable files on virtual directories |
| // like "Program Files" |
| fileTest = File.createTempFile("writableArea", ".dll", installDir); //$NON-NLS-1$ //$NON-NLS-2$ |
| } catch (IOException e) { |
| // If an exception occured while trying to create the file, it means that it is |
| // not writtable |
| return false; |
| } finally { |
| if (fileTest != null) |
| fileTest.delete(); |
| } |
| return true; |
| } |
| |
| public static URL encodeFileURL(File file) throws MalformedURLException { |
| return file.toURI().toURL(); |
| } |
| |
| public static byte[] getBytes(InputStream in, int length, int BUF_SIZE) throws IOException { |
| byte[] classbytes; |
| int bytesread = 0; |
| int readcount; |
| try { |
| if (length > 0) { |
| classbytes = new byte[length]; |
| for (; bytesread < length; bytesread += readcount) { |
| readcount = in.read(classbytes, bytesread, length - bytesread); |
| if (readcount <= 0) /* if we didn't read anything */ |
| break; /* leave the loop */ |
| } |
| } else /* does not know its own length! */ { |
| length = BUF_SIZE; |
| classbytes = new byte[length]; |
| readloop: while (true) { |
| for (; bytesread < length; bytesread += readcount) { |
| readcount = in.read(classbytes, bytesread, length - bytesread); |
| if (readcount <= 0) /* if we didn't read anything */ |
| break readloop; /* leave the loop */ |
| } |
| byte[] oldbytes = classbytes; |
| length += BUF_SIZE; |
| classbytes = new byte[length]; |
| System.arraycopy(oldbytes, 0, classbytes, 0, bytesread); |
| } |
| } |
| if (classbytes.length > bytesread) { |
| byte[] oldbytes = classbytes; |
| classbytes = new byte[bytesread]; |
| System.arraycopy(oldbytes, 0, classbytes, 0, bytesread); |
| } |
| } finally { |
| try { |
| in.close(); |
| } catch (IOException ee) { |
| // nothing to do here |
| } |
| } |
| return classbytes; |
| } |
| |
| public static void move(File from, File to, boolean DEBUG) throws IOException { |
| try { |
| Files.move(from.toPath(), to.toPath(), StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE); |
| } catch (IOException e) { |
| if (DEBUG) { |
| Debug.println("Failed to move atomically: " + from + " to " + to); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| // remove in case it failed because the target to non-empty directory or |
| // the target type does not match the from |
| rm(to, DEBUG); |
| // also, try without atomic operation |
| Files.move(from.toPath(), to.toPath(), StandardCopyOption.REPLACE_EXISTING); |
| } |
| if (DEBUG) { |
| Debug.println("Successfully moved file: " + from + " to " + to); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| } |
| |
| private static final boolean IS_WINDOWS = File.separatorChar == '\\'; |
| |
| // reserved names according to |
| // https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file |
| private static final Set<String> RESERVED_NAMES = new HashSet<>(Arrays.asList("aux", "com1", "com2", "com3", "com4", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ |
| "com5", "com6", "com7", "com8", "com9", "con", "lpt1", "lpt2", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ |
| "lpt3", "lpt4", "lpt5", "lpt6", "lpt7", "lpt8", "lpt9", "nul", "prn")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$ |
| |
| /** Tests whether the filename can escape path into special device **/ |
| public static boolean isReservedFileName(File file) { |
| // Directory names are not checked here because illegal directory names will be |
| // handled by OS. |
| if (!IS_WINDOWS) { // only windows has special file names which can escape any path |
| return false; |
| } |
| String fileName = file.getName(); |
| // Illegal characters are not checked here because they are check by both JDK |
| // and OS. This is only a check against technical allowed but unwanted device |
| // names. |
| int dot = fileName.indexOf('.'); |
| // on windows, filename suffixes are not relevant to name validity |
| String basename = dot == -1 ? fileName : fileName.substring(0, dot); |
| return RESERVED_NAMES.contains(basename.toLowerCase()); |
| } |
| |
| } |