blob: 2c9eddf8b2dd20739dbe2c1b8a6fbe0a910baa08 [file] [log] [blame]
/*******************************************************************************
* 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());
}
}