| /******************************************************************************* |
| * Copyright (c) 2003, 2018 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| |
| package org.eclipse.equinox.internal.cm.reliablefile; |
| |
| import java.io.*; |
| import java.nio.charset.StandardCharsets; |
| import java.util.*; |
| import java.util.zip.CRC32; |
| import java.util.zip.Checksum; |
| import org.eclipse.equinox.internal.cm.Activator; |
| |
| //This is a copy of org.eclipse.osgi.framework.internal.reliablefile.ReliableFile |
| |
| /** |
| * ReliableFile class used by ReliableFileInputStream and ReliableOutputStream. |
| * This class encapsulates all the logic for reliable file support. |
| * |
| */ |
| public class ReliableFile { |
| /** |
| * Open mask. Obtain the best data stream available. If the primary data |
| * contents are invalid (corrupt, missing, etc.), the data for a prior |
| * version may be used. |
| * An IOException will be thrown if a valid data content can not be |
| * determined. |
| * This is mutually exclusive with <code>OPEN_FAIL_ON_PRIMARY</code>. |
| */ |
| public static final int OPEN_BEST_AVAILABLE = 0; |
| /** |
| * Open mask. Obtain only the data stream for the primary file where any other |
| * version will not be valid. This should be used for data streams that are |
| * managed as a group as a prior contents may not match the other group data. |
| * If the primary data is not invalid, a IOException will be thrown. |
| * This is mutually exclusive with <code>OPEN_BEST_AVAILABLE</code>. |
| */ |
| public static final int OPEN_FAIL_ON_PRIMARY = 1; |
| |
| /** |
| * Use the last generation of the file |
| */ |
| public static final int GENERATION_LATEST = 0; |
| /** |
| * Keep infinite backup files |
| */ |
| public static final int GENERATIONS_INFINITE = 0; |
| |
| /** |
| * Extension of tmp file used during writing. |
| * A reliable file with this extension should |
| * never be directly used. |
| */ |
| public static final String tmpExt = ".tmp"; //$NON-NLS-1$ |
| |
| /** |
| * Property to set the maximum size of a file that will be buffered. When calculating a ReliableFile |
| * checksum, if the file is this size or small, ReliableFile will read the file contents into a |
| * <code>BufferedInputStream</code> and reset the buffer to avoid having to read the data from the |
| * media twice. Since this method require memory for storage, it is limited to this size. The default |
| * maximum is 128-KBytes. |
| */ |
| public static final String PROP_MAX_BUFFER = "osgi.reliableFile.maxInputStreamBuffer"; //$NON-NLS-1$ |
| /** |
| * The maximum number of generations to keep as backup files in case last generation |
| * file is determined to be invalid. |
| */ |
| public static final String PROP_MAX_GENERATIONS = "osgi.ReliableFile.maxGenerations"; //$NON-NLS-1$ |
| /** |
| *see org.eclipse.core.runtime.internal.adaptor.BasicLocation#PROP_OSGI_LOCKING |
| */ |
| public static final String PROP_OSGI_LOCKING = "osgi.locking"; //$NON-NLS-1$ |
| |
| private static final int FILETYPE_VALID = 0; |
| private static final int FILETYPE_CORRUPT = 1; |
| private static final int FILETYPE_NOSIGNATURE = 2; |
| |
| private static final byte identifier1[] = {'.', 'c', 'r', 'c'}; |
| private static final byte identifier2[] = {'.', 'v', '1', '\n'}; |
| |
| private static final int BUF_SIZE = 4096; |
| private static final int maxInputStreamBuffer; |
| private static final int defaultMaxGenerations; |
| private static final boolean fileSharing; |
| //our cache of the last looked up generations for a file |
| private static File lastGenerationFile = null; |
| private static int[] lastGenerations = null; |
| private static final Object lastGenerationLock = new Object(); |
| |
| static { |
| String prop = Activator.getProperty(PROP_MAX_BUFFER); |
| int tmpMaxInput = 128 * 1024; //128k |
| if (prop != null) { |
| try { |
| tmpMaxInput = Integer.parseInt(prop); |
| } catch (NumberFormatException e) {/*ignore*/ |
| } |
| } |
| maxInputStreamBuffer = tmpMaxInput; |
| |
| int tmpDefaultMax = 2; |
| prop = Activator.getProperty(PROP_MAX_GENERATIONS); |
| if (prop != null) { |
| try { |
| tmpDefaultMax = Integer.parseInt(prop); |
| } catch (NumberFormatException e) {/*ignore*/ |
| } |
| } |
| defaultMaxGenerations = tmpDefaultMax; |
| |
| prop = Activator.getProperty(PROP_OSGI_LOCKING); |
| boolean tmpFileSharing = true; |
| if (prop != null) { |
| if (prop.equals("none")) { //$NON-NLS-1$ |
| tmpFileSharing = false; |
| } |
| } |
| fileSharing = tmpFileSharing; |
| } |
| |
| /** File object for original reference file */ |
| private File referenceFile; |
| |
| /** List of checksum file objects: File => specific ReliableFile generation */ |
| private static Hashtable<File, CacheInfo> cacheFiles = new Hashtable<>(20); |
| |
| private File inputFile = null; |
| private File outputFile = null; |
| private Checksum appendChecksum = null; |
| |
| /** |
| * ReliableFile object factory. This method is called by ReliableFileInputStream |
| * and ReliableFileOutputStream to get a ReliableFile object for a target file. |
| * If the object is in the cache, the cached copy is returned. |
| * Otherwise a new ReliableFile object is created and returned. |
| * The use count of the returned ReliableFile object is incremented. |
| * |
| * @param name Name of the target file. |
| * @return A ReliableFile object for the target file. |
| * @throws IOException If the target file is a directory. |
| */ |
| static ReliableFile getReliableFile(String name) throws IOException { |
| return getReliableFile(new File(name)); |
| } |
| |
| /** |
| * ReliableFile object factory. This method is called by ReliableFileInputStream |
| * and ReliableFileOutputStream to get a ReliableFile object for a target file. |
| * If the object is in the cache, the cached copy is returned. |
| * Otherwise a new ReliableFile object is created and returned. |
| * The use count of the returned ReliableFile object is incremented. |
| * |
| * @param file File object for the target file. |
| * @return A ReliableFile object for the target file. |
| * @throws IOException If the target file is a directory. |
| */ |
| static ReliableFile getReliableFile(File file) throws IOException { |
| if (file.isDirectory()) { |
| throw new FileNotFoundException("file is a directory"); //$NON-NLS-1$ |
| } |
| return new ReliableFile(file); |
| } |
| |
| /** |
| * Private constructor used by the static getReliableFile factory methods. |
| * |
| * @param file File object for the target file. |
| */ |
| private ReliableFile(File file) { |
| referenceFile = file; |
| } |
| |
| private static int[] getFileGenerations(File file) { |
| if (!fileSharing) { |
| synchronized (lastGenerationLock) { |
| if (lastGenerationFile != null) { |
| //shortcut maybe, only if filesharing is not supported |
| if (file.equals(lastGenerationFile)) |
| return lastGenerations; |
| } |
| } |
| } |
| int[] generations = null; |
| try { |
| String name = file.getName(); |
| String prefix = name + '.'; |
| int prefixLen = prefix.length(); |
| File parent = new File(file.getParent()); |
| String[] files = parent.list(); |
| if (files == null) |
| return null; |
| List<Integer> list = new ArrayList<>(defaultMaxGenerations); |
| if (file.exists()) |
| list.add(Integer.valueOf(0)); //base file exists |
| for (int i = 0; i < files.length; i++) { |
| if (files[i].startsWith(prefix)) { |
| try { |
| int id = Integer.parseInt(files[i].substring(prefixLen)); |
| list.add(Integer.valueOf(id)); |
| } catch (NumberFormatException e) {/*ignore*/ |
| } |
| } |
| } |
| if (list.size() == 0) |
| return null; |
| Object[] array = list.toArray(); |
| Arrays.sort(array); |
| generations = new int[array.length]; |
| for (int i = 0, j = array.length - 1; i < array.length; i++, j--) { |
| generations[i] = ((Integer) array[j]).intValue(); |
| } |
| return generations; |
| } finally { |
| if (!fileSharing) { |
| synchronized (lastGenerationLock) { |
| lastGenerationFile = file; |
| lastGenerations = generations; |
| } |
| } |
| } |
| } |
| |
| /** |
| * Returns an InputStream object for reading the target file. |
| * |
| * @param generation the maximum generation to evaluate |
| * @param openMask mask used to open data. |
| * are invalid (corrupt, missing, etc). |
| * @return An InputStream object which can be used to read the target file. |
| * @throws IOException If an error occurs preparing the file. |
| */ |
| InputStream getInputStream(int generation, int openMask) throws IOException { |
| if (inputFile != null) { |
| throw new IOException("Input stream already open"); //$NON-NLS-1$ |
| } |
| int[] generations = getFileGenerations(referenceFile); |
| if (generations == null) { |
| throw new FileNotFoundException("File not found"); //$NON-NLS-1$ |
| } |
| String name = referenceFile.getName(); |
| File parent = new File(referenceFile.getParent()); |
| |
| boolean failOnPrimary = (openMask & OPEN_FAIL_ON_PRIMARY) != 0; |
| if (failOnPrimary && generation == GENERATIONS_INFINITE) |
| generation = generations[0]; |
| |
| File textFile = null; |
| InputStream textIS = null; |
| for (int idx = 0; idx < generations.length; idx++) { |
| if (generation != 0) { |
| if (generations[idx] > generation || (failOnPrimary && generations[idx] != generation)) |
| continue; |
| } |
| File file; |
| if (generations[idx] != 0) |
| file = new File(parent, name + '.' + generations[idx]); |
| else |
| file = referenceFile; |
| InputStream is = null; |
| CacheInfo info; |
| synchronized (cacheFiles) { |
| info = cacheFiles.get(file); |
| long timeStamp = file.lastModified(); |
| if (info == null || timeStamp != info.timeStamp) { |
| try { |
| is = new FileInputStream(file); |
| if (is.available() < maxInputStreamBuffer) |
| is = new BufferedInputStream(is); |
| Checksum cksum = getChecksumCalculator(); |
| int filetype = getStreamType(is, cksum); |
| info = new CacheInfo(filetype, cksum, timeStamp); |
| cacheFiles.put(file, info); |
| } catch (IOException e) {/*ignore*/ |
| } |
| } |
| } |
| |
| // if looking for a specific generation only, only look at one |
| // and return the result. |
| if (failOnPrimary) { |
| if (info != null && info.filetype == FILETYPE_VALID) { |
| inputFile = file; |
| if (is != null) |
| return is; |
| return new FileInputStream(file); |
| } |
| throw new IOException("ReliableFile is corrupt"); //$NON-NLS-1$ |
| } |
| |
| // if error, ignore this file & try next |
| if (info == null) |
| continue; |
| |
| // we're not looking for a specific version, so let's pick the best case |
| switch (info.filetype) { |
| case FILETYPE_VALID : |
| inputFile = file; |
| if (is != null) |
| return is; |
| return new FileInputStream(file); |
| |
| case FILETYPE_NOSIGNATURE : |
| if (textFile == null) { |
| textFile = file; |
| textIS = is; |
| } |
| break; |
| } |
| } |
| |
| // didn't find any valid files, if there are any plain text files |
| // use it instead |
| if (textFile != null) { |
| inputFile = textFile; |
| if (textIS != null) |
| return textIS; |
| return new FileInputStream(textFile); |
| } |
| throw new IOException("ReliableFile is corrupt"); //$NON-NLS-1$ |
| } |
| |
| /** |
| * Returns an OutputStream object for writing the target file. |
| * |
| * @param append append new data to an existing file. |
| * @param appendGeneration specific generation of file to append from. |
| * @return An OutputStream object which can be used to write the target file. |
| * @throws IOException IOException If an error occurs preparing the file. |
| */ |
| OutputStream getOutputStream(boolean append, int appendGeneration) throws IOException { |
| if (outputFile != null) |
| throw new IOException("Output stream is already open"); //$NON_NLS-1$ //$NON-NLS-1$ |
| String name = referenceFile.getName(); |
| File parent = new File(referenceFile.getParent()); |
| File tmpFile = File.createTempFile(name, tmpExt, parent); |
| |
| if (!append) { |
| OutputStream os = new FileOutputStream(tmpFile); |
| outputFile = tmpFile; |
| return os; |
| } |
| |
| InputStream is; |
| try { |
| is = getInputStream(appendGeneration, OPEN_BEST_AVAILABLE); |
| } catch (FileNotFoundException e) { |
| OutputStream os = new FileOutputStream(tmpFile); |
| outputFile = tmpFile; |
| return os; |
| } |
| |
| try { |
| CacheInfo info = cacheFiles.get(inputFile); |
| appendChecksum = info.checksum; |
| OutputStream os = new FileOutputStream(tmpFile); |
| if (info.filetype == FILETYPE_NOSIGNATURE) { |
| cp(is, os, 0); |
| } else { |
| cp(is, os, 16); // don't copy checksum signature |
| } |
| outputFile = tmpFile; |
| return os; |
| } finally { |
| closeInputFile(); |
| } |
| } |
| |
| /** |
| * Close the target file for reading. |
| * |
| * @param checksum Checksum of the file contents |
| * @throws IOException If an error occurs closing the file. |
| */ |
| void closeOutputFile(Checksum checksum) throws IOException { |
| if (outputFile == null) |
| throw new IOException("Output stream is not open"); //$NON-NLS-1$ |
| int[] generations = getFileGenerations(referenceFile); |
| String name = referenceFile.getName(); |
| File parent = new File(referenceFile.getParent()); |
| File newFile; |
| if (generations == null) |
| newFile = new File(parent, name + ".1"); //$NON-NLS-1$ |
| else |
| newFile = new File(parent, name + '.' + (generations[0] + 1)); |
| |
| mv(outputFile, newFile); // throws IOException if problem |
| outputFile = null; |
| appendChecksum = null; |
| CacheInfo info = new CacheInfo(FILETYPE_VALID, checksum, newFile.lastModified()); |
| cacheFiles.put(newFile, info); |
| cleanup(generations, true); |
| lastGenerationFile = null; |
| lastGenerations = null; |
| } |
| |
| /** |
| * Abort the current output stream and do not update the reliable file table. |
| * |
| */ |
| void abortOutputFile() { |
| if (outputFile == null) |
| return; |
| outputFile.delete(); |
| outputFile = null; |
| appendChecksum = null; |
| } |
| |
| File getOutputFile() { |
| return outputFile; |
| } |
| |
| /** |
| * Close the target file for reading. |
| */ |
| void closeInputFile() { |
| inputFile = null; |
| } |
| |
| private void cleanup(int[] generations, boolean generationAdded) { |
| if (generations == null) |
| return; |
| String name = referenceFile.getName(); |
| File parent = new File(referenceFile.getParent()); |
| int generationCount = generations.length; |
| // if a base file is in the list (0 in generations[]), we will |
| // never delete these files, so don't count them in the old |
| // generation count. |
| if (generations[generationCount - 1] == 0) |
| generationCount--; |
| // assume here that the int[] does not include a file just created |
| int rmCount = generationCount - defaultMaxGenerations; |
| if (generationAdded) |
| rmCount++; |
| if (rmCount < 1) |
| return; |
| synchronized (cacheFiles) { |
| // first, see if any of the files not deleted are known to |
| // be corrupt. If so, be sure to keep not to delete good |
| // backup files. |
| for (int idx = 0, count = generationCount - rmCount; idx < count; idx++) { |
| File file = new File(parent, name + '.' + generations[idx]); |
| CacheInfo info = cacheFiles.get(file); |
| if (info != null) { |
| if (info.filetype == FILETYPE_CORRUPT) |
| rmCount--; |
| } |
| } |
| for (int idx = generationCount - 1; rmCount > 0; idx--, rmCount--) { |
| File rmFile = new File(parent, name + '.' + generations[idx]); |
| rmFile.delete(); |
| cacheFiles.remove(rmFile); |
| } |
| } |
| } |
| |
| /** |
| * Rename a file. |
| * |
| * @param from The original file. |
| * @param to The new file name. |
| * @throws IOException If the rename failed. |
| */ |
| private static void mv(File from, File to) throws IOException { |
| if (!from.renameTo(to)) { |
| throw new IOException("rename failed"); //$NON-NLS-1$ |
| } |
| } |
| |
| /** |
| * Copy a file. |
| * |
| * @throws IOException If the copy failed. |
| */ |
| private static void cp(InputStream in, OutputStream out, int truncateSize) throws IOException { |
| try { |
| int length = in.available(); |
| if (truncateSize > length) |
| length = 0; |
| else |
| length -= truncateSize; |
| if (length > 0) { |
| int bufferSize; |
| if (length > BUF_SIZE) { |
| bufferSize = BUF_SIZE; |
| } else { |
| bufferSize = length; |
| } |
| |
| byte buffer[] = new byte[bufferSize]; |
| int size = 0; |
| int count; |
| while ((count = in.read(buffer, 0, length)) > 0) { |
| if ((size + count) >= length) |
| count = length - size; |
| out.write(buffer, 0, count); |
| size += count; |
| } |
| } |
| } finally { |
| try { |
| in.close(); |
| } catch (IOException e) {/*ignore*/ |
| } |
| out.close(); |
| } |
| } |
| |
| /** |
| * Answers a boolean indicating whether or not the specified reliable file |
| * exists on the underlying file system. This call only returns if a file |
| * exists and not if the file contents are valid. |
| * @param file returns true if the specified reliable file exists; otherwise false is returned |
| * |
| * @return <code>true</code> if the specified reliable file exists, |
| * <code>false</code> otherwise. |
| */ |
| public static boolean exists(File file) { |
| String prefix = file.getName() + '.'; |
| File parent = new File(file.getParent()); |
| int prefixLen = prefix.length(); |
| String[] files = parent.list(); |
| if (files == null) |
| return false; |
| for (int i = 0; i < files.length; i++) { |
| if (files[i].startsWith(prefix)) { |
| try { |
| Integer.parseInt(files[i].substring(prefixLen)); |
| return true; |
| } catch (NumberFormatException e) {/*ignore*/ |
| } |
| } |
| } |
| return file.exists(); |
| } |
| |
| /** |
| * Returns the time that the reliable file was last modified. Only the time |
| * of the last file generation is returned. |
| * @param file the file to determine the time of. |
| * @return time the file was last modified (see java.io.File.lastModified()). |
| */ |
| public static long lastModified(File file) { |
| int[] generations = getFileGenerations(file); |
| if (generations == null) |
| return 0L; |
| if (generations[0] == 0) |
| return file.lastModified(); |
| String name = file.getName(); |
| File parent = new File(file.getParent()); |
| File newFile = new File(parent, name + '.' + generations[0]); |
| return newFile.lastModified(); |
| } |
| |
| /** |
| * Returns the time that this ReliableFile was last modified. This method is only valid |
| * after requesting an input stream and the time of the actual input file is returned. |
| * |
| * @return time the file was last modified (see java.io.File.lastModified()) or |
| * 0L if an input stream is not open. |
| */ |
| public long lastModified() { |
| if (inputFile != null) { |
| return inputFile.lastModified(); |
| } |
| return 0L; |
| } |
| |
| /** |
| * Returns the a version number of a reliable managed file. The version can be expected |
| * to be unique for each successful file update. |
| * |
| * @param file the file to determine the version of. |
| * @return a unique version of this current file. A value of -1 indicates the file does |
| * not exist or an error occurred. |
| */ |
| public static int lastModifiedVersion(File file) { |
| int[] generations = getFileGenerations(file); |
| if (generations == null) |
| return -1; |
| return generations[0]; |
| } |
| |
| /** |
| * Delete the specified reliable file on the underlying file system. |
| * @param deleteFile the reliable file to delete |
| * |
| * @return <code>true</code> if the specified reliable file was deleted, |
| * <code>false</code> otherwise. |
| */ |
| public static boolean delete(File deleteFile) { |
| int[] generations = getFileGenerations(deleteFile); |
| if (generations == null) |
| return false; |
| String name = deleteFile.getName(); |
| File parent = new File(deleteFile.getParent()); |
| synchronized (cacheFiles) { |
| for (int idx = 0; idx < generations.length; idx++) { |
| // base files (.0 in generations[]) will never be deleted |
| if (generations[idx] == 0) |
| continue; |
| File file = new File(parent, name + '.' + generations[idx]); |
| if (file.exists()) { |
| file.delete(); |
| } |
| cacheFiles.remove(file); |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Get a list of ReliableFile base names in a given directory. Only files with a valid |
| * ReliableFile generation are included. |
| * @param directory the directory to inquire. |
| * @return an array of ReliableFile names in the directory. |
| * @throws IOException if an error occurs. |
| */ |
| public static String[] getBaseFiles(File directory) throws IOException { |
| if (!directory.isDirectory()) |
| throw new IOException("Not a valid directory"); //$NON-NLS-1$ |
| String files[] = directory.list(); |
| Set<String> list = new HashSet<>(files.length / 2); |
| for (int idx = 0; idx < files.length; idx++) { |
| String file = files[idx]; |
| int pos = file.lastIndexOf('.'); |
| if (pos == -1) |
| continue; |
| String ext = file.substring(pos + 1); |
| int generation = 0; |
| try { |
| generation = Integer.parseInt(ext); |
| } catch (NumberFormatException e) {/*skip*/ |
| } |
| if (generation == 0) |
| continue; |
| String base = file.substring(0, pos); |
| list.add(base); |
| } |
| files = new String[list.size()]; |
| int idx = 0; |
| for (Iterator<String> iter = list.iterator(); iter.hasNext();) { |
| files[idx++] = iter.next(); |
| } |
| return files; |
| } |
| |
| /** |
| * Delete any old excess generations of a given reliable file. |
| * @param base realible file. |
| */ |
| public static void cleanupGenerations(File base) { |
| ReliableFile rf = new ReliableFile(base); |
| int[] generations = getFileGenerations(base); |
| rf.cleanup(generations, false); |
| lastGenerationFile = null; |
| lastGenerations = null; |
| } |
| |
| /** |
| * Inform ReliableFile that a file has been updated outside of |
| * ReliableFile. |
| * @param file |
| */ |
| public static void fileUpdated(File file) { |
| lastGenerationFile = null; |
| lastGenerations = null; |
| } |
| |
| /** |
| * Append a checksum value to the end of an output stream. |
| * @param out the output stream. |
| * @param checksum the checksum value to append to the file. |
| * @throws IOException if a write error occurs. |
| */ |
| void writeChecksumSignature(OutputStream out, Checksum checksum) throws IOException { |
| // tag on our signature and checksum |
| out.write(ReliableFile.identifier1); |
| out.write(intToHex((int) checksum.getValue())); |
| out.write(ReliableFile.identifier2); |
| } |
| |
| /** |
| * Returns the size of the ReliableFile signature + CRC at the end of the file. |
| * This method should be called only after calling getInputStream() or |
| * getOutputStream() methods. |
| * |
| * @return <code>int</code> size of the ReliableFIle signature + CRC appended |
| * to the end of the file. |
| * @throws IOException if getInputStream() or getOutputStream has not been |
| * called. |
| */ |
| int getSignatureSize() throws IOException { |
| if (inputFile != null) { |
| CacheInfo info = cacheFiles.get(inputFile); |
| if (info != null) { |
| switch (info.filetype) { |
| case FILETYPE_VALID : |
| case FILETYPE_CORRUPT : |
| return 16; |
| case FILETYPE_NOSIGNATURE : |
| return 0; |
| } |
| } |
| } |
| throw new IOException("ReliableFile signature size is unknown"); //$NON-NLS-1$ |
| } |
| |
| /** |
| * Returns a Checksum object for the current file contents. This method |
| * should be called only after calling getInputStream() or |
| * getOutputStream() methods. |
| * |
| * @return Object implementing Checksum interface initialized to the |
| * current file contents. |
| * @throws IOException if getOutputStream for append has not been called. |
| */ |
| Checksum getFileChecksum() throws IOException { |
| if (appendChecksum == null) |
| throw new IOException("Checksum is invalid!"); //$NON-NLS-1$ |
| return appendChecksum; |
| } |
| |
| /** |
| * Create a checksum implementation used by ReliableFile. |
| * |
| * @return Object implementing Checksum interface used to calculate |
| * a reliable file checksum |
| */ |
| Checksum getChecksumCalculator() { |
| // Using CRC32 because Adler32 isn't in the eeMinimum library. |
| return new CRC32(); |
| } |
| |
| /** |
| * Determine if a File is a valid ReliableFile |
| * |
| * @return <code>true</code> if the file is a valid ReliableFile |
| * @throws IOException If an error occurs verifying the file. |
| */ |
| private int getStreamType(InputStream is, Checksum crc) throws IOException { |
| boolean markSupported = is.markSupported(); |
| if (markSupported) |
| is.mark(is.available()); |
| try { |
| int len = is.available(); |
| if (len < 16) { |
| if (crc != null) { |
| byte data[] = new byte[16]; |
| int num = is.read(data); |
| if (num > 0) |
| crc.update(data, 0, num); |
| } |
| return FILETYPE_NOSIGNATURE; |
| } |
| len -= 16; |
| |
| int pos = 0; |
| byte data[] = new byte[BUF_SIZE]; |
| |
| while (pos < len) { |
| int read = data.length; |
| if (pos + read > len) |
| read = len - pos; |
| |
| int num = is.read(data, 0, read); |
| if (num == -1) { |
| throw new IOException("Unable to read entire file."); //$NON-NLS-1$ |
| } |
| |
| crc.update(data, 0, num); |
| pos += num; |
| } |
| |
| int num = is.read(data); // read last 16-byte signature |
| if (num != 16) { |
| throw new IOException("Unable to read entire file."); //$NON-NLS-1$ |
| } |
| |
| int i, j; |
| for (i = 0; i < 4; i++) |
| if (identifier1[i] != data[i]) { |
| crc.update(data, 0, 16); // update crc w/ sig bytes |
| return FILETYPE_NOSIGNATURE; |
| } |
| for (i = 0, j = 12; i < 4; i++, j++) |
| if (identifier2[i] != data[j]) { |
| crc.update(data, 0, 16); // update crc w/ sig bytes |
| return FILETYPE_NOSIGNATURE; |
| } |
| long crccmp = Long.valueOf(new String(data, 4, 8, StandardCharsets.UTF_8), 16).longValue(); |
| if (crccmp == crc.getValue()) { |
| return FILETYPE_VALID; |
| } |
| // do not update CRC |
| return FILETYPE_CORRUPT; |
| } finally { |
| if (markSupported) |
| is.reset(); |
| } |
| } |
| |
| private static byte[] intToHex(int l) { |
| byte[] buffer = new byte[8]; |
| int count = 8; |
| |
| do { |
| int ch = (l & 0xf); |
| if (ch > 9) |
| ch = ch - 10 + 'a'; |
| else |
| ch += '0'; |
| buffer[--count] = (byte) ch; |
| l >>= 4; |
| } while (count > 0); |
| return buffer; |
| } |
| |
| private class CacheInfo { |
| int filetype; |
| Checksum checksum; |
| long timeStamp; |
| |
| CacheInfo(int filetype, Checksum checksum, long timeStamp) { |
| this.filetype = filetype; |
| this.checksum = checksum; |
| this.timeStamp = timeStamp; |
| } |
| } |
| } |