0N/A/*
2362N/A * Copyright (c) 1997, 2008, Oracle and/or its affiliates. All rights reserved.
0N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0N/A *
0N/A * This code is free software; you can redistribute it and/or modify it
0N/A * under the terms of the GNU General Public License version 2 only, as
2362N/A * published by the Free Software Foundation. Oracle designates this
0N/A * particular file as subject to the "Classpath" exception as provided
2362N/A * by Oracle in the LICENSE file that accompanied this code.
0N/A *
0N/A * This code is distributed in the hope that it will be useful, but WITHOUT
0N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0N/A * version 2 for more details (a copy is included in the LICENSE file that
0N/A * accompanied this code).
0N/A *
0N/A * You should have received a copy of the GNU General Public License version
0N/A * 2 along with this work; if not, write to the Free Software Foundation,
0N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0N/A *
2362N/A * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
2362N/A * or visit www.oracle.com if you need additional information or have any
2362N/A * questions.
0N/A */
0N/A
0N/Apackage sun.rmi.log;
0N/A
0N/Aimport java.io.*;
0N/Aimport java.lang.reflect.Constructor;
0N/Aimport java.rmi.server.RMIClassLoader;
0N/Aimport java.security.AccessController;
0N/Aimport java.security.PrivilegedAction;
0N/Aimport sun.security.action.GetBooleanAction;
0N/Aimport sun.security.action.GetPropertyAction;
0N/A
0N/A/**
0N/A * This class is a simple implementation of a reliable Log. The
0N/A * client of a ReliableLog must provide a set of callbacks (via a
0N/A * LogHandler) that enables a ReliableLog to read and write
0N/A * checkpoints and log records. This implementation ensures that the
0N/A * current value of the data stored (via a ReliableLog) is recoverable
0N/A * after a system crash. <p>
0N/A *
0N/A * The secondary storage strategy is to record values in files using a
0N/A * representation of the caller's choosing. Two sorts of files are
0N/A * kept: snapshots and logs. At any instant, one snapshot is current.
0N/A * The log consists of a sequence of updates that have occurred since
0N/A * the current snapshot was taken. The current stable state is the
0N/A * value of the snapshot, as modified by the sequence of updates in
0N/A * the log. From time to time, the client of a ReliableLog instructs
0N/A * the package to make a new snapshot and clear the log. A ReliableLog
0N/A * arranges disk writes such that updates are stable (as long as the
0N/A * changes are force-written to disk) and atomic : no update is lost,
0N/A * and each update either is recorded completely in the log or not at
0N/A * all. Making a new snapshot is also atomic. <p>
0N/A *
0N/A * Normal use for maintaining the recoverable store is as follows: The
0N/A * client maintains the relevant data structure in virtual memory. As
0N/A * updates happen to the structure, the client informs the ReliableLog
0N/A * (all it "log") by calling log.update. Periodically, the client
0N/A * calls log.snapshot to provide the current value of the data
0N/A * structure. On restart, the client calls log.recover to obtain the
0N/A * latest snapshot and the following sequences of updates; the client
0N/A * applies the updates to the snapshot to obtain the state that
0N/A * existed before the crash. <p>
0N/A *
0N/A * The current logfile format is: <ol>
0N/A * <li> a format version number (two 4-octet integers, major and
0N/A * minor), followed by
0N/A * <li> a sequence of log records. Each log record contains, in
0N/A * order, <ol>
0N/A * <li> a 4-octet integer representing the length of the following log
0N/A * data,
0N/A * <li> the log data (variable length). </ol> </ol> <p>
0N/A *
0N/A * @see LogHandler
0N/A *
0N/A * @author Ann Wollrath
0N/A *
0N/A */
0N/Apublic class ReliableLog {
0N/A
0N/A public final static int PreferredMajorVersion = 0;
0N/A public final static int PreferredMinorVersion = 2;
0N/A
0N/A // sun.rmi.log.debug=false
0N/A private boolean Debug = false;
0N/A
0N/A private static String snapshotPrefix = "Snapshot.";
0N/A private static String logfilePrefix = "Logfile.";
0N/A private static String versionFile = "Version_Number";
0N/A private static String newVersionFile = "New_Version_Number";
0N/A private static int intBytes = 4;
0N/A private static long diskPageSize = 512;
0N/A
0N/A private File dir; // base directory
0N/A private int version = 0; // current snapshot and log version
0N/A private String logName = null;
0N/A private LogFile log = null;
0N/A private long snapshotBytes = 0;
0N/A private long logBytes = 0;
0N/A private int logEntries = 0;
0N/A private long lastSnapshot = 0;
0N/A private long lastLog = 0;
0N/A //private long padBoundary = intBytes;
0N/A private LogHandler handler;
0N/A private final byte[] intBuf = new byte[4];
0N/A
0N/A // format version numbers read from/written to this.log
0N/A private int majorFormatVersion = 0;
0N/A private int minorFormatVersion = 0;
0N/A
0N/A
0N/A /**
0N/A * Constructor for the log file. If the system property
0N/A * sun.rmi.log.class is non-null and the class specified by this
0N/A * property a) can be loaded, b) is a subclass of LogFile, and c) has a
0N/A * public two-arg constructor (String, String), ReliableLog uses the
0N/A * constructor to construct the LogFile.
0N/A **/
0N/A private static final Constructor<? extends LogFile>
0N/A logClassConstructor = getLogClassConstructor();
0N/A
0N/A /**
0N/A * Creates a ReliableLog to handle checkpoints and logging in a
0N/A * stable storage directory.
0N/A *
0N/A * @param dirPath path to the stable storage directory
0N/A * @param logCl the closure object containing callbacks for logging and
0N/A * recovery
0N/A * @param pad ignored
0N/A * @exception IOException If a directory creation error has
0N/A * occurred or if initialSnapshot callback raises an exception or
0N/A * if an exception occurs during invocation of the handler's
0N/A * snapshot method or if other IOException occurs.
0N/A */
0N/A public ReliableLog(String dirPath,
0N/A LogHandler handler,
0N/A boolean pad)
0N/A throws IOException
0N/A {
0N/A super();
28N/A this.Debug = AccessController.doPrivileged(
28N/A new GetBooleanAction("sun.rmi.log.debug")).booleanValue();
0N/A dir = new File(dirPath);
0N/A if (!(dir.exists() && dir.isDirectory())) {
0N/A // create directory
0N/A if (!dir.mkdir()) {
0N/A throw new IOException("could not create directory for log: " +
0N/A dirPath);
0N/A }
0N/A }
0N/A //padBoundary = (pad ? diskPageSize : intBytes);
0N/A this.handler = handler;
0N/A lastSnapshot = 0;
0N/A lastLog = 0;
0N/A getVersion();
0N/A if (version == 0) {
0N/A try {
0N/A snapshot(handler.initialSnapshot());
0N/A } catch (IOException e) {
0N/A throw e;
0N/A } catch (Exception e) {
0N/A throw new IOException("initial snapshot failed with " +
0N/A "exception: " + e);
0N/A }
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Creates a ReliableLog to handle checkpoints and logging in a
0N/A * stable storage directory.
0N/A *
0N/A * @param dirPath path to the stable storage directory
0N/A * @param logCl the closure object containing callbacks for logging and
0N/A * recovery
0N/A * @exception IOException If a directory creation error has
0N/A * occurred or if initialSnapshot callback raises an exception
0N/A */
0N/A public ReliableLog(String dirPath,
0N/A LogHandler handler)
0N/A throws IOException
0N/A {
0N/A this(dirPath, handler, false);
0N/A }
0N/A
0N/A /* public methods */
0N/A
0N/A /**
0N/A * Returns an object which is the value recorded in the current
0N/A * snapshot. This snapshot is recovered by calling the client
0N/A * supplied callback "recover" and then subsequently invoking
0N/A * the "readUpdate" callback to apply any logged updates to the state.
0N/A *
0N/A * @exception IOException If recovery fails due to serious log
0N/A * corruption, read update failure, or if an exception occurs
0N/A * during the recover callback
0N/A */
0N/A public synchronized Object recover()
0N/A throws IOException
0N/A {
0N/A if (Debug)
0N/A System.err.println("log.debug: recover()");
0N/A
0N/A if (version == 0)
0N/A return null;
0N/A
0N/A Object snapshot;
0N/A String fname = versionName(snapshotPrefix);
0N/A File snapshotFile = new File(fname);
0N/A InputStream in =
0N/A new BufferedInputStream(new FileInputStream(snapshotFile));
0N/A
0N/A if (Debug)
0N/A System.err.println("log.debug: recovering from " + fname);
0N/A
0N/A try {
0N/A try {
0N/A snapshot = handler.recover(in);
0N/A
0N/A } catch (IOException e) {
0N/A throw e;
0N/A } catch (Exception e) {
0N/A if (Debug)
0N/A System.err.println("log.debug: recovery failed: " + e);
0N/A throw new IOException("log recover failed with " +
0N/A "exception: " + e);
0N/A }
0N/A snapshotBytes = snapshotFile.length();
0N/A } finally {
0N/A in.close();
0N/A }
0N/A
0N/A return recoverUpdates(snapshot);
0N/A }
0N/A
0N/A /**
0N/A * Records this update in the log file (does not force update to disk).
0N/A * The update is recorded by calling the client's "writeUpdate" callback.
0N/A * This method must not be called until this log's recover method has
0N/A * been invoked (and completed).
0N/A *
0N/A * @param value the object representing the update
0N/A * @exception IOException If an exception occurred during a
0N/A * writeUpdate callback or if other I/O error has occurred.
0N/A */
0N/A public synchronized void update(Object value) throws IOException {
0N/A update(value, true);
0N/A }
0N/A
0N/A /**
0N/A * Records this update in the log file. The update is recorded by
0N/A * calling the client's writeUpdate callback. This method must not be
0N/A * called until this log's recover method has been invoked
0N/A * (and completed).
0N/A *
0N/A * @param value the object representing the update
0N/A * @param forceToDisk ignored; changes are always forced to disk
0N/A * @exception IOException If force-write to log failed or an
0N/A * exception occurred during the writeUpdate callback or if other
0N/A * I/O error occurs while updating the log.
0N/A */
0N/A public synchronized void update(Object value, boolean forceToDisk)
0N/A throws IOException
0N/A {
0N/A // avoid accessing a null log field.
0N/A if (log == null) {
0N/A throw new IOException("log is inaccessible, " +
0N/A "it may have been corrupted or closed");
0N/A }
0N/A
0N/A /*
0N/A * If the entry length field spans a sector boundary, write
0N/A * the high order bit of the entry length, otherwise write zero for
0N/A * the entry length.
0N/A */
0N/A long entryStart = log.getFilePointer();
0N/A boolean spansBoundary = log.checkSpansBoundary(entryStart);
0N/A writeInt(log, spansBoundary? 1<<31 : 0);
0N/A
0N/A /*
0N/A * Write update, and sync.
0N/A */
0N/A try {
0N/A handler.writeUpdate(new LogOutputStream(log), value);
0N/A } catch (IOException e) {
0N/A throw e;
0N/A } catch (Exception e) {
0N/A throw (IOException)
0N/A new IOException("write update failed").initCause(e);
0N/A }
0N/A log.sync();
0N/A
0N/A long entryEnd = log.getFilePointer();
0N/A int updateLen = (int) ((entryEnd - entryStart) - intBytes);
0N/A log.seek(entryStart);
0N/A
0N/A if (spansBoundary) {
0N/A /*
0N/A * If length field spans a sector boundary, then
0N/A * the next two steps are required (see 4652922):
0N/A *
0N/A * 1) Write actual length with high order bit set; sync.
0N/A * 2) Then clear high order bit of length; sync.
0N/A */
0N/A writeInt(log, updateLen | 1<<31);
0N/A log.sync();
0N/A
0N/A log.seek(entryStart);
0N/A log.writeByte(updateLen >> 24);
0N/A log.sync();
0N/A
0N/A } else {
0N/A /*
0N/A * Write actual length; sync.
0N/A */
0N/A writeInt(log, updateLen);
0N/A log.sync();
0N/A }
0N/A
0N/A log.seek(entryEnd);
0N/A logBytes = entryEnd;
0N/A lastLog = System.currentTimeMillis();
0N/A logEntries++;
0N/A }
0N/A
0N/A /**
0N/A * Returns the constructor for the log file if the system property
0N/A * sun.rmi.log.class is non-null and the class specified by the
0N/A * property a) can be loaded, b) is a subclass of LogFile, and c) has a
0N/A * public two-arg constructor (String, String); otherwise returns null.
0N/A **/
0N/A private static Constructor<? extends LogFile>
0N/A getLogClassConstructor() {
0N/A
28N/A String logClassName = AccessController.doPrivileged(
28N/A new GetPropertyAction("sun.rmi.log.class"));
0N/A if (logClassName != null) {
0N/A try {
0N/A ClassLoader loader =
0N/A AccessController.doPrivileged(
0N/A new PrivilegedAction<ClassLoader>() {
0N/A public ClassLoader run() {
0N/A return ClassLoader.getSystemClassLoader();
0N/A }
0N/A });
5559N/A Class<? extends LogFile> cl =
5559N/A loader.loadClass(logClassName).asSubclass(LogFile.class);
5559N/A return cl.getConstructor(String.class, String.class);
0N/A } catch (Exception e) {
0N/A System.err.println("Exception occurred:");
0N/A e.printStackTrace();
0N/A }
0N/A }
0N/A return null;
0N/A }
0N/A
0N/A /**
0N/A * Records this value as the current snapshot by invoking the client
0N/A * supplied "snapshot" callback and then empties the log.
0N/A *
0N/A * @param value the object representing the new snapshot
0N/A * @exception IOException If an exception occurred during the
0N/A * snapshot callback or if other I/O error has occurred during the
0N/A * snapshot process
0N/A */
0N/A public synchronized void snapshot(Object value)
0N/A throws IOException
0N/A {
0N/A int oldVersion = version;
0N/A incrVersion();
0N/A
0N/A String fname = versionName(snapshotPrefix);
0N/A File snapshotFile = new File(fname);
0N/A FileOutputStream out = new FileOutputStream(snapshotFile);
0N/A try {
0N/A try {
0N/A handler.snapshot(out, value);
0N/A } catch (IOException e) {
0N/A throw e;
0N/A } catch (Exception e) {
4105N/A throw new IOException("snapshot failed", e);
0N/A }
0N/A lastSnapshot = System.currentTimeMillis();
0N/A } finally {
0N/A out.close();
0N/A snapshotBytes = snapshotFile.length();
0N/A }
0N/A
0N/A openLogFile(true);
0N/A writeVersionFile(true);
0N/A commitToNewVersion();
0N/A deleteSnapshot(oldVersion);
0N/A deleteLogFile(oldVersion);
0N/A }
0N/A
0N/A /**
0N/A * Close the stable storage directory in an orderly manner.
0N/A *
0N/A * @exception IOException If an I/O error occurs when the log is
0N/A * closed
0N/A */
0N/A public synchronized void close() throws IOException {
0N/A if (log == null) return;
0N/A try {
0N/A log.close();
0N/A } finally {
0N/A log = null;
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Returns the size of the snapshot file in bytes;
0N/A */
0N/A public long snapshotSize() {
0N/A return snapshotBytes;
0N/A }
0N/A
0N/A /**
0N/A * Returns the size of the log file in bytes;
0N/A */
0N/A public long logSize() {
0N/A return logBytes;
0N/A }
0N/A
0N/A /* private methods */
0N/A
0N/A /**
0N/A * Write an int value in single write operation. This method
0N/A * assumes that the caller is synchronized on the log file.
0N/A *
0N/A * @param out output stream
0N/A * @param val int value
0N/A * @throws IOException if any other I/O error occurs
0N/A */
0N/A private void writeInt(DataOutput out, int val)
0N/A throws IOException
0N/A {
0N/A intBuf[0] = (byte) (val >> 24);
0N/A intBuf[1] = (byte) (val >> 16);
0N/A intBuf[2] = (byte) (val >> 8);
0N/A intBuf[3] = (byte) val;
0N/A out.write(intBuf);
0N/A }
0N/A
0N/A /**
0N/A * Generates a filename prepended with the stable storage directory path.
0N/A *
0N/A * @param name the leaf name of the file
0N/A */
0N/A private String fName(String name) {
0N/A return dir.getPath() + File.separator + name;
0N/A }
0N/A
0N/A /**
0N/A * Generates a version 0 filename prepended with the stable storage
0N/A * directory path
0N/A *
0N/A * @param name version file name
0N/A */
0N/A private String versionName(String name) {
0N/A return versionName(name, 0);
0N/A }
0N/A
0N/A /**
0N/A * Generates a version filename prepended with the stable storage
0N/A * directory path with the version number as a suffix.
0N/A *
0N/A * @param name version file name
0N/A * @thisversion a version number
0N/A */
0N/A private String versionName(String prefix, int ver) {
0N/A ver = (ver == 0) ? version : ver;
0N/A return fName(prefix) + String.valueOf(ver);
0N/A }
0N/A
0N/A /**
0N/A * Increments the directory version number.
0N/A */
0N/A private void incrVersion() {
0N/A do { version++; } while (version==0);
0N/A }
0N/A
0N/A /**
0N/A * Delete a file.
0N/A *
0N/A * @param name the name of the file
0N/A * @exception IOException If new version file couldn't be removed
0N/A */
0N/A private void deleteFile(String name) throws IOException {
0N/A
0N/A File f = new File(name);
0N/A if (!f.delete())
0N/A throw new IOException("couldn't remove file: " + name);
0N/A }
0N/A
0N/A /**
0N/A * Removes the new version number file.
0N/A *
0N/A * @exception IOException If an I/O error has occurred.
0N/A */
0N/A private void deleteNewVersionFile() throws IOException {
0N/A deleteFile(fName(newVersionFile));
0N/A }
0N/A
0N/A /**
0N/A * Removes the snapshot file.
0N/A *
0N/A * @param ver the version to remove
0N/A * @exception IOException If an I/O error has occurred.
0N/A */
0N/A private void deleteSnapshot(int ver) throws IOException {
0N/A if (ver == 0) return;
0N/A deleteFile(versionName(snapshotPrefix, ver));
0N/A }
0N/A
0N/A /**
0N/A * Removes the log file.
0N/A *
0N/A * @param ver the version to remove
0N/A * @exception IOException If an I/O error has occurred.
0N/A */
0N/A private void deleteLogFile(int ver) throws IOException {
0N/A if (ver == 0) return;
0N/A deleteFile(versionName(logfilePrefix, ver));
0N/A }
0N/A
0N/A /**
0N/A * Opens the log file in read/write mode. If file does not exist, it is
0N/A * created.
0N/A *
0N/A * @param truncate if true and file exists, file is truncated to zero
0N/A * length
0N/A * @exception IOException If an I/O error has occurred.
0N/A */
0N/A private void openLogFile(boolean truncate) throws IOException {
0N/A try {
0N/A close();
0N/A } catch (IOException e) { /* assume this is okay */
0N/A }
0N/A
0N/A logName = versionName(logfilePrefix);
0N/A
0N/A try {
0N/A log = (logClassConstructor == null ?
0N/A new LogFile(logName, "rw") :
0N/A logClassConstructor.newInstance(logName, "rw"));
0N/A } catch (Exception e) {
0N/A throw (IOException) new IOException(
0N/A "unable to construct LogFile instance").initCause(e);
0N/A }
0N/A
0N/A if (truncate) {
0N/A initializeLogFile();
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Creates a new log file, truncated and initialized with the format
0N/A * version number preferred by this implementation.
0N/A * <p>Environment: inited, synchronized
0N/A * <p>Precondition: valid: log, log contains nothing useful
0N/A * <p>Postcondition: if successful, log is initialised with the format
0N/A * version number (Preferred{Major,Minor}Version), and logBytes is
0N/A * set to the resulting size of the updatelog, and logEntries is set to
0N/A * zero. Otherwise, log is in an indeterminate state, and logBytes
0N/A * is unchanged, and logEntries is unchanged.
0N/A *
0N/A * @exception IOException If an I/O error has occurred.
0N/A */
0N/A private void initializeLogFile()
0N/A throws IOException
0N/A {
0N/A log.setLength(0);
0N/A majorFormatVersion = PreferredMajorVersion;
0N/A writeInt(log, PreferredMajorVersion);
0N/A minorFormatVersion = PreferredMinorVersion;
0N/A writeInt(log, PreferredMinorVersion);
0N/A logBytes = intBytes * 2;
0N/A logEntries = 0;
0N/A }
0N/A
0N/A
0N/A /**
0N/A * Writes out version number to file.
0N/A *
0N/A * @param newVersion if true, writes to a new version file
0N/A * @exception IOException If an I/O error has occurred.
0N/A */
0N/A private void writeVersionFile(boolean newVersion) throws IOException {
0N/A String name;
0N/A if (newVersion) {
0N/A name = newVersionFile;
0N/A } else {
0N/A name = versionFile;
0N/A }
5559N/A try (FileOutputStream fos = new FileOutputStream(fName(name));
5559N/A DataOutputStream out = new DataOutputStream(fos)) {
5559N/A writeInt(out, version);
5559N/A }
0N/A }
0N/A
0N/A /**
0N/A * Creates the initial version file
0N/A *
0N/A * @exception IOException If an I/O error has occurred.
0N/A */
0N/A private void createFirstVersion() throws IOException {
0N/A version = 0;
0N/A writeVersionFile(false);
0N/A }
0N/A
0N/A /**
0N/A * Commits (atomically) the new version.
0N/A *
0N/A * @exception IOException If an I/O error has occurred.
0N/A */
0N/A private void commitToNewVersion() throws IOException {
0N/A writeVersionFile(false);
0N/A deleteNewVersionFile();
0N/A }
0N/A
0N/A /**
0N/A * Reads version number from a file.
0N/A *
0N/A * @param name the name of the version file
0N/A * @return the version
0N/A * @exception IOException If an I/O error has occurred.
0N/A */
0N/A private int readVersion(String name) throws IOException {
5559N/A try (DataInputStream in = new DataInputStream
5559N/A (new FileInputStream(name))) {
0N/A return in.readInt();
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Sets the version. If version file does not exist, the initial
0N/A * version file is created.
0N/A *
0N/A * @exception IOException If an I/O error has occurred.
0N/A */
0N/A private void getVersion() throws IOException {
0N/A try {
0N/A version = readVersion(fName(newVersionFile));
0N/A commitToNewVersion();
0N/A } catch (IOException e) {
0N/A try {
0N/A deleteNewVersionFile();
0N/A }
0N/A catch (IOException ex) {
0N/A }
0N/A
0N/A try {
0N/A version = readVersion(fName(versionFile));
0N/A }
0N/A catch (IOException ex) {
0N/A createFirstVersion();
0N/A }
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Applies outstanding updates to the snapshot.
0N/A *
0N/A * @param state the most recent snapshot
0N/A * @exception IOException If serious log corruption is detected or
0N/A * if an exception occurred during a readUpdate callback or if
0N/A * other I/O error has occurred.
0N/A * @return the resulting state of the object after all updates
0N/A */
0N/A private Object recoverUpdates(Object state)
0N/A throws IOException
0N/A {
0N/A logBytes = 0;
0N/A logEntries = 0;
0N/A
0N/A if (version == 0) return state;
0N/A
0N/A String fname = versionName(logfilePrefix);
0N/A InputStream in =
0N/A new BufferedInputStream(new FileInputStream(fname));
0N/A DataInputStream dataIn = new DataInputStream(in);
0N/A
0N/A if (Debug)
0N/A System.err.println("log.debug: reading updates from " + fname);
0N/A
0N/A try {
0N/A majorFormatVersion = dataIn.readInt(); logBytes += intBytes;
0N/A minorFormatVersion = dataIn.readInt(); logBytes += intBytes;
0N/A } catch (EOFException e) {
0N/A /* This is a log which was corrupted and/or cleared (by
0N/A * fsck or equivalent). This is not an error.
0N/A */
0N/A openLogFile(true); // create and truncate
0N/A in = null;
0N/A }
0N/A /* A new major version number is a catastrophe (it means
0N/A * that the file format is incompatible with older
0N/A * clients, and we'll only be breaking things by trying to
0N/A * use the log). A new minor version is no big deal for
0N/A * upward compatibility.
0N/A */
0N/A if (majorFormatVersion != PreferredMajorVersion) {
0N/A if (Debug) {
0N/A System.err.println("log.debug: major version mismatch: " +
0N/A majorFormatVersion + "." + minorFormatVersion);
0N/A }
0N/A throw new IOException("Log file " + logName + " has a " +
0N/A "version " + majorFormatVersion +
0N/A "." + minorFormatVersion +
0N/A " format, and this implementation " +
0N/A " understands only version " +
0N/A PreferredMajorVersion + "." +
0N/A PreferredMinorVersion);
0N/A }
0N/A
0N/A try {
0N/A while (in != null) {
0N/A int updateLen = 0;
0N/A
0N/A try {
0N/A updateLen = dataIn.readInt();
0N/A } catch (EOFException e) {
0N/A if (Debug)
0N/A System.err.println("log.debug: log was sync'd cleanly");
0N/A break;
0N/A }
0N/A if (updateLen <= 0) {/* crashed while writing last log entry */
0N/A if (Debug) {
0N/A System.err.println(
0N/A "log.debug: last update incomplete, " +
0N/A "updateLen = 0x" +
0N/A Integer.toHexString(updateLen));
0N/A }
0N/A break;
0N/A }
0N/A
0N/A // this is a fragile use of available() which relies on the
0N/A // twin facts that BufferedInputStream correctly consults
0N/A // the underlying stream, and that FileInputStream returns
0N/A // the number of bytes remaining in the file (via FIONREAD).
0N/A if (in.available() < updateLen) {
0N/A /* corrupted record at end of log (can happen since we
0N/A * do only one fsync)
0N/A */
0N/A if (Debug)
0N/A System.err.println("log.debug: log was truncated");
0N/A break;
0N/A }
0N/A
0N/A if (Debug)
0N/A System.err.println("log.debug: rdUpdate size " + updateLen);
0N/A try {
0N/A state = handler.readUpdate(new LogInputStream(in, updateLen),
0N/A state);
0N/A } catch (IOException e) {
0N/A throw e;
0N/A } catch (Exception e) {
0N/A e.printStackTrace();
0N/A throw new IOException("read update failed with " +
0N/A "exception: " + e);
0N/A }
0N/A logBytes += (intBytes + updateLen);
0N/A logEntries++;
0N/A } /* while */
0N/A } finally {
0N/A if (in != null)
0N/A in.close();
0N/A }
0N/A
0N/A if (Debug)
0N/A System.err.println("log.debug: recovered updates: " + logEntries);
0N/A
0N/A /* reopen log file at end */
0N/A openLogFile(false);
0N/A
0N/A // avoid accessing a null log field
0N/A if (log == null) {
0N/A throw new IOException("rmid's log is inaccessible, " +
0N/A "it may have been corrupted or closed");
0N/A }
0N/A
0N/A log.seek(logBytes);
0N/A log.setLength(logBytes);
0N/A
0N/A return state;
0N/A }
0N/A
0N/A /**
0N/A * ReliableLog's log file implementation. This implementation
0N/A * is subclassable for testing purposes.
0N/A */
0N/A public static class LogFile extends RandomAccessFile {
0N/A
0N/A private final FileDescriptor fd;
0N/A
0N/A /**
0N/A * Constructs a LogFile and initializes the file descriptor.
0N/A **/
0N/A public LogFile(String name, String mode)
0N/A throws FileNotFoundException, IOException
0N/A {
0N/A super(name, mode);
0N/A this.fd = getFD();
0N/A }
0N/A
0N/A /**
0N/A * Invokes sync on the file descriptor for this log file.
0N/A */
0N/A protected void sync() throws IOException {
0N/A fd.sync();
0N/A }
0N/A
0N/A /**
0N/A * Returns true if writing 4 bytes starting at the specified file
0N/A * position, would span a 512 byte sector boundary; otherwise returns
0N/A * false.
0N/A **/
0N/A protected boolean checkSpansBoundary(long fp) {
0N/A return fp % 512 > 508;
0N/A }
0N/A }
0N/A}