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