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