docs: void openLog() throws LogOpenException, LogCorruptedException, LogAllocationException, InternalLogException,
                      LogIncompatibleException
      {
          if (tc.isEntryEnabled())
              Tr.entry(tc, "openLog", this);
  
          // Check that the file is not already open.
          if (_activeFile != null)
          {
              if (tc.isEntryEnabled())
                  Tr.exit(tc, "openLog", "LogOpenException");
              throw new LogOpenException(null);
          }
  
          // Create (ensure) the required directory tree
          if (!(RLSUtils.createDirectoryTree(_logDirectory)))
          {
              if (tc.isEntryEnabled())
                  Tr.exit(tc, "openLog", "LogAllocationException");
              throw new LogAllocationException(null);
          }
  
          // Create DO NOT DELETE LOG FILES file in logs directory
          createWarningFile();
  
          // Now construct the two LogFileHandle objects that manage the individual log
          // files. 
          //
          // There is a bug in the HP JVM's NIO implementation that causes it to fail when
          // an attempt to expand the mapped files is made. To overcome this when running
          // on HP the log files must be opened at their configured maximum size so that
          // they will an attempt to expand them will never be made.
          final String osName = System.getProperty("os.name");
  
          if (osName.equalsIgnoreCase("HPUX") || osName.equalsIgnoreCase("HP-UX"))
          {
              if (tc.isDebugEnabled())
                  Tr.debug(tc, "Running on HP-UX, open logs with initial size of maximum log size");
  
              _file1 = new LogFileHandle(_logDirectory, RECOVERY_FILE_1_NAME, _serverName, _serviceName, _serviceVersion, _logName, _maxLogFileSize, _failureScope);
              _file2 = new LogFileHandle(_logDirectory, RECOVERY_FILE_2_NAME, _serverName, _serviceName, _serviceVersion, _logName, _maxLogFileSize, _failureScope);
          }
          else
          {
              _file1 = new LogFileHandle(_logDirectory, RECOVERY_FILE_1_NAME, _serverName, _serviceName, _serviceVersion, _logName, _logFileSize, _failureScope);
              _file2 = new LogFileHandle(_logDirectory, RECOVERY_FILE_2_NAME, _serverName, _serviceName, _serviceVersion, _logName, _logFileSize, _failureScope);
          }
  
          _activeFile = null;
          _inactiveFile = null;
  
          // If one of the recovery log files has been deleted the recovery log has potentially been
          // damaged. In some situations it might be possible to recover, but the integrity has
          // been comprimized and we should be somewhat draconian in this case. Throw an exception. 
          if (_file1.fileExists() != _file2.fileExists())
          {
              if (tc.isEventEnabled())
                  Tr.event(tc, "One of the recovery log files has been deleted. Recovery can't complete");
              _file1 = null;
              _file2 = null;
              if (tc.isEntryEnabled())
                  Tr.exit(tc, "openLog", "LogCorruptedException");
              throw new LogCorruptedException(null);
          }
  
          // Determine if this will be a 'cold' start (no existing log files) or a 'warm' start  
          // (existing log files to recover) 
          boolean coldStart = !_file1.fileExists();
  
          // If this is a cold start then we need to choose which of the two recovery log files
          // should be written to first. This choice is arbitrary - by convention always pick the
          // the first file. Calling the activate method on the file changes its state to ACTIVE
          // and by doing so ensures that it will be selected as the target file by the logic
          // below (file two will be INACTIVE in this state)
          if (coldStart)
          {
              _file1.becomeActive();
  
              Tr.info(tc, "CWRLS0007_RECOVERY_LOG_NOT_EXIST", _logDirectory);
          }
  
          // Open the files. If this is a cold start then they will be created and the headers written
          // to disk. If this is a warm start, the existing headers will be loaded from disk.
          try
          {
              _file1.fileOpen();
          } catch (LogIncompatibleException exc)
          {
              // The _file1 log file has been opened and examined, but was found to be incompatible with the required
              // log for one of the following reasons:
              // 
              // 1. The file was created by a different service
              // 2. The file was created by this service but is a different log file
              // 3. The file was created by a later version of this service and has a different content format.
              // 
              // The reason will be preserved in the trace file. The log file can't be opened and an exception will
              // be generated.
  
              FFDCFilter.processException(exc, "com.ibm.ws.recoverylog.spi.LogHandle.openLog", "391", this);
  
              _file1 = null;
              _file2 = null;
  
              if (tc.isEntryEnabled())
                  Tr.exit(tc, "openLog", exc);
              throw exc;
          } catch (InternalLogException ile)
          {
              FFDCFilter.processException(ile, "com.ibm.ws.recoverylog.spi.LogHandle.openLog", "331", this);
              _file1 = null;
              _file2 = null;
              if (tc.isEntryEnabled())
                  Tr.exit(tc, "openLog", ile);
              throw ile;
          } catch (LogAllocationException lae)
          {
              FFDCFilter.processException(lae, "com.ibm.ws.recoverylog.spi.LogHandle.openLog", "339", this);
              _file1 = null;
              _file2 = null;
              if (tc.isEntryEnabled())
                  Tr.exit(tc, "openLog", lae);
              throw lae;
          } catch (Throwable exc)
          {
              FFDCFilter.processException(exc, "com.ibm.ws.recoverylog.spi.LogHandle.openLog", "347", this);
              _file1 = null;
              _file2 = null;
              if (tc.isEntryEnabled())
                  Tr.exit(tc, "openLog", "InternalLogException");
              throw new InternalLogException(exc);
          }
  
          try
          {
              _file2.fileOpen();
          } catch (LogIncompatibleException exc)
          {
              // The _file2 log file has been opened and examined, but was found to be incompatible with the required
              // log for one of the following reasons:
              // 
              // 1. The file was created by a different service
              // 2. The file was created by this service but is a different log file
              // 3. The file was created by a later version of this service and has a different content format.
              // 
              // The reason will be preserved in the trace file. The log file can't be opened and an exception will
              // be generated.
  
              FFDCFilter.processException(exc, "com.ibm.ws.recoverylog.spi.LogHandle.openLog", "449", this);
  
              _file1 = null;
              _file2 = null;
  
              if (tc.isEntryEnabled())
                  Tr.exit(tc, "openLog", exc);
              throw exc;
          } catch (InternalLogException ile)
          {
              FFDCFilter.processException(ile, "com.ibm.ws.recoverylog.spi.LogHandle.openLog", "369", this);
              _file1 = null;
              _file2 = null;
              if (tc.isEntryEnabled())
                  Tr.exit(tc, "openLog", ile);
              throw ile;
          } catch (LogAllocationException lae)
          {
              FFDCFilter.processException(lae, "com.ibm.ws.recoverylog.spi.LogHandle.openLog", "377", this);
              _file1 = null;
              _file2 = null;
              if (tc.isEntryEnabled())
                  Tr.exit(tc, "openLog", lae);
              throw lae;
          } catch (Throwable exc)
          {
              FFDCFilter.processException(exc, "com.ibm.ws.recoverylog.spi.LogHandle.openLog", "385", this);
              _file1 = null;
              _file2 = null;
              if (tc.isEntryEnabled())
                  Tr.exit(tc, "openLog", "InternalLogException");
              throw new InternalLogException(exc);
          }
  
          // If neither file had a valid header then the recovery log is totally corrupted.
          if ((_file1 == null) || (_file2 == null))
          {
              if (tc.isEventEnabled())
                  Tr.event(tc, "Neither of the recovery log files are valid. Recovery can't complete");
              if (tc.isEntryEnabled())
                  Tr.exit(tc, "openLog", "LogCorruptedException");
              throw new LogCorruptedException(null);
          }
  
          if (_file2.logFileHeader().status() == LogFileHeader.STATUS_INVALID)
          {
              if (_file1.logFileHeader().status() == LogFileHeader.STATUS_ACTIVE)
              {
                  // The only valid file is file1. This file is ACTIVE - ie not INACTIVE or KEYPOINTING
                  if (tc.isEventEnabled())
                      Tr.event(tc, "Selecting recovery log file '" + _file1.fileName() + "' as its ACTIVE and '" + _file2.fileName() + "' is not valid");
                  _activeFile = _file1;
                  _inactiveFile = _file2;
                  _inactiveFile.resetHeader(_activeFile);
              }
              else
              {
                  // The only valid file is file1. Unfortunatly, this file is not ACTIVE so it can't be used to recover.
                  if (tc.isEventEnabled())
                      Tr.event(tc, "The only valid recovery log file '" + _file1.fileName() + "' is not in ACTIVE state");
  
                  _file1 = null;
                  _file2 = null;
  
                  if (tc.isEntryEnabled())
                      Tr.exit(tc, "openLog", "LogCorruptedException");
                  throw new LogCorruptedException(null);
              }
          }
          else if (_file1.logFileHeader().status() == LogFileHeader.STATUS_INVALID)
          {
              if (_file2.logFileHeader().status() == LogFileHeader.STATUS_ACTIVE)
              {
                  // The only valid file is file2. This file is ACTIVE - ie not INVALID or KEYPOINTING
                  if (tc.isEventEnabled())
                      Tr.event(tc, "Selecting recovery log file '" + _file2.fileName() + "' as its ACTIVE and '" + _file1.fileName() + "' is not valid");
                  _activeFile = _file2;
                  _inactiveFile = _file1;
                  _inactiveFile.resetHeader(_activeFile);
              }
              else
              {
                  // The only valid file is file2. Unfortunatly, this file is not ACTIVE so it can't be used to recover.
                  if (tc.isEventEnabled())
                      Tr.event(tc, "The only valid recovery log file '" + _file2.fileName() + "' is not in ACTIVE state");
  
                  _file1 = null;
                  _file2 = null;
  
                  if (tc.isEntryEnabled())
                      Tr.exit(tc, "openLog", "LogCorruptedException");
                  throw new LogCorruptedException(null);
              }
          }
          else
          {
              // Both files have valid headers.
  
              final int file1Status = _file1.logFileHeader().status();
              final int file2Status = _file2.logFileHeader().status();
              final long file1FRSN = _file1.logFileHeader().firstRecordSequenceNumber();
              final long file2FRSN = _file2.logFileHeader().firstRecordSequenceNumber();
              final long file1Date = _file1.logFileHeader().date();
              final long file2Date = _file2.logFileHeader().date();
  
              if ((file1Status == LogFileHeader.STATUS_ACTIVE) && (file2Status != LogFileHeader.STATUS_ACTIVE))
              {
                  // File 1 is ACTIVE whilst file2 is not ACTIVE. Use file 1
                  if (tc.isEventEnabled())
                      Tr.event(tc, "Selecting recovery log file '" + _file1.fileName() + "' as its ACTIVE and '" + _file2.fileName() + "' is not ACTIVE");
                  _activeFile = _file1;
                  _inactiveFile = _file2;
              }
              else if ((file1Status != LogFileHeader.STATUS_ACTIVE) && (file2Status == LogFileHeader.STATUS_ACTIVE))
              {
                  // File 2 is ACTIVE whilst file1 is not ACTIVE. Use file 2
                  if (tc.isEventEnabled())
                      Tr.event(tc, "Selecting recovery log file '" + _file2.fileName() + "' as its ACTIVE and '" + _file1.fileName() + "' is not ACTIVE");
                  _activeFile = _file2;
                  _inactiveFile = _file1;
              }
              else if ((file1Status == LogFileHeader.STATUS_ACTIVE) && (file2Status == LogFileHeader.STATUS_ACTIVE))
              {
                  // Both files are in active state. This is an unusual case as the window for having two files in ACTIVE
                  // state is very small. The keypointing logic switches the 'old' file from ACTIVE to INACTIVE state
                  // immediatly after changeing the 'new' file from KEYPOINTING to ACTIVE state. To get this situation to
                  // occur, the server has to fail between these two state changes.
                  if (tc.isEventEnabled())
                      Tr.event(tc, "Both recovery files are in ACTIVE state. Determine target file by examining timestamp");
  
                  if (file1Date > file2Date)
                  {
                      // File 1 was created after file 2 so select file 1.
                      if (tc.isEventEnabled())
                          Tr.event(tc, "Selecting recovery log file '" + _file1.fileName() + "' as it was created after '" + _file2.fileName() + "'");
                      _activeFile = _file1;
                      _inactiveFile = _file2;
                  }
                  else if (file1Date < file2Date)
                  {
                      // File 2 was created after file 1 so select file 2.
                      if (tc.isEventEnabled())
                          Tr.event(tc, "Selecting recovery log file '" + _file2.fileName() + "' as it was created after '" + _file1.fileName() + "'");
                      _activeFile = _file2;
                      _inactiveFile = _file1;
                  }
                  else
                  {
                      // Both files were created at the same time. This seems unlikely as the times are in milliseconds but
                      // its possible, so choose between them by examining the first record sequence numbers and picking
                      // the file that contains the newer records (larger number).
                      if (tc.isEventEnabled())
                          Tr.event(tc, "Both recovery files have the same timestamp. Determine target file by examining first record sequence numbers");
  
                      if ((file1FRSN == 0) && (file2FRSN == 0))
                      {
                          if (tc.isEventEnabled())
                              Tr.event(tc, "Selecting recovery log file '" + _file1.fileName() + "' as both files are empty");
                          // Totally empty log
                          _activeFile = _file1;
                          _inactiveFile = _file2;
                      }
                      else if (file1FRSN > file2FRSN)
                      {
                          if (tc.isEventEnabled())
                              Tr.event(tc, "Selecting recovery log file '" + _file1.fileName() + "' as it has as the highest record sequence number");
                          _activeFile = _file1;
                          _inactiveFile = _file2;
                      }
                      else if (file1FRSN < file2FRSN)
                      {
                          if (tc.isEventEnabled())
                              Tr.event(tc, "Selecting recovery log file '" + _file2.fileName() + "' as it has as the highest record sequence number");
                          _activeFile = _file2;
                          _inactiveFile = _file1;
                      }
                      else
                      {
                          _file1 = null;
                          _file2 = null;
                          if (tc.isEventEnabled())
                              Tr.event(tc, "Neither of the recovery log files are valid as they have the same record sequence number. Recovery can't complete");
                          if (tc.isEntryEnabled())
                              Tr.exit(tc, "openLog", "LogCorruptedException");
                          throw new LogCorruptedException(null);
                      }
                  }
              }
              else
              {
                  _file1 = null;
                  _file2 = null;
                  if (tc.isEventEnabled())
                      Tr.event(tc, "The recovery lof files are in an undefined state");
                  if (tc.isEntryEnabled())
                      Tr.exit(tc, "openLog", "LogCorruptedException");
                  throw new LogCorruptedException(null);
              }
          }
  
          if (tc.isDebugEnabled())
              Tr.debug(tc, "Selected log file '" + _activeFile.fileName() + "' for recovery");
  
          // Lookup the service data from the current target file. This will either be null (if this
          // was a cold start) or whatever service data was read from the selected target file.
          _serviceData = _activeFile.getServiceData();
  
          // Pass the service data to the file not selected for recovery. This ensures that when
          // this file is used it will have the service data originally recovered from the target
          // file.
          if (_activeFile == _file1)
          {
              _file2.setServiceData(_serviceData);
          }
          else
          {
              _file1.setServiceData(_serviceData);
          }
  
          // Now recover the record data that can be used to re-construct the recoverable units and sections
          try
          {
              _recoveredRecords = readRecords();
          } catch (InternalLogException exc)
          {
              FFDCFilter.processException(exc, "com.ibm.ws.recoverylog.spi.LogHandle.openLog", "554", this);
              _file1 = null;
              _file2 = null;
              _activeFile = null;
              _inactiveFile = null;
              _recoveredRecords = null;
              if (tc.isEntryEnabled())
                  Tr.exit(tc, "openLog", exc);
              throw exc;
          } catch (Throwable exc)
          {
              FFDCFilter.processException(exc, "com.ibm.ws.recoverylog.spi.LogHandle.openLog", "565", this);
              _file1 = null;
              _file2 = null;
              _activeFile = null;
              _inactiveFile = null;
              _recoveredRecords = null;
              if (tc.isEntryEnabled())
                  Tr.exit(tc, "openLog", "InternalLogException");
              throw new InternalLogException(exc);
          }
  
          // At this point one of the following will apply:
          //
          // 1. The file has been created from scratch and has a header containing a first record sequence number
          // of zero. The file has been expanded to the required file size and the file cursor is positioned on
          // the first byte after the header. No records are stored in memory.
          // 2. An existing file has been opened and the valid header loaded into memory. The first record sequence
          // number in the header was zero, so no records have been loaded into memory. The file has been expanded
          // to the required file size and the file cursor is positioned on the first byte after the header.
          // 3. An existing file has been opened and the valid header loaded into memory. The first record sequence
          // number in the header non-zero, so the records have been loaded into memory. The file has been expanded
          // to the required file size and the file cursor is positioned on the first byte after the last valid record.
  
          // Determine the remaining free bytes in the file
          _physicalFreeBytes = _activeFile.freeBytes();
  
          if (tc.isEntryEnabled())
              Tr.exit(tc, "openLog");
      }