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