1 /*
   2  * Copyright (c) 1997, 2008, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.rmi.log;
  27 
  28 import java.io.*;
  29 import java.lang.reflect.Constructor;
  30 import java.rmi.server.RMIClassLoader;
  31 import java.security.AccessController;
  32 import java.security.PrivilegedAction;
  33 import sun.security.action.GetBooleanAction;
  34 import sun.security.action.GetPropertyAction;
  35 
  36 /**
  37  * This class is a simple implementation of a reliable Log.  The
  38  * client of a ReliableLog must provide a set of callbacks (via a
  39  * LogHandler) that enables a ReliableLog to read and write
  40  * checkpoints and log records.  This implementation ensures that the
  41  * current value of the data stored (via a ReliableLog) is recoverable
  42  * after a system crash. <p>
  43  *
  44  * The secondary storage strategy is to record values in files using a
  45  * representation of the caller's choosing.  Two sorts of files are
  46  * kept: snapshots and logs.  At any instant, one snapshot is current.
  47  * The log consists of a sequence of updates that have occurred since
  48  * the current snapshot was taken.  The current stable state is the
  49  * value of the snapshot, as modified by the sequence of updates in
  50  * the log.  From time to time, the client of a ReliableLog instructs
  51  * the package to make a new snapshot and clear the log.  A ReliableLog
  52  * arranges disk writes such that updates are stable (as long as the
  53  * changes are force-written to disk) and atomic : no update is lost,
  54  * and each update either is recorded completely in the log or not at
  55  * all.  Making a new snapshot is also atomic. <p>
  56  *
  57  * Normal use for maintaining the recoverable store is as follows: The
  58  * client maintains the relevant data structure in virtual memory.  As
  59  * updates happen to the structure, the client informs the ReliableLog
  60  * (all it "log") by calling log.update.  Periodically, the client
  61  * calls log.snapshot to provide the current value of the data
  62  * structure.  On restart, the client calls log.recover to obtain the
  63  * latest snapshot and the following sequences of updates; the client
  64  * applies the updates to the snapshot to obtain the state that
  65  * existed before the crash. <p>
  66  *
  67  * The current logfile format is: <ol>
  68  * <li> a format version number (two 4-octet integers, major and
  69  * minor), followed by
  70  * <li> a sequence of log records.  Each log record contains, in
  71  * order, <ol>
  72  * <li> a 4-octet integer representing the length of the following log
  73  * data,
  74  * <li> the log data (variable length). </ol> </ol> <p>
  75  *
  76  * @see LogHandler
  77  *
  78  * @author Ann Wollrath
  79  *
  80  */
  81 public class ReliableLog {
  82 
  83     public final static int PreferredMajorVersion = 0;
  84     public final static int PreferredMinorVersion = 2;
  85 
  86     // sun.rmi.log.debug=false
  87     private boolean Debug = false;
  88 
  89     private static String snapshotPrefix = "Snapshot.";
  90     private static String logfilePrefix = "Logfile.";
  91     private static String versionFile = "Version_Number";
  92     private static String newVersionFile = "New_Version_Number";
  93     private static int    intBytes = 4;
  94     private static long   diskPageSize = 512;
  95 
  96     private File dir;                   // base directory
  97     private int version = 0;            // current snapshot and log version
  98     private String logName = null;
  99     private LogFile log = null;
 100     private long snapshotBytes = 0;
 101     private long logBytes = 0;
 102     private int logEntries = 0;
 103     private long lastSnapshot = 0;
 104     private long lastLog = 0;
 105     //private long padBoundary = intBytes;
 106     private LogHandler handler;
 107     private final byte[] intBuf = new byte[4];
 108 
 109     // format version numbers read from/written to this.log
 110     private int majorFormatVersion = 0;
 111     private int minorFormatVersion = 0;
 112 
 113 
 114     /**
 115      * Constructor for the log file.  If the system property
 116      * sun.rmi.log.class is non-null and the class specified by this
 117      * property a) can be loaded, b) is a subclass of LogFile, and c) has a
 118      * public two-arg constructor (String, String), ReliableLog uses the
 119      * constructor to construct the LogFile.
 120      **/
 121     private static final Constructor<? extends LogFile>
 122         logClassConstructor = getLogClassConstructor();
 123 
 124     /**
 125      * Creates a ReliableLog to handle checkpoints and logging in a
 126      * stable storage directory.
 127      *
 128      * @param dirPath path to the stable storage directory
 129      * @param logCl the closure object containing callbacks for logging and
 130      * recovery
 131      * @param pad ignored
 132      * @exception IOException If a directory creation error has
 133      * occurred or if initialSnapshot callback raises an exception or
 134      * if an exception occurs during invocation of the handler's
 135      * snapshot method or if other IOException occurs.
 136      */
 137     public ReliableLog(String dirPath,
 138                      LogHandler handler,
 139                      boolean pad)
 140         throws IOException
 141     {
 142         super();
 143         this.Debug = AccessController.doPrivileged(
 144             new GetBooleanAction("sun.rmi.log.debug")).booleanValue();
 145         dir = new File(dirPath);
 146         if (!(dir.exists() && dir.isDirectory())) {
 147             // create directory
 148             if (!dir.mkdir()) {
 149                 throw new IOException("could not create directory for log: " +
 150                                       dirPath);
 151             }
 152         }
 153         //padBoundary = (pad ? diskPageSize : intBytes);
 154         this.handler = handler;
 155         lastSnapshot = 0;
 156         lastLog = 0;
 157         getVersion();
 158         if (version == 0) {
 159             try {
 160                 snapshot(handler.initialSnapshot());
 161             } catch (IOException e) {
 162                 throw e;
 163             } catch (Exception e) {
 164                 throw new IOException("initial snapshot failed with " +
 165                                       "exception: " + e);
 166             }
 167         }
 168     }
 169 
 170     /**
 171      * Creates a ReliableLog to handle checkpoints and logging in a
 172      * stable storage directory.
 173      *
 174      * @param dirPath path to the stable storage directory
 175      * @param logCl the closure object containing callbacks for logging and
 176      * recovery
 177      * @exception IOException If a directory creation error has
 178      * occurred or if initialSnapshot callback raises an exception
 179      */
 180     public ReliableLog(String dirPath,
 181                      LogHandler handler)
 182         throws IOException
 183     {
 184         this(dirPath, handler, false);
 185     }
 186 
 187     /* public methods */
 188 
 189     /**
 190      * Returns an object which is the value recorded in the current
 191      * snapshot.  This snapshot is recovered by calling the client
 192      * supplied callback "recover" and then subsequently invoking
 193      * the "readUpdate" callback to apply any logged updates to the state.
 194      *
 195      * @exception IOException If recovery fails due to serious log
 196      * corruption, read update failure, or if an exception occurs
 197      * during the recover callback
 198      */
 199     public synchronized Object recover()
 200         throws IOException
 201     {
 202         if (Debug)
 203             System.err.println("log.debug: recover()");
 204 
 205         if (version == 0)
 206             return null;
 207 
 208         Object snapshot;
 209         String fname = versionName(snapshotPrefix);
 210         File snapshotFile = new File(fname);
 211         InputStream in =
 212                 new BufferedInputStream(new FileInputStream(snapshotFile));
 213 
 214         if (Debug)
 215             System.err.println("log.debug: recovering from " + fname);
 216 
 217         try {
 218             try {
 219                 snapshot = handler.recover(in);
 220 
 221             } catch (IOException e) {
 222                 throw e;
 223             } catch (Exception e) {
 224                 if (Debug)
 225                     System.err.println("log.debug: recovery failed: " + e);
 226                 throw new IOException("log recover failed with " +
 227                                       "exception: " + e);
 228             }
 229             snapshotBytes = snapshotFile.length();
 230         } finally {
 231             in.close();
 232         }
 233 
 234         return recoverUpdates(snapshot);
 235     }
 236 
 237     /**
 238      * Records this update in the log file (does not force update to disk).
 239      * The update is recorded by calling the client's "writeUpdate" callback.
 240      * This method must not be called until this log's recover method has
 241      * been invoked (and completed).
 242      *
 243      * @param value the object representing the update
 244      * @exception IOException If an exception occurred during a
 245      * writeUpdate callback or if other I/O error has occurred.
 246      */
 247     public synchronized void update(Object value) throws IOException {
 248         update(value, true);
 249     }
 250 
 251     /**
 252      * Records this update in the log file.  The update is recorded by
 253      * calling the client's writeUpdate callback.  This method must not be
 254      * called until this log's recover method has been invoked
 255      * (and completed).
 256      *
 257      * @param value the object representing the update
 258      * @param forceToDisk ignored; changes are always forced to disk
 259      * @exception IOException If force-write to log failed or an
 260      * exception occurred during the writeUpdate callback or if other
 261      * I/O error occurs while updating the log.
 262      */
 263     public synchronized void update(Object value, boolean forceToDisk)
 264         throws IOException
 265     {
 266         // avoid accessing a null log field.
 267         if (log == null) {
 268             throw new IOException("log is inaccessible, " +
 269                 "it may have been corrupted or closed");
 270         }
 271 
 272         /*
 273          * If the entry length field spans a sector boundary, write
 274          * the high order bit of the entry length, otherwise write zero for
 275          * the entry length.
 276          */
 277         long entryStart = log.getFilePointer();
 278         boolean spansBoundary = log.checkSpansBoundary(entryStart);
 279         writeInt(log, spansBoundary? 1<<31 : 0);
 280 
 281         /*
 282          * Write update, and sync.
 283          */
 284         try {
 285             handler.writeUpdate(new LogOutputStream(log), value);
 286         } catch (IOException e) {
 287             throw e;
 288         } catch (Exception e) {
 289             throw (IOException)
 290                 new IOException("write update failed").initCause(e);
 291         }
 292         log.sync();
 293 
 294         long entryEnd = log.getFilePointer();
 295         int updateLen = (int) ((entryEnd - entryStart) - intBytes);
 296         log.seek(entryStart);
 297 
 298         if (spansBoundary) {
 299             /*
 300              * If length field spans a sector boundary, then
 301              * the next two steps are required (see 4652922):
 302              *
 303              * 1) Write actual length with high order bit set; sync.
 304              * 2) Then clear high order bit of length; sync.
 305              */
 306             writeInt(log, updateLen | 1<<31);
 307             log.sync();
 308 
 309             log.seek(entryStart);
 310             log.writeByte(updateLen >> 24);
 311             log.sync();
 312 
 313         } else {
 314             /*
 315              * Write actual length; sync.
 316              */
 317             writeInt(log, updateLen);
 318             log.sync();
 319         }
 320 
 321         log.seek(entryEnd);
 322         logBytes = entryEnd;
 323         lastLog = System.currentTimeMillis();
 324         logEntries++;
 325     }
 326 
 327     /**
 328      * Returns the constructor for the log file if the system property
 329      * sun.rmi.log.class is non-null and the class specified by the
 330      * property a) can be loaded, b) is a subclass of LogFile, and c) has a
 331      * public two-arg constructor (String, String); otherwise returns null.
 332      **/
 333     private static Constructor<? extends LogFile>
 334         getLogClassConstructor() {
 335 
 336         String logClassName = AccessController.doPrivileged(
 337             new GetPropertyAction("sun.rmi.log.class"));
 338         if (logClassName != null) {
 339             try {
 340                 ClassLoader loader =
 341                     AccessController.doPrivileged(
 342                         new PrivilegedAction<ClassLoader>() {
 343                             public ClassLoader run() {
 344                                return ClassLoader.getSystemClassLoader();
 345                             }
 346                         });
 347                 Class cl = loader.loadClass(logClassName);
 348                 if (LogFile.class.isAssignableFrom(cl)) {
 349                     return cl.getConstructor(String.class, String.class);
 350                 }
 351             } catch (Exception e) {
 352                 System.err.println("Exception occurred:");
 353                 e.printStackTrace();
 354             }
 355         }
 356         return null;
 357     }
 358 
 359     /**
 360      * Records this value as the current snapshot by invoking the client
 361      * supplied "snapshot" callback and then empties the log.
 362      *
 363      * @param value the object representing the new snapshot
 364      * @exception IOException If an exception occurred during the
 365      * snapshot callback or if other I/O error has occurred during the
 366      * snapshot process
 367      */
 368     public synchronized void snapshot(Object value)
 369         throws IOException
 370     {
 371         int oldVersion = version;
 372         incrVersion();
 373 
 374         String fname = versionName(snapshotPrefix);
 375         File snapshotFile = new File(fname);
 376         FileOutputStream out = new FileOutputStream(snapshotFile);
 377         try {
 378             try {
 379                 handler.snapshot(out, value);
 380             } catch (IOException e) {
 381                 throw e;
 382             } catch (Exception e) {
 383                 throw new IOException("snapshot failed with exception of type: " +
 384                                       e.getClass().getName() +
 385                                       ", message was: " + e.getMessage());
 386             }
 387             lastSnapshot = System.currentTimeMillis();
 388         } finally {
 389             out.close();
 390             snapshotBytes = snapshotFile.length();
 391         }
 392 
 393         openLogFile(true);
 394         writeVersionFile(true);
 395         commitToNewVersion();
 396         deleteSnapshot(oldVersion);
 397         deleteLogFile(oldVersion);
 398     }
 399 
 400     /**
 401      * Close the stable storage directory in an orderly manner.
 402      *
 403      * @exception IOException If an I/O error occurs when the log is
 404      * closed
 405      */
 406     public synchronized void close() throws IOException {
 407         if (log == null) return;
 408         try {
 409             log.close();
 410         } finally {
 411             log = null;
 412         }
 413     }
 414 
 415     /**
 416      * Returns the size of the snapshot file in bytes;
 417      */
 418     public long snapshotSize() {
 419         return snapshotBytes;
 420     }
 421 
 422     /**
 423      * Returns the size of the log file in bytes;
 424      */
 425     public long logSize() {
 426         return logBytes;
 427     }
 428 
 429     /* private methods */
 430 
 431     /**
 432      * Write an int value in single write operation.  This method
 433      * assumes that the caller is synchronized on the log file.
 434      *
 435      * @param out output stream
 436      * @param val int value
 437      * @throws IOException if any other I/O error occurs
 438      */
 439     private void writeInt(DataOutput out, int val)
 440         throws IOException
 441     {
 442         intBuf[0] = (byte) (val >> 24);
 443         intBuf[1] = (byte) (val >> 16);
 444         intBuf[2] = (byte) (val >> 8);
 445         intBuf[3] = (byte) val;
 446         out.write(intBuf);
 447     }
 448 
 449     /**
 450      * Generates a filename prepended with the stable storage directory path.
 451      *
 452      * @param name the leaf name of the file
 453      */
 454     private String fName(String name) {
 455         return dir.getPath() + File.separator + name;
 456     }
 457 
 458     /**
 459      * Generates a version 0 filename prepended with the stable storage
 460      * directory path
 461      *
 462      * @param name version file name
 463      */
 464     private String versionName(String name) {
 465         return versionName(name, 0);
 466     }
 467 
 468     /**
 469      * Generates a version filename prepended with the stable storage
 470      * directory path with the version number as a suffix.
 471      *
 472      * @param name version file name
 473      * @thisversion a version number
 474      */
 475     private String versionName(String prefix, int ver) {
 476         ver = (ver == 0) ? version : ver;
 477         return fName(prefix) + String.valueOf(ver);
 478     }
 479 
 480     /**
 481      * Increments the directory version number.
 482      */
 483     private void incrVersion() {
 484         do { version++; } while (version==0);
 485     }
 486 
 487     /**
 488      * Delete a file.
 489      *
 490      * @param name the name of the file
 491      * @exception IOException If new version file couldn't be removed
 492      */
 493     private void deleteFile(String name) throws IOException {
 494 
 495         File f = new File(name);
 496         if (!f.delete())
 497             throw new IOException("couldn't remove file: " + name);
 498     }
 499 
 500     /**
 501      * Removes the new version number file.
 502      *
 503      * @exception IOException If an I/O error has occurred.
 504      */
 505     private void deleteNewVersionFile() throws IOException {
 506         deleteFile(fName(newVersionFile));
 507     }
 508 
 509     /**
 510      * Removes the snapshot file.
 511      *
 512      * @param ver the version to remove
 513      * @exception IOException If an I/O error has occurred.
 514      */
 515     private void deleteSnapshot(int ver) throws IOException {
 516         if (ver == 0) return;
 517         deleteFile(versionName(snapshotPrefix, ver));
 518     }
 519 
 520     /**
 521      * Removes the log file.
 522      *
 523      * @param ver the version to remove
 524      * @exception IOException If an I/O error has occurred.
 525      */
 526     private void deleteLogFile(int ver) throws IOException {
 527         if (ver == 0) return;
 528         deleteFile(versionName(logfilePrefix, ver));
 529     }
 530 
 531     /**
 532      * Opens the log file in read/write mode.  If file does not exist, it is
 533      * created.
 534      *
 535      * @param truncate if true and file exists, file is truncated to zero
 536      * length
 537      * @exception IOException If an I/O error has occurred.
 538      */
 539     private void openLogFile(boolean truncate) throws IOException {
 540         try {
 541             close();
 542         } catch (IOException e) { /* assume this is okay */
 543         }
 544 
 545         logName = versionName(logfilePrefix);
 546 
 547         try {
 548             log = (logClassConstructor == null ?
 549                    new LogFile(logName, "rw") :
 550                    logClassConstructor.newInstance(logName, "rw"));
 551         } catch (Exception e) {
 552             throw (IOException) new IOException(
 553                 "unable to construct LogFile instance").initCause(e);
 554         }
 555 
 556         if (truncate) {
 557             initializeLogFile();
 558         }
 559     }
 560 
 561     /**
 562      * Creates a new log file, truncated and initialized with the format
 563      * version number preferred by this implementation.
 564      * <p>Environment: inited, synchronized
 565      * <p>Precondition: valid: log, log contains nothing useful
 566      * <p>Postcondition: if successful, log is initialised with the format
 567      * version number (Preferred{Major,Minor}Version), and logBytes is
 568      * set to the resulting size of the updatelog, and logEntries is set to
 569      * zero.  Otherwise, log is in an indeterminate state, and logBytes
 570      * is unchanged, and logEntries is unchanged.
 571      *
 572      * @exception IOException If an I/O error has occurred.
 573      */
 574     private void initializeLogFile()
 575         throws IOException
 576     {
 577         log.setLength(0);
 578         majorFormatVersion = PreferredMajorVersion;
 579         writeInt(log, PreferredMajorVersion);
 580         minorFormatVersion = PreferredMinorVersion;
 581         writeInt(log, PreferredMinorVersion);
 582         logBytes = intBytes * 2;
 583         logEntries = 0;
 584     }
 585 
 586 
 587     /**
 588      * Writes out version number to file.
 589      *
 590      * @param newVersion if true, writes to a new version file
 591      * @exception IOException If an I/O error has occurred.
 592      */
 593     private void writeVersionFile(boolean newVersion) throws IOException {
 594         String name;
 595         if (newVersion) {
 596             name = newVersionFile;
 597         } else {
 598             name = versionFile;
 599         }
 600         DataOutputStream out =
 601             new DataOutputStream(new FileOutputStream(fName(name)));
 602         writeInt(out, version);
 603         out.close();
 604     }
 605 
 606     /**
 607      * Creates the initial version file
 608      *
 609      * @exception IOException If an I/O error has occurred.
 610      */
 611     private void createFirstVersion() throws IOException {
 612         version = 0;
 613         writeVersionFile(false);
 614     }
 615 
 616     /**
 617      * Commits (atomically) the new version.
 618      *
 619      * @exception IOException If an I/O error has occurred.
 620      */
 621     private void commitToNewVersion() throws IOException {
 622         writeVersionFile(false);
 623         deleteNewVersionFile();
 624     }
 625 
 626     /**
 627      * Reads version number from a file.
 628      *
 629      * @param name the name of the version file
 630      * @return the version
 631      * @exception IOException If an I/O error has occurred.
 632      */
 633     private int readVersion(String name) throws IOException {
 634         DataInputStream in = new DataInputStream(new FileInputStream(name));
 635         try {
 636             return in.readInt();
 637         } finally {
 638             in.close();
 639         }
 640     }
 641 
 642     /**
 643      * Sets the version.  If version file does not exist, the initial
 644      * version file is created.
 645      *
 646      * @exception IOException If an I/O error has occurred.
 647      */
 648     private void getVersion() throws IOException {
 649         try {
 650             version = readVersion(fName(newVersionFile));
 651             commitToNewVersion();
 652         } catch (IOException e) {
 653             try {
 654                 deleteNewVersionFile();
 655             }
 656             catch (IOException ex) {
 657             }
 658 
 659             try {
 660                 version = readVersion(fName(versionFile));
 661             }
 662             catch (IOException ex) {
 663                 createFirstVersion();
 664             }
 665         }
 666     }
 667 
 668     /**
 669      * Applies outstanding updates to the snapshot.
 670      *
 671      * @param state the most recent snapshot
 672      * @exception IOException If serious log corruption is detected or
 673      * if an exception occurred during a readUpdate callback or if
 674      * other I/O error has occurred.
 675      * @return the resulting state of the object after all updates
 676      */
 677     private Object recoverUpdates(Object state)
 678         throws IOException
 679     {
 680         logBytes = 0;
 681         logEntries = 0;
 682 
 683         if (version == 0) return state;
 684 
 685         String fname = versionName(logfilePrefix);
 686         InputStream in =
 687                 new BufferedInputStream(new FileInputStream(fname));
 688         DataInputStream dataIn = new DataInputStream(in);
 689 
 690         if (Debug)
 691             System.err.println("log.debug: reading updates from " + fname);
 692 
 693         try {
 694             majorFormatVersion = dataIn.readInt(); logBytes += intBytes;
 695             minorFormatVersion = dataIn.readInt(); logBytes += intBytes;
 696         } catch (EOFException e) {
 697             /* This is a log which was corrupted and/or cleared (by
 698              * fsck or equivalent).  This is not an error.
 699              */
 700             openLogFile(true);  // create and truncate
 701             in = null;
 702         }
 703         /* A new major version number is a catastrophe (it means
 704          * that the file format is incompatible with older
 705          * clients, and we'll only be breaking things by trying to
 706          * use the log).  A new minor version is no big deal for
 707          * upward compatibility.
 708          */
 709         if (majorFormatVersion != PreferredMajorVersion) {
 710             if (Debug) {
 711                 System.err.println("log.debug: major version mismatch: " +
 712                         majorFormatVersion + "." + minorFormatVersion);
 713             }
 714             throw new IOException("Log file " + logName + " has a " +
 715                                   "version " + majorFormatVersion +
 716                                   "." + minorFormatVersion +
 717                                   " format, and this implementation " +
 718                                   " understands only version " +
 719                                   PreferredMajorVersion + "." +
 720                                   PreferredMinorVersion);
 721         }
 722 
 723         try {
 724             while (in != null) {
 725                 int updateLen = 0;
 726 
 727                 try {
 728                     updateLen = dataIn.readInt();
 729                 } catch (EOFException e) {
 730                     if (Debug)
 731                         System.err.println("log.debug: log was sync'd cleanly");
 732                     break;
 733                 }
 734                 if (updateLen <= 0) {/* crashed while writing last log entry */
 735                     if (Debug) {
 736                         System.err.println(
 737                             "log.debug: last update incomplete, " +
 738                             "updateLen = 0x" +
 739                             Integer.toHexString(updateLen));
 740                     }
 741                     break;
 742                 }
 743 
 744                 // this is a fragile use of available() which relies on the
 745                 // twin facts that BufferedInputStream correctly consults
 746                 // the underlying stream, and that FileInputStream returns
 747                 // the number of bytes remaining in the file (via FIONREAD).
 748                 if (in.available() < updateLen) {
 749                     /* corrupted record at end of log (can happen since we
 750                      * do only one fsync)
 751                      */
 752                     if (Debug)
 753                         System.err.println("log.debug: log was truncated");
 754                     break;
 755                 }
 756 
 757                 if (Debug)
 758                     System.err.println("log.debug: rdUpdate size " + updateLen);
 759                 try {
 760                     state = handler.readUpdate(new LogInputStream(in, updateLen),
 761                                           state);
 762                 } catch (IOException e) {
 763                     throw e;
 764                 } catch (Exception e) {
 765                     e.printStackTrace();
 766                     throw new IOException("read update failed with " +
 767                                           "exception: " + e);
 768                 }
 769                 logBytes += (intBytes + updateLen);
 770                 logEntries++;
 771             } /* while */
 772         } finally {
 773             if (in != null)
 774                 in.close();
 775         }
 776 
 777         if (Debug)
 778             System.err.println("log.debug: recovered updates: " + logEntries);
 779 
 780         /* reopen log file at end */
 781         openLogFile(false);
 782 
 783         // avoid accessing a null log field
 784         if (log == null) {
 785             throw new IOException("rmid's log is inaccessible, " +
 786                 "it may have been corrupted or closed");
 787         }
 788 
 789         log.seek(logBytes);
 790         log.setLength(logBytes);
 791 
 792         return state;
 793     }
 794 
 795     /**
 796      * ReliableLog's log file implementation.  This implementation
 797      * is subclassable for testing purposes.
 798      */
 799     public static class LogFile extends RandomAccessFile {
 800 
 801         private final FileDescriptor fd;
 802 
 803         /**
 804          * Constructs a LogFile and initializes the file descriptor.
 805          **/
 806         public LogFile(String name, String mode)
 807             throws FileNotFoundException, IOException
 808         {
 809             super(name, mode);
 810             this.fd = getFD();
 811         }
 812 
 813         /**
 814          * Invokes sync on the file descriptor for this log file.
 815          */
 816         protected void sync() throws IOException {
 817             fd.sync();
 818         }
 819 
 820         /**
 821          * Returns true if writing 4 bytes starting at the specified file
 822          * position, would span a 512 byte sector boundary; otherwise returns
 823          * false.
 824          **/
 825         protected boolean checkSpansBoundary(long fp) {
 826             return  fp % 512 > 508;
 827         }
 828     }
 829 }