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", e);
 384             }
 385             lastSnapshot = System.currentTimeMillis();
 386         } finally {
 387             out.close();
 388             snapshotBytes = snapshotFile.length();
 389         }
 390 
 391         openLogFile(true);
 392         writeVersionFile(true);
 393         commitToNewVersion();
 394         deleteSnapshot(oldVersion);
 395         deleteLogFile(oldVersion);
 396     }
 397 
 398     /**
 399      * Close the stable storage directory in an orderly manner.
 400      *
 401      * @exception IOException If an I/O error occurs when the log is
 402      * closed
 403      */
 404     public synchronized void close() throws IOException {
 405         if (log == null) return;
 406         try {
 407             log.close();
 408         } finally {
 409             log = null;
 410         }
 411     }
 412 
 413     /**
 414      * Returns the size of the snapshot file in bytes;
 415      */
 416     public long snapshotSize() {
 417         return snapshotBytes;
 418     }
 419 
 420     /**
 421      * Returns the size of the log file in bytes;
 422      */
 423     public long logSize() {
 424         return logBytes;
 425     }
 426 
 427     /* private methods */
 428 
 429     /**
 430      * Write an int value in single write operation.  This method
 431      * assumes that the caller is synchronized on the log file.
 432      *
 433      * @param out output stream
 434      * @param val int value
 435      * @throws IOException if any other I/O error occurs
 436      */
 437     private void writeInt(DataOutput out, int val)
 438         throws IOException
 439     {
 440         intBuf[0] = (byte) (val >> 24);
 441         intBuf[1] = (byte) (val >> 16);
 442         intBuf[2] = (byte) (val >> 8);
 443         intBuf[3] = (byte) val;
 444         out.write(intBuf);
 445     }
 446 
 447     /**
 448      * Generates a filename prepended with the stable storage directory path.
 449      *
 450      * @param name the leaf name of the file
 451      */
 452     private String fName(String name) {
 453         return dir.getPath() + File.separator + name;
 454     }
 455 
 456     /**
 457      * Generates a version 0 filename prepended with the stable storage
 458      * directory path
 459      *
 460      * @param name version file name
 461      */
 462     private String versionName(String name) {
 463         return versionName(name, 0);
 464     }
 465 
 466     /**
 467      * Generates a version filename prepended with the stable storage
 468      * directory path with the version number as a suffix.
 469      *
 470      * @param name version file name
 471      * @thisversion a version number
 472      */
 473     private String versionName(String prefix, int ver) {
 474         ver = (ver == 0) ? version : ver;
 475         return fName(prefix) + String.valueOf(ver);
 476     }
 477 
 478     /**
 479      * Increments the directory version number.
 480      */
 481     private void incrVersion() {
 482         do { version++; } while (version==0);
 483     }
 484 
 485     /**
 486      * Delete a file.
 487      *
 488      * @param name the name of the file
 489      * @exception IOException If new version file couldn't be removed
 490      */
 491     private void deleteFile(String name) throws IOException {
 492 
 493         File f = new File(name);
 494         if (!f.delete())
 495             throw new IOException("couldn't remove file: " + name);
 496     }
 497 
 498     /**
 499      * Removes the new version number file.
 500      *
 501      * @exception IOException If an I/O error has occurred.
 502      */
 503     private void deleteNewVersionFile() throws IOException {
 504         deleteFile(fName(newVersionFile));
 505     }
 506 
 507     /**
 508      * Removes the snapshot file.
 509      *
 510      * @param ver the version to remove
 511      * @exception IOException If an I/O error has occurred.
 512      */
 513     private void deleteSnapshot(int ver) throws IOException {
 514         if (ver == 0) return;
 515         deleteFile(versionName(snapshotPrefix, ver));
 516     }
 517 
 518     /**
 519      * Removes the log file.
 520      *
 521      * @param ver the version to remove
 522      * @exception IOException If an I/O error has occurred.
 523      */
 524     private void deleteLogFile(int ver) throws IOException {
 525         if (ver == 0) return;
 526         deleteFile(versionName(logfilePrefix, ver));
 527     }
 528 
 529     /**
 530      * Opens the log file in read/write mode.  If file does not exist, it is
 531      * created.
 532      *
 533      * @param truncate if true and file exists, file is truncated to zero
 534      * length
 535      * @exception IOException If an I/O error has occurred.
 536      */
 537     private void openLogFile(boolean truncate) throws IOException {
 538         try {
 539             close();
 540         } catch (IOException e) { /* assume this is okay */
 541         }
 542 
 543         logName = versionName(logfilePrefix);
 544 
 545         try {
 546             log = (logClassConstructor == null ?
 547                    new LogFile(logName, "rw") :
 548                    logClassConstructor.newInstance(logName, "rw"));
 549         } catch (Exception e) {
 550             throw (IOException) new IOException(
 551                 "unable to construct LogFile instance").initCause(e);
 552         }
 553 
 554         if (truncate) {
 555             initializeLogFile();
 556         }
 557     }
 558 
 559     /**
 560      * Creates a new log file, truncated and initialized with the format
 561      * version number preferred by this implementation.
 562      * <p>Environment: inited, synchronized
 563      * <p>Precondition: valid: log, log contains nothing useful
 564      * <p>Postcondition: if successful, log is initialised with the format
 565      * version number (Preferred{Major,Minor}Version), and logBytes is
 566      * set to the resulting size of the updatelog, and logEntries is set to
 567      * zero.  Otherwise, log is in an indeterminate state, and logBytes
 568      * is unchanged, and logEntries is unchanged.
 569      *
 570      * @exception IOException If an I/O error has occurred.
 571      */
 572     private void initializeLogFile()
 573         throws IOException
 574     {
 575         log.setLength(0);
 576         majorFormatVersion = PreferredMajorVersion;
 577         writeInt(log, PreferredMajorVersion);
 578         minorFormatVersion = PreferredMinorVersion;
 579         writeInt(log, PreferredMinorVersion);
 580         logBytes = intBytes * 2;
 581         logEntries = 0;
 582     }
 583 
 584 
 585     /**
 586      * Writes out version number to file.
 587      *
 588      * @param newVersion if true, writes to a new version file
 589      * @exception IOException If an I/O error has occurred.
 590      */
 591     private void writeVersionFile(boolean newVersion) throws IOException {
 592         String name;
 593         if (newVersion) {
 594             name = newVersionFile;
 595         } else {
 596             name = versionFile;
 597         }
 598         DataOutputStream out =
 599             new DataOutputStream(new FileOutputStream(fName(name)));
 600         writeInt(out, version);
 601         out.close();
 602     }
 603 
 604     /**
 605      * Creates the initial version file
 606      *
 607      * @exception IOException If an I/O error has occurred.
 608      */
 609     private void createFirstVersion() throws IOException {
 610         version = 0;
 611         writeVersionFile(false);
 612     }
 613 
 614     /**
 615      * Commits (atomically) the new version.
 616      *
 617      * @exception IOException If an I/O error has occurred.
 618      */
 619     private void commitToNewVersion() throws IOException {
 620         writeVersionFile(false);
 621         deleteNewVersionFile();
 622     }
 623 
 624     /**
 625      * Reads version number from a file.
 626      *
 627      * @param name the name of the version file
 628      * @return the version
 629      * @exception IOException If an I/O error has occurred.
 630      */
 631     private int readVersion(String name) throws IOException {
 632         DataInputStream in = new DataInputStream(new FileInputStream(name));
 633         try {
 634             return in.readInt();
 635         } finally {
 636             in.close();
 637         }
 638     }
 639 
 640     /**
 641      * Sets the version.  If version file does not exist, the initial
 642      * version file is created.
 643      *
 644      * @exception IOException If an I/O error has occurred.
 645      */
 646     private void getVersion() throws IOException {
 647         try {
 648             version = readVersion(fName(newVersionFile));
 649             commitToNewVersion();
 650         } catch (IOException e) {
 651             try {
 652                 deleteNewVersionFile();
 653             }
 654             catch (IOException ex) {
 655             }
 656 
 657             try {
 658                 version = readVersion(fName(versionFile));
 659             }
 660             catch (IOException ex) {
 661                 createFirstVersion();
 662             }
 663         }
 664     }
 665 
 666     /**
 667      * Applies outstanding updates to the snapshot.
 668      *
 669      * @param state the most recent snapshot
 670      * @exception IOException If serious log corruption is detected or
 671      * if an exception occurred during a readUpdate callback or if
 672      * other I/O error has occurred.
 673      * @return the resulting state of the object after all updates
 674      */
 675     private Object recoverUpdates(Object state)
 676         throws IOException
 677     {
 678         logBytes = 0;
 679         logEntries = 0;
 680 
 681         if (version == 0) return state;
 682 
 683         String fname = versionName(logfilePrefix);
 684         InputStream in =
 685                 new BufferedInputStream(new FileInputStream(fname));
 686         DataInputStream dataIn = new DataInputStream(in);
 687 
 688         if (Debug)
 689             System.err.println("log.debug: reading updates from " + fname);
 690 
 691         try {
 692             majorFormatVersion = dataIn.readInt(); logBytes += intBytes;
 693             minorFormatVersion = dataIn.readInt(); logBytes += intBytes;
 694         } catch (EOFException e) {
 695             /* This is a log which was corrupted and/or cleared (by
 696              * fsck or equivalent).  This is not an error.
 697              */
 698             openLogFile(true);  // create and truncate
 699             in = null;
 700         }
 701         /* A new major version number is a catastrophe (it means
 702          * that the file format is incompatible with older
 703          * clients, and we'll only be breaking things by trying to
 704          * use the log).  A new minor version is no big deal for
 705          * upward compatibility.
 706          */
 707         if (majorFormatVersion != PreferredMajorVersion) {
 708             if (Debug) {
 709                 System.err.println("log.debug: major version mismatch: " +
 710                         majorFormatVersion + "." + minorFormatVersion);
 711             }
 712             throw new IOException("Log file " + logName + " has a " +
 713                                   "version " + majorFormatVersion +
 714                                   "." + minorFormatVersion +
 715                                   " format, and this implementation " +
 716                                   " understands only version " +
 717                                   PreferredMajorVersion + "." +
 718                                   PreferredMinorVersion);
 719         }
 720 
 721         try {
 722             while (in != null) {
 723                 int updateLen = 0;
 724 
 725                 try {
 726                     updateLen = dataIn.readInt();
 727                 } catch (EOFException e) {
 728                     if (Debug)
 729                         System.err.println("log.debug: log was sync'd cleanly");
 730                     break;
 731                 }
 732                 if (updateLen <= 0) {/* crashed while writing last log entry */
 733                     if (Debug) {
 734                         System.err.println(
 735                             "log.debug: last update incomplete, " +
 736                             "updateLen = 0x" +
 737                             Integer.toHexString(updateLen));
 738                     }
 739                     break;
 740                 }
 741 
 742                 // this is a fragile use of available() which relies on the
 743                 // twin facts that BufferedInputStream correctly consults
 744                 // the underlying stream, and that FileInputStream returns
 745                 // the number of bytes remaining in the file (via FIONREAD).
 746                 if (in.available() < updateLen) {
 747                     /* corrupted record at end of log (can happen since we
 748                      * do only one fsync)
 749                      */
 750                     if (Debug)
 751                         System.err.println("log.debug: log was truncated");
 752                     break;
 753                 }
 754 
 755                 if (Debug)
 756                     System.err.println("log.debug: rdUpdate size " + updateLen);
 757                 try {
 758                     state = handler.readUpdate(new LogInputStream(in, updateLen),
 759                                           state);
 760                 } catch (IOException e) {
 761                     throw e;
 762                 } catch (Exception e) {
 763                     e.printStackTrace();
 764                     throw new IOException("read update failed with " +
 765                                           "exception: " + e);
 766                 }
 767                 logBytes += (intBytes + updateLen);
 768                 logEntries++;
 769             } /* while */
 770         } finally {
 771             if (in != null)
 772                 in.close();
 773         }
 774 
 775         if (Debug)
 776             System.err.println("log.debug: recovered updates: " + logEntries);
 777 
 778         /* reopen log file at end */
 779         openLogFile(false);
 780 
 781         // avoid accessing a null log field
 782         if (log == null) {
 783             throw new IOException("rmid's log is inaccessible, " +
 784                 "it may have been corrupted or closed");
 785         }
 786 
 787         log.seek(logBytes);
 788         log.setLength(logBytes);
 789 
 790         return state;
 791     }
 792 
 793     /**
 794      * ReliableLog's log file implementation.  This implementation
 795      * is subclassable for testing purposes.
 796      */
 797     public static class LogFile extends RandomAccessFile {
 798 
 799         private final FileDescriptor fd;
 800 
 801         /**
 802          * Constructs a LogFile and initializes the file descriptor.
 803          **/
 804         public LogFile(String name, String mode)
 805             throws FileNotFoundException, IOException
 806         {
 807             super(name, mode);
 808             this.fd = getFD();
 809         }
 810 
 811         /**
 812          * Invokes sync on the file descriptor for this log file.
 813          */
 814         protected void sync() throws IOException {
 815             fd.sync();
 816         }
 817 
 818         /**
 819          * Returns true if writing 4 bytes starting at the specified file
 820          * position, would span a 512 byte sector boundary; otherwise returns
 821          * false.
 822          **/
 823         protected boolean checkSpansBoundary(long fp) {
 824             return  fp % 512 > 508;
 825         }
 826     }
 827 }