All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.ibm.as400.access.IFSFileImplRemote Maven / Gradle / Ivy

The newest version!
///////////////////////////////////////////////////////////////////////////////
//                                                                             
// JTOpen (IBM Toolbox for Java - OSS version)                              
//                                                                             
// Filename: IFSFileImplRemote.java
//                                                                             
// The source code contained herein is licensed under the IBM Public License   
// Version 1.0, which has been approved by the Open Source Initiative.         
// Copyright (C) 1997-2007 International Business Machines Corporation and     
// others. All rights reserved.                                                
//                                                                             
///////////////////////////////////////////////////////////////////////////////
// @D7 - 07/25/2007 - Add allowSortedRequests to the listDirectoryDetails()
//                    method to resolve problem of issuing PWFS List Attributes 
//                    request with both "Sort" indication and "RestartByID" 
//                    which is documented to be an invalid combination.
// @D8 - 10/04/2007 - Remove obsolete code for determining whether QSYS objects
//                    are to be treated as a "directory" or a "file".
//                    For QSYS objects, those which may be treated as directories
//                    have the attribute IFSListAttrsRep.DIRECTORY.
//                    For QSYS objects, those which may be treated as files
//                    have the attribute IFSListAttrsRep.FILE.
//                    All other QSYS objects are neither dirs/files (e.g OUTQ, 
//                    DSPF, TAPF, or PRTF objects).
// @D9 - 04/03/2008 - Add clearCachedAttributes() to clear impl cache attributes. 
///////////////////////////////////////////////////////////////////////////////

package com.ibm.as400.access;

import java.io.InterruptedIOException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Hashtable;
import java.util.Vector;

/**
 * Provides a full remote implementation for the IFSFile class.
 **/
class IFSFileImplRemote implements IFSFileImpl
{
    // Used for debugging only.  This should always be false for production.
    private static final boolean DEBUG = false;

    private static final boolean IS_RESTART_NAME = true;
    private static final boolean NO_RESTART_NAME = IS_RESTART_NAME;
    private static final boolean SORT_LIST = true;
    private static final int     NO_MAX_GET_COUNT = -1;
    private static final int     UNINITIALIZED = -1;

    // Constants for QlgAccess(), from system definitions file "unistd.h"
    //private static final int ACCESS_MODE_READ    = 0x04;  // R_OK: test for read permission
    //private static final int ACCESS_MODE_WRITE   = 0x02;  // W_OK: test for write permission
    private static final int ACCESS_MODE_EXECUTE = 0x01;  // X_OK: test for execute permission
    //private static final int ACCESS_MODE_EXISTS  = 0x00;  // F_OK: test for existence of file

    static
    {
        // Add all byte stream reply data streams of interest to the
        // server's reply data stream hash table.
        AS400Server.addReplyStream(new IFSListAttrsRep(), AS400.FILE);
        AS400Server.addReplyStream(new IFSOpenRep(), AS400.FILE);
        AS400Server.addReplyStream(new IFSCreateDirHandleRep(), AS400.FILE);
        AS400Server.addReplyStream(new IFSQuerySpaceRep(), AS400.FILE);
        AS400Server.addReplyStream(new IFSReturnCodeRep(), AS400.FILE);
        AS400Server.addReplyStream(new IFSLookupRep(), AS400.FILE);
        AS400Server.addReplyStream(new IFSGetFileSystemRep(), AS400.FILE);
    }

    transient private IFSListAttrsRep attributesReply_; // "list attributes" reply

    private IFSFileDescriptorImplRemote fd_ = new IFSFileDescriptorImplRemote();

    private Boolean isSymbolicLink_;
    private boolean sortLists_;  // whether file-lists are returned from the File Server in sorted order
    private RemoteCommandImpl rmtCmd_;  // Impl object for remote command host server.

    private byte[] qualifiedFileName_;
    private Integer databaseFileAttributes_;


    @Override
    public boolean canExecute() throws IOException, AS400SecurityException
    {
        // Ensure that we are connected to the server.
        fd_.connect();

        return canAccess(ACCESS_MODE_EXECUTE);
    }

    @Override
    public boolean canRead() throws IOException, AS400SecurityException
    {
        // Ensure that we are connected to the server.
        fd_.connect();

        int rc = fd_.checkAccess(IFSOpenReq.READ_ACCESS, IFSOpenReq.OPEN_OPTION_FAIL_OPEN);
        return (rc == IFSReturnCodeRep.SUCCESS);
        // Design note: The QlgAccess() API gives somewhat different results in certain scenarios.
        // Using IFSOpenReq appears to be a bit more "thorough".
    }

    @Override
    public boolean canWrite() throws IOException, AS400SecurityException
    {
        // Ensure that we are connected to the server.
        fd_.connect();

        int rc = fd_.checkAccess(IFSOpenReq.WRITE_ACCESS, IFSOpenReq.OPEN_OPTION_FAIL_OPEN);
        return (rc == IFSReturnCodeRep.SUCCESS);
        // Design note: The QlgAccess() API gives somewhat different results in certain scenarios.
        // Using IFSOpenReq appears to be a bit more "thorough".
    }

  /**
   Calls QlgAccess() to determine whether the current user can access the file in the specified mode.
   If the file does not exist, returns false.
   Note: The QlgAccess API was introduced in V5R1.
   **/
  private boolean canAccess(int accessMode) throws IOException, AS400SecurityException
  {
      // Assume that the caller has already connected to the server.

      if (fd_.getSystemVRM() < 0x00050100)
      {
          if (Trace.traceOn_) Trace.log(Trace.WARNING, "Server is pre-V5R1, so canAccess() is returning false.");
          return false;
      }

      // We will call the QlgAccess API, to determine whether the current user can access the file in the specified
      // mode.
      // Note: According to the spec for QlgAccess: "If the [user profile] has *ALLOBJ special authority, access() will
      // indicate success for R_OK, W_OK, or X_OK even if none of the permission bits are set."
      // Note: QlgAccess() is only _conditionally_ threadsafe.

      boolean result;
      try
      {
          // Create the pgm call object
          if (rmtCmd_ == null)
              setupRemoteCommand();

          ProgramParameter[] parameters = new ProgramParameter[] {
                  // Parameter 1: Qlg_Path_Name_T *Path_Name (input) :
                  new ProgramParameter(ProgramParameter.PASS_BY_REFERENCE, createPathName()),
                  // Parameter 2: int amode (input) :
                  new ProgramParameter(ProgramParameter.PASS_BY_VALUE, BinaryConverter.intToByteArray(accessMode))
          };

          // Call the service program.
          byte[] returnedBytes = rmtCmd_.runServiceProgram("QSYS", "QP0LLIB1", "QlgAccess", parameters);
          if (returnedBytes == null)
          {
              Trace.log(Trace.ERROR, "Call to QlgAccess() returned null.");
              throw new AS400Exception(rmtCmd_.getMessageList());
          }

          int returnValue = BinaryConverter.byteArrayToInt(returnedBytes, 0);

          switch (returnValue)
          {
              case -1:  // this indicates that we got an "errno" back
              {
                  result = false;
                  int errno = BinaryConverter.byteArrayToInt(returnedBytes, 4);
                  switch (errno)
                  {
                      case 3025:  // ENOENT: "No such path or directory."
                          // Assume that we got this error because the file isn't a symlink.
                          if (Trace.traceOn_) 
                              Trace.log(Trace.DIAGNOSTIC, "Received errno "+errno+" from QlgAccess() for file " 
                                                          + fd_.path_ + ". Assuming that the file does not exist.");
                          break;
                      case 3401:  // EACCES: "Permission denied."
                          // Assume that we got this error because we don't have the specified access.
                          if (Trace.traceOn_) 
                              Trace.log(Trace.DIAGNOSTIC, "Received errno "+errno+" from QlgAccess() for file " 
                                                          + fd_.path_ + ". Assuming that the user does not have the specified access.");
                          break;
                          default:    // some other errno
                              Trace.log(Trace.ERROR, "Received errno "+errno+" from QlgAccess() for file " + fd_.path_);
                              
                              // Note: An ErrnoException might be more appropriate, but the ErrnoException constructor 
                              // requires an AS400 object, and we don't have one to give it.
                              throw new ExtendedIOException(fd_.path_, ExtendedIOException.UNKNOWN_ERROR, errno);
                  }
                  break;
              }
              case 0:  // the call to QlgAccess() was successful.
              {
                  result = true;
                  break;
              }
              default:  // This should never happen. The API spec says it only returns 0 or -1.
              {
                  Trace.log(Trace.ERROR, "Received unexpected return value " + returnValue + " from QlgAccess() for file " + fd_.path_);
                  throw new InternalErrorException(InternalErrorException.UNEXPECTED_RETURN_CODE, "QlgAccess()", returnValue);
              }
          }
      }
      catch (AS400SecurityException|IOException e) {
          throw e;
      } catch (Exception e) {
          Trace.log(Trace.ERROR, "Error while determining accessibility of file.", e);
          throw new ExtendedIOException(fd_.path_, ExtendedIOException.UNKNOWN_ERROR);
      }

      return result;
  }


  /**
   Calls QlgChmod() to reset the access mode for the file.
   // If the file does not exist, returns false.
   Note: The QlgChmod API was introduced in V5R1.
   **/
  @Override
  public boolean setAccess(int accessType, boolean enableAccess, boolean ownerOnly) throws IOException, AS400SecurityException
  {
      // Assume that the caller has already connected to the server.

      // We will call the QlgChmod API, to determine whether the current user can access the file in the specified mode.
      // Note: QlgChmod() is only _conditionally_ threadsafe.

      if (fd_.getSystemVRM() < 0x00050100)
      {
          if (Trace.traceOn_) Trace.log(Trace.WARNING, "Server is pre-V5R1, so setAccess() is not supported.");
          return false;
      }

      try
      {
          // Create the pgm call object
          if (rmtCmd_ == null)
              setupRemoteCommand();

          // Get the current access modes, so that we can selectively turn on/off the desired bit(s).
          int oldAccessMode = getAccess();

          int bitMask = accessType << 6;  // for example: 0000400 == [ 'read' for owner ] 
          if (!ownerOnly) 
          {
              bitMask = bitMask | (accessType << 3) | accessType;
              // for example: 0000444 == [ 'read' for owner, group, and other ]
          }

          int newAccessMode;
          if (enableAccess)
              newAccessMode = oldAccessMode | bitMask;  // selectively turn bits _on_
          else {  
              // disable access
              newAccessMode = oldAccessMode & ~bitMask;  // selectively turn bits _off_
          }
          newAccessMode = newAccessMode & 0007777;  // QlgChmod can only set the low 12 bits

          ProgramParameter[] parameters = new ProgramParameter[] {
                  // Parameter 1: Qlg_Path_Name_T *Path_Name (input) :
                  new ProgramParameter(ProgramParameter.PASS_BY_REFERENCE, createPathName()),
                  // Parameter 2: int amode (input) :
                  new ProgramParameter(ProgramParameter.PASS_BY_VALUE, BinaryConverter.intToByteArray(newAccessMode))
          };

          // Call the service program.
          byte[] returnedBytes = rmtCmd_.runServiceProgram("QSYS", "QP0LLIB1", "QlgChmod", parameters);
          if (returnedBytes == null)
          {
              Trace.log(Trace.ERROR, "Call to QlgChmod() returned null.");
              throw new AS400Exception(rmtCmd_.getMessageList());
          }

          int returnValue = BinaryConverter.byteArrayToInt(returnedBytes, 0);

          switch (returnValue)
          {
              case -1:  // this indicates that we got an "errno" back
              {
                  int errno = BinaryConverter.byteArrayToInt(returnedBytes, 4);
                  Trace.log(Trace.ERROR, "Received errno " + errno + " from QlgChmod() for file " + fd_.path_);
                  throw new ExtendedIOException(fd_.path_, ExtendedIOException.UNKNOWN_ERROR, errno);
              }
              case 0:  // the call to QlgChmod() was successful.
              {
                  break;
              }
              default:  // This should never happen. The API spec says it only returns 0 or -1.
              {
                  Trace.log(Trace.ERROR, "Received unexpected return value " + returnValue + " from QlgChmod() for file " + fd_.path_);
                  throw new InternalErrorException(InternalErrorException.UNEXPECTED_RETURN_CODE, "QlgChmod()", returnValue);
              }
          }
      } 
      catch (AS400SecurityException | IOException e) {
          throw e;
      } catch (Exception e) {
          Trace.log(Trace.ERROR, "Error while determining accessibility of file.", e);
          throw new ExtendedIOException(fd_.path_, ExtendedIOException.UNKNOWN_ERROR);
      }

      return true;
  }


  /**
   Calls QlgStat() to get status information about the file.
   If the file does not exist, throws an ObjectDoesNotExistException.
   Note: The QlgStat API was introduced in V5R1.  Do not call this method without checking VRM.
   **/
  private int getAccess() throws IOException, AS400SecurityException, ObjectDoesNotExistException
  {
      // Assume that the caller has already connected to the server.
      // Assume that the caller has already verified that the server is V5R1 or higher.

      // We will call the QlgStat API, to get status information about the file.
      // Note: QlgStat() is only _conditionally_ threadsafe.

      int statInfo = 0;
      try
      {
          // Create the pgm call object
          if (rmtCmd_ == null)
              setupRemoteCommand();

          int bufferSizeProvided = 128;  // large enough to accommodate a 'stat' structure

          ProgramParameter[] parameters = new ProgramParameter[] {
                  // Parameter 1: Qlg_Path_Name_T *Path_Name (input) :
                  new ProgramParameter(ProgramParameter.PASS_BY_REFERENCE, createPathName()),
                  // Parameter 2: struct stat *buf (output) :
                  new ProgramParameter(ProgramParameter.PASS_BY_REFERENCE, bufferSizeProvided)
          };

          // Call the service program.
          byte[] returnedBytes = rmtCmd_.runServiceProgram("QSYS", "QP0LLIB1", "QlgStat", parameters);
          if (returnedBytes == null)
          {
              Trace.log(Trace.ERROR, "Call to QlgStat() returned null.");
              throw new AS400Exception(rmtCmd_.getMessageList());
          }

          int returnValue = BinaryConverter.byteArrayToInt(returnedBytes, 0);

          switch (returnValue)
          {
              case -1:  // this indicates that we got an "errno" back
              {
                  int errno = BinaryConverter.byteArrayToInt(returnedBytes, 4);
                  switch (errno)
                  {
                      case 3025:  // ENOENT: "No such path or directory."
                          // Assume that we got this error because the file isn't a symlink.
                          Trace.log(Trace.ERROR, "Received errno "+errno+" from QlgStat() for file " + fd_.path_ + ". Assuming that the file does not exist.");
                          throw new ObjectDoesNotExistException(fd_.path_, ObjectDoesNotExistException.OBJECT_DOES_NOT_EXIST);
                      case 3401:  // EACCES: "Permission denied."
                          // Assume that we got this error because we don't have the specified access.
                          Trace.log(Trace.ERROR, "Received errno "+errno+" from QlgStat() for file " + fd_.path_ + ". Assuming that the user does not have permission to access the file.");
                          throw new AS400SecurityException(fd_.path_, AS400SecurityException.DIRECTORY_ENTRY_ACCESS_DENIED);
                      default:    // some other errno
                          Trace.log(Trace.ERROR, "Received errno " + errno + " from QlgStat() for file " + fd_.path_);
                          throw new ExtendedIOException(fd_.path_, ExtendedIOException.UNKNOWN_ERROR, errno);
                  }
              }
              case 0:  // the call to QglStat() was successful.
              {
                  // Parse the "file modes" from the returned stat structure (second parameter).
                  statInfo = parseStatInfo(parameters[1].getOutputData());
                  break;
              }
              default:  // This should never happen. The API spec says it only returns 0 or -1.
              {
                  Trace.log(Trace.ERROR, "Received unexpected return value " + returnValue + " from QlgStat() for file " + fd_.path_);
                  throw new InternalErrorException(InternalErrorException.UNEXPECTED_RETURN_CODE, "QlgStat()", returnValue);
              }
          }
      }
      catch (AS400SecurityException|IOException e) {
          throw e;
      }
      catch (Exception e) {
          Trace.log(Trace.ERROR, "Error while determining accessibility of file.", e);
          throw new ExtendedIOException(fd_.path_, ExtendedIOException.UNKNOWN_ERROR);
      }

      return statInfo;
  }

  @Override
  public void clearCachedAttributes()
  {
      attributesReply_ = null;
      databaseFileAttributes_ = null;
  }

  @Override
  public boolean copyTo(String destinationPath, boolean replace) throws IOException, AS400SecurityException, ObjectAlreadyExistsException
  {
      fd_.connect();
      if (Trace.traceOn_ && replace==false && fd_.getSystemVRM() < 0x00050300)
          Trace.log(Trace.WARNING, "Server is V5R2 or lower, so the 'Do not replace' argument will be ignored.");

      // If the source is a directory, verify that the destination doesn't already exist.
      if (isDirectory() == IFSReturnCodeRep.SUCCESS && exists(destinationPath) == IFSReturnCodeRep.SUCCESS)
          throw new ObjectAlreadyExistsException(destinationPath, ObjectAlreadyExistsException.OBJECT_ALREADY_EXISTS);

      return fd_.copyTo(destinationPath, replace);
  }


  @Override
  public long created() throws IOException, AS400SecurityException
  {
      // Ensure that we are connected to the server.
      fd_.connect();

      long creationDate = 0L;

      // Attempt to list the attributes of the specified file.
      // Note: Do not use cached attributes, since they may be out of date.
      IFSListAttrsRep attrs = getAttributeSetFromServer(fd_.path_);
      if (attrs != null) {
          attributesReply_ = attrs;
          creationDate = attrs.getCreationDate();
      }

      return creationDate;
  }

  @Override
  public int createNewFile() throws IOException, AS400SecurityException
  {
      // Ensure that we are connected to the server.
      fd_.connect();

      return (fd_.checkAccess(IFSOpenReq.WRITE_ACCESS, IFSOpenReq.OPEN_OPTION_CREATE_FAIL));
  }

  @Override
  public int delete() throws IOException, AS400SecurityException
  {
      // Ensure that we are connected to the server.
      fd_.connect();

      // Convert the path name to the server CCSID.
      byte[] pathname = fd_.converter_.stringToByteArray(fd_.path_);

      // Determine if this is a file or directory and instantiate the
      // appropriate type of delete request.
      IFSDataStreamReq req = new IFSDeleteFileReq(pathname, fd_.preferredServerCCSID_);
      try
      {
          if (!isSymbolicLink() && isDirectory() == IFSReturnCodeRep.SUCCESS )
              req = new IFSDeleteDirReq(pathname, fd_.preferredServerCCSID_);
      }
      catch (Exception e)
      {
          if (Trace.traceOn_) Trace.log(Trace.WARNING, "Unable to determine if file or directory.\n" + e.toString());
      
          // If the server has been disconnected, go ahead and throw an exception
          // to avoid the null pointer exception from fs_.server_
          if (fd_.server_ == null)
          {
              if (e instanceof IOException)
                  throw (IOException) e;
              else
                  throw new IOException(e);
          }
      }

      // Delete this entry.
      ClientAccessDataStream ds = null;
      try {
          ds = (ClientAccessDataStream) fd_.server_.sendAndReceive(req);
      }
      catch(ConnectionDroppedException e)
      {
          Trace.log(Trace.ERROR, "Byte stream server connection lost.");
          fd_.connectionDropped(e);
      }
      catch(InterruptedException e)
      {
          Trace.log(Trace.ERROR, "Interrupted");
          InterruptedIOException throwException = new InterruptedIOException(e.getMessage());
          throwException.initCause(e);
          throw throwException;
      }

      // Verify that the request was successful.
      int rc = 0;
      if (ds instanceof IFSReturnCodeRep)
      {
          rc = ((IFSReturnCodeRep) ds).getReturnCode();
          if (rc != IFSReturnCodeRep.SUCCESS) {
              if (Trace.traceOn_) Trace.log(Trace.ERROR, "IFSReturnCodeRep return code", rc);
          }
      } 
      else 
      {
          Trace.log(Trace.ERROR, "Unknown reply data stream", ds.data_);
          throw new InternalErrorException(InternalErrorException.DATA_STREAM_UNKNOWN, Integer.toHexString(ds.getReqRepID()), null);
      }

      // Clear any cached file attributes.
      attributesReply_ = null;

      return (rc);
  }

  /**
   Determines if a file is a directory without a call to the server.
   **/
  private boolean determineIsDirectory(IFSListAttrsRep attributeList)
  {
      boolean answer = false;
      // Determine if the file attributes indicate a directory.
      switch (attributeList.getObjectType())
      {
      case IFSListAttrsRep.DIRECTORY:
      case IFSListAttrsRep.FILE:
          answer = ((attributeList.getFixedAttributes() & IFSListAttrsRep.FA_DIRECTORY) != 0);
          break;
//          * Deleted the following case... treat as default (false)               @D8A
//          * For example, OUTQ, DSPF, PRTF, and TAPF objects are NOT directories  @D8A
//          * LIB, PF, LF, SRCPF are returned as IFSListAttrsRep.DIRECTORY         @D8A
//          * SAVF is returned as IFSListAttrsRep.FILE                             @D8A
//      case IFSListAttrsRep.AS400_OBJECT:                                         @D8D
//         // Server libraries and database files look like directories            @D8D
//         String nameUpper = name.toUpperCase();         // @C2a                  @D8D
//         answer = (nameUpper.endsWith(".LIB") ||                                 @D8D
//                   nameUpper.endsWith(".FILE")); //B1C Changed path_ to name     @D8D
//                   //@C2c
//                   //@B1D Removed code that checked for file separators
//                   //|| path_.endsWith(".LIB" + IFSFile.separator) ||
//                   //path_.endsWith(".FILE" + IFSFile.separator));
//         break;                                                                  @D8D*/
      default:
          answer = false;
      }

      return answer;
  }

  /**
   Determines if a file is a file without a call to the server.
   **/
  private boolean determineIsFile(IFSListAttrsRep attributeList)
  {
      boolean answer = false;
      // Determine if the file attributes indicate a file.
      switch (attributeList.getObjectType())
      {
      case IFSListAttrsRep.DIRECTORY:
      case IFSListAttrsRep.FILE:
          answer = ((attributeList.getFixedAttributes() & IFSListAttrsRep.FA_DIRECTORY) == 0);
          break;
//          * Deleted the following case... treat as default (false)               @D8A
//          * For example, OUTQ, DSPF, PRTF, and TAPF objects are NOT files        @D8A
//          * LIB, PF, LF, SRCPF are returned as IFSListAttrsRep.DIRECTORY         @D8A
//          * SAVF is returned as IFSListAttrsRep.FILE                             @D8A
//      case IFSListAttrsRep.AS400_OBJECT:                                         @D8D
//         //Server libraries and database files look like directories.
//         String nameUpper = name.toUpperCase();         // @C2a                  @D8D
//         answer = !(nameUpper.endsWith(".LIB") ||                                @D8D 
//                    nameUpper.endsWith(".FILE")); //B1C Changed path_ to name    @D8D
//                  //@C2c
//                  //@B1D Removed code that checked for file separators
//                  //|| path_.endsWith(".LIB" + IFSFile.separator) ||
//                  //path_.endsWith(".FILE" + IFSFile.separator));
//         break;                                                                  @D8D*/
      default:
          answer = false;
      }
      
      return answer;
  }

  @Override
  public int exists() throws IOException, AS400SecurityException {
      return exists(fd_.path_);
  }

  /**
   Determines if the integrated file system object represented by this object exists.
   **/
  private int exists(String name) throws IOException, AS400SecurityException
  {
      int returnCode = IFSReturnCodeRep.SUCCESS;

      // Ensure that we are connected to the server.
      fd_.connect();

      returnCode = IFSReturnCodeRep.FILE_NOT_FOUND;
      // Attempt to list the attributes of the specified file.
      try
      {
          IFSListAttrsRep attrs = getAttributeSetFromServer(name);
          if (attrs != null)
          {
              returnCode = IFSReturnCodeRep.SUCCESS;
              attributesReply_ = attrs;
          }
      }
      catch (AS400SecurityException e) {
          returnCode = IFSReturnCodeRep.ACCESS_DENIED_TO_DIR_ENTRY;
          // Note: This is consistent with the behavior of java.io.File on IBM JVMs.
          // On IBM i, java.io.File.exists() returns false if the profile is denied access to the file being queried.
      }

      return (returnCode);
      // Design note:
      // The QlgAccess() API gives somewhat different results in certain scenarios.
      // For example, in one test it returned 'true' when the directory didn't exist.
      // Using IFSListAttrsReq appears to be more reliable.
  }


  /**
   Returns the path name of the integrated file system object represented by this object.  This is the full path starting at the root directory.
   @return The absolute path name of this integrated file system object.
   **/
  String getAbsolutePath() {
      return fd_.path_;
  }

  /**
   Get a list attribute reply from the server for a single entity (get the attributes
   of a specific object, not the attributes of every file in a directory).
  **/
  private IFSListAttrsRep getAttributeSetFromServer(String filePath) throws IOException, AS400SecurityException
  {
      IFSListAttrsRep reply = null;

      // Attempt to list the attributes of the specified file.
      Vector replys = listAttributes(filePath, NO_MAX_GET_COUNT, null, NO_RESTART_NAME, !SORT_LIST);
      // Note: This does setFD() on each returned IFSListAttrsRep.

      // If this is a directory then there must be exactly one reply.
      if (replys != null && replys.size() == 1)
          reply = (IFSListAttrsRep) replys.elementAt(0);

      return reply;
  }

  @Override
  public int getCCSID(boolean retrieveAll) throws IOException, AS400SecurityException {
      return fd_.getCCSID(retrieveAll);
  }

  @Override
  public int getCCSID() throws IOException, AS400SecurityException {
      return fd_.getCCSID();
  }

  private static final boolean SPACE_AVAILABLE  = true;
  private static final boolean SPACE_TOTAL      = false;

  @Override
  public long getAvailableSpace(boolean forUserOnly) throws IOException, AS400SecurityException
  {
      long spaceAvailable = getAmountOfSpace(forUserOnly, SPACE_AVAILABLE);

      // Design note: When querying the space available for a specific user,
      // the File Server team advises us to make two queries:
      // First query the space available to the user (within the user profile's "Maximum Storage Allowed" limit).
      // Then query the total space available in the file system.
      // The smaller of the two values is what we should then report to the application.
      if (forUserOnly)
      {
          long spaceAvailableInFileSystem = getAmountOfSpace(false, SPACE_AVAILABLE);
          spaceAvailable = Math.min(spaceAvailable, spaceAvailableInFileSystem);
      }

      return spaceAvailable;
  }

  @Override
  public long getTotalSpace(boolean forUserOnly) throws IOException, AS400SecurityException {
      return getAmountOfSpace(forUserOnly, SPACE_TOTAL);
  }

  /**
   Returns the amount of storage space.
   @param forUserOnly Whether to report only the space for the user. If false, report space in entire file system.
   @param availableSpaceOnly Whether to report only the space available. If false, report total space, rather than just available space.
   @return The number of bytes of storage.
   Returns special value Long.MAX_VALUE if the system reports "no maximum".
   **/
  private long getAmountOfSpace(boolean forUserOnly, boolean availableSpaceOnly) throws IOException, AS400SecurityException
  {
      // Ensure that we are connected to the server.
      fd_.connect();

      long amountOfSpace = 0L;
      int directoryHandle;
      ClientAccessDataStream ds = null;
      int rc = 0;

      if (forUserOnly) // prepare to get space info for specific user
      {
          // Special value for file handle, indicating that space attributes for the user should be retrieved, rather
          // than space attributes of the file system.
          // According to the PWSI Datastream Spec:
          // "When the client sends 0 as the working directory handle ... the space characteristics of the user are
          // returned instead of the characteristics of the system."
          directoryHandle = 0;
      }
      else // prepare to get space info for the entire file system
      {
          // To query the file system, we need to specify a "working directory handle" to the directory. So first, get a
          // handle.
          String path = fd_.path_;
          if (isDirectory() != IFSReturnCodeRep.SUCCESS)
              path = IFSFile.getParent(fd_.path_);

          byte[] pathname = fd_.getConverter().stringToByteArray(path);
          try
          {
              // Issue a Create Working Directory Handle request.
              IFSCreateDirHandleReq req = new IFSCreateDirHandleReq(pathname, fd_.preferredServerCCSID_);
              ds = (ClientAccessDataStream) fd_.server_.sendAndReceive(req);
          }
          catch(ConnectionDroppedException e)
          {
              Trace.log(Trace.ERROR, "Byte stream server connection lost.");
              fd_.connectionDropped(e);
          }
          catch(InterruptedException e)
          {
              Trace.log(Trace.ERROR, "Interrupted");
              InterruptedIOException throwException = new InterruptedIOException(e.getMessage());
              throwException.initCause(e);
              throw throwException;
          }

          // Verify that we got a handle back.
          rc = 0;
          if (ds instanceof IFSCreateDirHandleRep)
              directoryHandle = ((IFSCreateDirHandleRep) ds).getHandle();
          else if (ds instanceof IFSReturnCodeRep)
          {
              rc = ((IFSReturnCodeRep) ds).getReturnCode();
              if (rc != IFSReturnCodeRep.SUCCESS) Trace.log(Trace.ERROR, "IFSReturnCodeRep return code", rc);
              throw new ExtendedIOException(fd_.path_, rc);
          }
          else
          {
              Trace.log(Trace.ERROR, "Unknown reply data stream", ds.getReqRepID());
              throw new InternalErrorException(Integer.toHexString(ds.getReqRepID()), InternalErrorException.DATA_STREAM_UNKNOWN);
          }
      }

      // Query the amount of space.
      ds = null;
      try
      {
          IFSQuerySpaceReq req = new IFSQuerySpaceReq(directoryHandle);
          ds = (ClientAccessDataStream) fd_.server_.sendAndReceive(req);
      }
      catch(ConnectionDroppedException e)
      {
          Trace.log(Trace.ERROR, "Byte stream server connection lost.");
          fd_.connectionDropped(e);
      }
      catch(InterruptedException e)
      {
          Trace.log(Trace.ERROR, "Interrupted");
          InterruptedIOException throwException = new InterruptedIOException(e.getMessage());
          throwException.initCause(e);
          throw throwException;
      }

      // Verify the reply.
      rc = 0;
      if (ds instanceof IFSQuerySpaceRep)
          amountOfSpace = (availableSpaceOnly) ? ((IFSQuerySpaceRep) ds).getSpaceAvailable() : ((IFSQuerySpaceRep) ds).getTotalSpace();
      else if (ds instanceof IFSReturnCodeRep)
      {
          rc = ((IFSReturnCodeRep) ds).getReturnCode();
          if (rc != IFSReturnCodeRep.SUCCESS) Trace.log(Trace.ERROR, "IFSReturnCodeRep return code", rc);
          throw new ExtendedIOException(fd_.path_, rc);
      }
      else
      {
          Trace.log(Trace.ERROR, "Unknown reply data stream", ds.getReqRepID());
          throw new InternalErrorException(InternalErrorException.DATA_STREAM_UNKNOWN, Integer.toHexString(ds.getReqRepID()), null);
      }

      return amountOfSpace;
  }

  @Override
  public String getFileSystemType() throws IOException, AS400SecurityException {
      return fd_.getFileSystemType();
  }

  @Override
  public String getOwnerName(boolean retrieveAll) throws IOException, AS400SecurityException
  {
      // Design note: This method demonstrates how to get attributes that are returned in the OA1* structure (as opposed to the OA2).
      String ownerName = null;

      fd_.connect();
      
      // The 'owner name' field is in the OA1 structure; the flag is in the first Flags() field.
      try
      {
          IFSListAttrsRep reply = null;
          if (retrieveAll)
              return fd_.getOwnerName();
          else
              reply = fd_.listObjAttrs1(IFSObjAttrs1.OWNER_NAME_FLAG, 0);

          if (reply != null)
              ownerName = reply.getOwnerName(fd_.system_.getCcsid());
          else 
          {
              if (Trace.traceOn_) Trace.log(Trace.WARNING, "getOwnerName: " + "IFSReturnCodeRep return code", fd_.errorRC_);
              
              if (fd_.errorRC_ == IFSReturnCodeRep.FILE_NOT_FOUND || fd_.errorRC_ == IFSReturnCodeRep.PATH_NOT_FOUND)
                  throw new ExtendedIOException(fd_.path_, ExtendedIOException.PATH_NOT_FOUND);
          }
      } 
      catch (ExtendedIOException e)
      {
          if (e.getReturnCode() == ExtendedIOException.DIR_ENTRY_EXISTS) {
              if (Trace.traceOn_) Trace.log(Trace.WARNING, "Unable to determine owner of directory.", e);
          } 
          else
              throw e;
      }

      return (ownerName == null ? "" : ownerName);
  }

@Override
public String getOwnerName() throws IOException, AS400SecurityException
{
    // Design note: This method demonstrates how to get attributes that are returned in the OA1* structure (as opposed to the OA2).
    String ownerName = null;

    fd_.connect();
    
    // The 'owner name' field is in the OA1 structure; the flag is in the first Flags() field.
    try
    {
        IFSListAttrsRep reply = fd_.listObjAttrs1(IFSObjAttrs1.OWNER_NAME_FLAG, 0);
        if (reply != null)
            ownerName = reply.getOwnerName(fd_.system_.getCcsid());
        else
        {
            if (Trace.traceOn_) Trace.log(Trace.WARNING, "getOwnerName: IFSReturnCodeRep return code", fd_.errorRC_);
        
            if (fd_.errorRC_ == IFSReturnCodeRep.FILE_NOT_FOUND || fd_.errorRC_ == IFSReturnCodeRep.PATH_NOT_FOUND)
                throw new ExtendedIOException(fd_.path_, ExtendedIOException.PATH_NOT_FOUND);
        }
    }
    catch (ExtendedIOException e)
    {
        if (e.getReturnCode() == ExtendedIOException.DIR_ENTRY_EXISTS) {
            if (Trace.traceOn_) Trace.log(Trace.WARNING, "Unable to determine owner of directory.", e);
        }
        else 
            throw e;
    }

    return (ownerName == null ? "" : ownerName);
}
  
 @Override
 public String getOwnerNameByUserHandle(boolean forceRetrieve) throws IOException, AS400SecurityException {
     return fd_.getOwnerNameByUserHandle(forceRetrieve);
 }

 @Override
 public int getASP() throws IOException, AS400SecurityException {
     return fd_.getASP();
 }

  // Design note: The following is an alternative implementation of getOwnerName(), using the Qp0lGetAttr API.

//  /**
//   Returns the name of the user profile that is the owner of the file.
//   Returns null if *NOUSRPRF or if error.
//   **/
//  private String getOwnerName(AS400 system)
//    throws AS400SecurityException, ErrorCompletingRequestException, InterruptedException, IOException, ObjectDoesNotExistException
//  {
//    String ownerName = null;
//    int pathnameCCSID = fd_.converter_.getCcsid();
//    ProgramParameter[] parms = new ProgramParameter[7];
//
//    //
//    // Parameter 0 (input, reference): The "path name" structure.
//
//    ByteArrayOutputStream baos = new ByteArrayOutputStream(600);
//    byte[] zeros = { (byte)0, (byte)0, (byte)0, (byte)0, (byte)0,
//                     (byte)0, (byte)0, (byte)0, (byte)0, (byte)0 };
//    // BINARY(4): CCSID of pathname and path delimiter
//    baos.write(BinaryConverter.intToByteArray(pathnameCCSID),0,4);
//    // CHAR(2): Country or region ID.  X'0000' == Use the current job country or region ID.
//    baos.write(zeros,0,2);
//    // CHAR(3): Language ID.  X'000000' == Use the current job language ID.
//    baos.write(zeros,0,3);
//    // CHAR(3): Reserved.  Must be set to hexadecimal zeros.
//    baos.write(zeros,0,3);
//    // BINARY(4): Path type indicator.  0 == The path name is a character string, and the path name delimiter character is 1 character long.
//    baos.write(zeros,0,4);
//    // BINARY(4): Length of path name (in bytes).
//    byte[] pathnameBytes = fd_.getPathnameAsBytes();
//    baos.write(BinaryConverter.intToByteArray(pathnameBytes.length),0,4);
//    // CHAR(2): Path name delimiter character.
//    baos.write(fd_.converter_.stringToByteArray("/"),0,2);
//    // CHAR(10): Reserved.  Must be set to hexadecimal zeros.
//    baos.write(zeros,0,10);
//    // CHAR(*): Path name.
//    baos.write(pathnameBytes, 0, pathnameBytes.length);
//
//    parms[0] = new ProgramParameter(baos.toByteArray());
//    setPassByReference(parms[0]);
//
//    //
//    // Parameter 1 (input, reference): The attribute identifiers array.
//
//    baos.reset();
//    // BINARY(4): Number of requested attributes.
//    baos.write(BinaryConverter.intToByteArray(1),0,4);
//    // BINARY(4): Attribute identifier.  11 == QP0L_ATTR_AUTH, public/private authorities
//    baos.write(BinaryConverter.intToByteArray(11),0,4);
//    parms[1] = new ProgramParameter(baos.toByteArray());
//    setPassByReference(parms[1]);
//
//    //
//    // Parameter 2 (input, reference): Buffer for the returned attribute values.
//    parms[2] = new ProgramParameter(64);
//    setPassByReference(parms[2]);
//
//    //
//    // Parameter 3 (input): Buffer size provided.
//    parms[3] = new ProgramParameter(BinaryConverter.intToByteArray(64));
//
//    //
//    // Parameter 4 (output, reference): Buffer size needed.
//    parms[4] = new ProgramParameter(4);
//    setPassByReference(parms[4]);
//
//    //
//    // Parameter 5 (output, reference): Number of bytes returned.
//    parms[5] = new ProgramParameter(4);
//    setPassByReference(parms[5]);
//
//    // Parameter 6 (input): Follow symlink.  0 == Do not follow symlink; 1 == follow symlink
//    boolean followSymlink = false;  // this should probably be a parameter on the method
//    int follow = (followSymlink ? 1 : 0);
//    parms[6] = new ProgramParameter(BinaryConverter.intToByteArray(follow));
//
//    ServiceProgramCall spc = new ServiceProgramCall(system, "/QSYS.LIB/QP0LLIB2.SRVPGM", "Qp0lGetAttr", ServiceProgramCall.RETURN_INTEGER, parms);
//    //pc.suggestThreadsafe(true);
//
//    if (!spc.run()) {
//      throw new AS400Exception(spc.getMessageList());
//    }
//
//    // Check the returned byte counts.
//    int bufSizeNeeded = BinaryConverter.byteArrayToInt(parms[4].getOutputData(), 0);
//    int numBytesReturned = BinaryConverter.byteArrayToInt(parms[5].getOutputData(), 0);
//
//    // The 'Object Owner' field is the CHAR(10) at offset 16 in the output data.
//    byte[] outputData = parms[2].getOutputData();
//
//    int ccsid = fd_.system_.getCcsid();  // system CCSID (usually EBCDIC)
//    ConvTable conv = ConvTable.getTable(ccsid, null);
//    ownerName = conv.byteArrayToString(outputData, 16, 10).trim();
//    if (ownerName.equals("*NOUSRPRF")) ownerName = null;
//
//    return ownerName;
//  }



  @Override
  public long getOwnerUID() throws IOException, AS400SecurityException
  {
      fd_.connect();
      IFSListAttrsRep reply = fd_.listObjAttrs2();
      
      return (reply != null) ? reply.getOwnerUID() : -1;
  }

  @Override
  public String getPathPointedTo() throws IOException, AS400SecurityException
  {
      // Ensure that we are connected to the server.
      fd_.connect();

      // We will call the QlgReadlink API, to determine the path of the immediately pointed-to file. Note that
      // QlgReadlink is only _conditionally_ threadsafe.

      String resolvedPathname = null;
      try
      {
          // Create the pgm call object
          if (rmtCmd_ == null)
              setupRemoteCommand();
          
          int bufferSizeProvided = 1024;  // large enough for most anticipated paths

          ProgramParameter[] parameters = new ProgramParameter[] {
                  // Parameter 1: Qlg_Path_Name_T *path (input) :
                  new ProgramParameter(ProgramParameter.PASS_BY_REFERENCE, createPathName()),
                  // Parameter 2: Qlg_Path_Name_T *buf (output) :
                  new ProgramParameter(ProgramParameter.PASS_BY_REFERENCE, bufferSizeProvided),
                  // Parameter 3: size_t bufsiz (input) :
                  new ProgramParameter(ProgramParameter.PASS_BY_VALUE, BinaryConverter.intToByteArray(bufferSizeProvided))
          };

          final int HEADER_LENGTH = 32;  // fixed header of returned Qlg_Path_Name_T structure

          boolean repeatRun;
          do
          {
              repeatRun = false;
              
              // Call the service program.
              byte[] returnedBytes = rmtCmd_.runServiceProgram("QSYS", "QP0LLIB1", "QlgReadlink", parameters);
              if (returnedBytes == null)
              {
                  Trace.log(Trace.ERROR, "Call to QlgReadlink() returned null.");
                  throw new AS400Exception(rmtCmd_.getMessageList());
              }

              int returnValue = BinaryConverter.byteArrayToInt(returnedBytes, 0);

              if (returnValue == -1)  // this indicates that we got an "errno" back
              {
                  int errno = BinaryConverter.byteArrayToInt(returnedBytes, 4);
                  switch (errno)
                  {
                  case 3021:  // EINVAL: "The value specified for the argument is not correct."
                      // Assume that we got this error because the file isn't a symlink.
                      if (Trace.traceOn_) 
                          Trace.log(Trace.DIAGNOSTIC, "Received errno "+errno+" from QlgReadlink() for file " + fd_.path_ + ". Assuming that the file is not a symbolic link.");
                      break;
                  case 3025:  // ENOENT: "No such path or directory."
                      // Assume that we got this error because the file isn't a symlink.
                      if (Trace.traceOn_) 
                          Trace.log(Trace.DIAGNOSTIC, "Received errno "+errno+" from QlgReadlink() for file " + fd_.path_ + ". Assuming that the file does not exist.");
                      break;
                  default:    // some other errno
                       Trace.log(Trace.ERROR, "Received errno " + errno + " from QlgReadlink() for file " + fd_.path_);
                       throw new ExtendedIOException(fd_.path_, ExtendedIOException.UNKNOWN_ERROR, errno);
                  }
              }
              else if ((returnValue + HEADER_LENGTH) > bufferSizeProvided)
              {
                  repeatRun = true;
                  // Note: returnValue is number of bytes required to hold complete path.
                  int bufferSizeNeeded = returnValue + HEADER_LENGTH;
                  if (Trace.traceOn_) 
                      Trace.log(Trace.DIAGNOSTIC, "QlgReadlink() buffer too small. Buffer size provided: " + bufferSizeProvided + ". Buffer size needed: " + bufferSizeNeeded+". Calling QlgReadLink() with larger buffer.");
          
                  bufferSizeProvided = bufferSizeNeeded;
                  parameters[1].setOutputDataLength(bufferSizeProvided);
                  parameters[2].setInputData(BinaryConverter.intToByteArray(bufferSizeProvided));
              }
              else  // We allocated a sufficiently large buffer for the returned data.
              {
                  // Parse the pathname from the returned pathname structure (second parameter).
                  resolvedPathname = parsePathName(parameters[1].getOutputData());
              }
          }
          while (repeatRun);
      }
      catch (AS400SecurityException|IOException e) {
          throw e;
      }
      catch (Exception e)
      {
          Trace.log(Trace.ERROR, "Error while resolving symbolic link.", e);
          throw new ExtendedIOException(fd_.path_, ExtendedIOException.UNKNOWN_ERROR);
      }

      return resolvedPathname;
  }


//
// Path name structure for QlgAccess():
// 
//  0 INPUT     BINARY(4)  CCSID
//  4 INPUT     CHAR(2)    Country or region ID
//  6 INPUT     CHAR(3)    Language ID
//  9 INPUT     CHAR(3)    Reserved
// 12 INPUT     BINARY(4)  Path type indicator
// 16 INPUT     BINARY(4)  Length of path name
// 20 INPUT     CHAR(2)    Path name delimiter character
// 22 INPUT     CHAR(10)   Reserved
// 32 INPUT     CHAR(*)    Path name
// 
// 
// Value of "path type indicator" field:
//   2:  The path name is a character string, and the path name delimiter character is 2 characters long.
//


  // Utility method to convert String path into path name parameter used by the QlgAccess() API.
  private byte[] createPathName() throws IOException
  {
      ConverterImplRemote conv = new ConverterImplRemote();
      conv.setCcsid(1200, fd_.system_);
      int pathLength = fd_.path_.length(); // number of Unicode chars

      byte[] buf = new byte[32 + pathLength * 2]; // 2 bytes per Unicode char
      BinaryConverter.intToByteArray(1200, buf, 0); // CCSID
      BinaryConverter.intToByteArray(2, buf, 12); // path type indicator
      BinaryConverter.intToByteArray(pathLength * 2, buf, 16); // length of path name (#bytes)
      conv.stringToByteArray("/", buf, 20, 2); // path name delimiter
      conv.stringToByteArray(fd_.path_, buf, 32); // path name
      
      return buf;
  }


  // Utility method to parse the path name parameter returned by the QlgAccess() API.
  private String parsePathName(byte[] buf) throws IOException
  {
      ConverterImplRemote conv = new ConverterImplRemote();
      conv.setCcsid(1200, fd_.system_);
      int offset = 0;
      int nameLength;
      if (DEBUG)
      {
          System.out.println("Buffer length: " + buf.length);
          System.out.println("CCSID: " + BinaryConverter.byteArrayToInt(buf, offset));
          offset += 4;
          // System.out.println("Country: " + conv.byteArrayToString(buf, offset, 2));
          offset += 2;
          // System.out.println("LangID: " + conv.byteArrayToString(buf, offset, 3));
          offset += 3;
          offset += 3; // reserved field
          System.out.println("Path type: " + BinaryConverter.byteArrayToInt(buf, offset));
          offset += 4;
          nameLength = BinaryConverter.byteArrayToInt(buf, offset);
          System.out.println("Path name length: " + nameLength);
          offset += 4;
          System.out.println("Delimiter: " + conv.byteArrayToString(buf, offset, 2));
          offset += 2;
          offset += 10; // reserved field
      }
      else 
      {
          offset += 16;
          nameLength = BinaryConverter.byteArrayToInt(buf, offset);
          offset += 16;
      }

      // We will assume that the caller has verified that the buffer was big enough to accommodate the returned data.
      String pathname = conv.byteArrayToString(buf, offset, nameLength);
      
      return pathname;
  }

  // Utility method to parse the structure returned by the QlgStat() API.
  private int parseStatInfo(byte[] buf) throws IOException
  {
      ConverterImplRemote conv = new ConverterImplRemote();
      conv.setCcsid(37, fd_.system_); // always EBCDIC

      int fileMode = BinaryConverter.byteArrayToInt(buf, 0);
      return fileMode;


// This is for future reference, in case we ever want to exploit other returned fields.
// Note that the QlgStat structure is specified in system header files stat.h and types.h
//
//    int offset = 0;
//    System.out.println("File mode (octal): " + Integer.toOctalString(fileMode) );
//    offset += 4;
//    System.out.println("File serial number: " + BinaryConverter.byteArrayToInt(buf, offset));
//    offset += 4;
//    System.out.println("Number of links: " + BinaryConverter.byteArrayToShort(buf, offset));
//    offset += 2;
//    offset += 2;  // reserved field
//    System.out.println("UID of owner: " + BinaryConverter.byteArrayToInt(buf, offset));
//    offset += 4;
//    System.out.println("Group ID: " + BinaryConverter.byteArrayToInt(buf, offset));
//    offset += 4;
//    System.out.println("File size (#bytes): " + BinaryConverter.byteArrayToInt(buf, offset));
//    offset += 4;
//    System.out.println("Time of last access: " + BinaryConverter.byteArrayToInt(buf, offset));
//    offset += 4;
//    System.out.println("Time of last mod: " + BinaryConverter.byteArrayToInt(buf, offset));
//    offset += 4;
//    System.out.println("Time of status chg: " + BinaryConverter.byteArrayToInt(buf, offset));
//    offset += 4;
//    System.out.println("Device ID: " + BinaryConverter.byteArrayToInt(buf, offset));
//    offset += 4;
//    System.out.println("Block size (#bytes): " + BinaryConverter.byteArrayToInt(buf, offset));
//    offset += 4;
//    System.out.println("Allocation size (#bytes): " + BinaryConverter.byteArrayToInt(buf, offset));
//    offset += 4;
//    System.out.println("Object type: " + conv.byteArrayToString(buf, offset, 10)); // field is 11 characters, null-terminated, so exclude the final null
//    offset += 11;
//    offset += 1;  // reserved
//    System.out.println("Data codepage: " + BinaryConverter.byteArrayToShort(buf, offset));
//    offset += 2;
//    System.out.println("Data CCSID: " + BinaryConverter.byteArrayToShort(buf, offset));
//    offset += 2;
//    System.out.println("Device ID: " + BinaryConverter.byteArrayToInt(buf, offset));
//    offset += 4;
//    System.out.println("Number of links: " + BinaryConverter.byteArrayToInt(buf, offset));
//    offset += 4;
//    System.out.println("Device ID (64bit): " + BinaryConverter.byteArrayToLong(buf, offset));
//    offset += 8;
//    System.out.println("File system ID (64bit): " + BinaryConverter.byteArrayToLong(buf, offset));
//    offset += 8;
//    System.out.println("Mount ID: " + BinaryConverter.byteArrayToInt(buf, offset));
//    offset += 4;
//    offset += 32;  // reserved
//    System.out.println("Serial# gen ID: " + BinaryConverter.byteArrayToInt(buf, offset));
  }


  @Override
  public String getSubtype() throws IOException, AS400SecurityException
  {
      String subtype = "";

      // Ensure that we are connected to the server.
      fd_.connect();
    
      // Convert the path name to the server CCSID.
      byte[] pathname = fd_.converter_.stringToByteArray(fd_.path_);

      boolean needCodePage = (fd_.getSystemVRM() >= 0x00060100 && fd_.path_.indexOf("/QSYS.LIB") != -1);

      // Prepare the List Attributes request.

      // Set up the list of Extended Attributes Names.
      byte[] eaName_TYPE = fd_.converter_.stringToByteArray(".TYPE");
      int eaNameBytesLength;
      byte[][] eaNamesList;
      
      // Special handling for QSYS files, starting in V6R1.
      // Starting in V6R1, for QSYS files, the ".TYPE" EA value field is returned in the CCSID of the object.
      // Prior to V6R1, the field is always returned in EBCDIC.
      if (needCodePage)
      {
          byte[] eaName_CODEPAGE = fd_.converter_.stringToByteArray(".CODEPAGE");
          eaNameBytesLength = eaName_TYPE.length + eaName_CODEPAGE.length;
          eaNamesList = new byte[][] { eaName_TYPE, eaName_CODEPAGE };
      }
      else  // not in QSYS, or pre-V6R1
      {
          eaNameBytesLength = eaName_TYPE.length;
          eaNamesList = new byte[][] { eaName_TYPE };
      }

      IFSListAttrsReq req = new IFSListAttrsReq(pathname, fd_.preferredServerCCSID_,
                                                IFSListAttrsReq.NO_AUTHORITY_REQUIRED, NO_MAX_GET_COUNT,
                                                null, false, eaNamesList, eaNameBytesLength, false, fd_.patternMatching_);

      Vector replys = fd_.listAttributes(req);

      // Verify that we got at least one reply.
      if (replys == null) {
          if (Trace.traceOn_) Trace.log(Trace.WARNING, "Received null from listAttributes(req).");
      }
      else if (replys.size() == 0) {
          if (Trace.traceOn_) Trace.log(Trace.WARNING, "Received no replies from listAttributes(req).");
      }
      else
      {
          if (replys.size() > 1) {
              if (Trace.traceOn_) Trace.log(Trace.WARNING, "Received multiple replies from listAttributes(req) (" + replys.size() + ")");
          }
          
          IFSListAttrsRep reply = (IFSListAttrsRep)replys.elementAt(0);
          Hashtable extendedAttributes = reply.getExtendedAttributeValues();
          byte[] subtypeAsBytes = (byte[])extendedAttributes.get(".TYPE");
          
          if (subtypeAsBytes != null)
          {
              int ccsid;
              if (!needCodePage)
                  ccsid = 37; // the returned bytes are in EBCDIC
              else 
              {
                  // Get the ".CODEPAGE" extended attribute value from the reply.
                  byte[] codepageAsBytes = (byte[])extendedAttributes.get(".CODEPAGE");
                  // The .CODEPAGE attribute is returned as 2 bytes in little-endian format.
                  // Therefore we need to swap the bytes.
                  byte[] swappedBytes = new byte[2];  // the codepage is returned in 2 bytes
                  swappedBytes[0] = codepageAsBytes[1];
                  swappedBytes[1] = codepageAsBytes[0];
                  ccsid = BinaryConverter.byteArrayToUnsignedShort(swappedBytes,0);
                  if (ccsid == 1400) 
                      ccsid = 1200;  // codepage 1400 corresponds to CCSID 1200
              }
              
               try {
                   subtype = (new CharConverter(ccsid)).byteArrayToString(subtypeAsBytes, 0).trim();
               }
               catch (java.io.UnsupportedEncodingException e) {
                   Trace.log(Trace.ERROR, "Unrecognized codepage returned: " + ccsid, e);
                   subtype = "??";
               }
          }
      }
      
      return subtype;
  }

  @Override
  public boolean isSourcePhysicalFile() throws IOException, AS400SecurityException, AS400Exception
  {
      // Assume that the caller has verified that the file is a Physical File in QSYS.

      // Layout of first 2 attribute bytes returned in FILD0100 format:
      //
      // BIT(2): Reserved.
      // BIT(1): Type of file. If on, the file is a logical database file.
      // If off, a physical database file.
      // BIT(1): Reserved.
      // BIT(1): File type (FILETYPE). If on, the file is a source file (*SRC).
      // If off, a data file (*DATA).
      // BIT(1): Reserved.
      // BIT(1): Access path. If on, the file has a keyed sequence access path.
      // If off, an arrival sequence access path.
      // BIT(1): Reserved.
      // BIT(1): Record format level check (LVLCHK).
      // If on, the record format level identifiers are checked when the file is opened (*YES).
      // If off, they are not checked when the file is opened (*NO).
      // BIT(1): Select/omit. If on, the file is a select/omit logical file.
      // BIT(4): Reserved.
      // BIT(1): Double-byte character set (DBCS) or Graphic data.
      // If on, the file's record format(s) contains DBCS or Graphic data fields.
      // BIT(1): Double-byte character set (DBCS) or Graphic literals.
      // If on, the file's record format(s) contains DBCS or Graphic literals.
      //

      // Examine the FILETYPE bit (the 12th bit from the right).
      // If the bit is on, that indicates the file is a source file (*SRC).
      // If the bit is off, that indicates the file is a data file (*DATA).
      int attributeFlags = retrieveDatabaseFileAttributes();
      return ((attributeFlags & 0x00000800) != 0);  // 12th bit from the right
  }

  /**
   Call QDBRTVFD (if necessary) to get additional status information about the file.
   If the file does not exist, throws an ObjectDoesNotExistException.
   **/
  private int retrieveDatabaseFileAttributes() throws IOException, AS400SecurityException, AS400Exception
  {
      if (databaseFileAttributes_ == null)
      {
          try
          {
              int bufferSizeProvided = 400; // the FILD0100-format structure occupies 400 bytes
              ProgramParameter[] parameters = new ProgramParameter[] {
                      // Receiver variable, Output, Char(*)
                      new ProgramParameter(bufferSizeProvided),
                      // Length of receiver variable, Input, Binary(4)
                      new ProgramParameter(BinaryConverter.intToByteArray(bufferSizeProvided)),
                      // Qualified returned file name, Output, Char(20)
                      new ProgramParameter(20),
                      // Format name, Input, Char(8) : EBCDIC 'FILD0100'
                      new ProgramParameter(new byte[] { (byte)0xC6, (byte)0xC9, (byte)0xD3, (byte)0xC4, (byte)0xF0, (byte)0xF1, (byte)0xF0, (byte)0xF0 } ),
                      // Qualified file name, Input, Char(20) : fileName + libraryName
                      new ProgramParameter(composeQualifiedNameBytes()),
                      // Record format name, Input, Char(10) : 10 EBCDIC blanks
                      new ProgramParameter(new byte[] { (byte)0x40, (byte)0x40, (byte)0x40, (byte)0x40, (byte)0x40, (byte)0x40, (byte)0x40, (byte)0x40, (byte)0x40, (byte)0x40 } ),
                      // Override processing, Input, Char(1) : EBCDIC '0' (no override processing)
                      new ProgramParameter(new byte[] { (byte)0xF0 } ),
                      // System, Input, Char(10) : EBCDIC '*LCL' (local files only)
                      new ProgramParameter(new byte[] { (byte)0x5C, (byte)0xD3, (byte)0xC3, (byte)0xD3, (byte)0x40, (byte)0x40, (byte)0x40, (byte)0x40, (byte)0x40, (byte)0x40 } ),
                      // Format type, Input, Char(10) : EBCDIC '*INT'
                      new ProgramParameter(new byte[] { (byte)0x5C, (byte)0xC9, (byte)0xD5, (byte)0xE3, (byte)0x40, (byte)0x40, (byte)0x40, (byte)0x40, (byte)0x40, (byte)0x40 } ),
                      // Error Code, I/O, Char(*)
                      new ErrorCodeParameter()
              };

              // Create the pgm call object
              if (rmtCmd_ == null)
                  setupRemoteCommand();

              if (!rmtCmd_.runProgram("QSYS", "QDBRTVFD", parameters)) // conditionally threadsafe
                  throw new AS400Exception(rmtCmd_.getMessageList());

              byte[] outputData = parameters[0].getOutputData();
              int bytesReturned = BinaryConverter.byteArrayToInt(outputData, 0);
              if (bytesReturned < 10)
              {
                  Trace.log(Trace.ERROR, "Insufficient output bytes returned from QDBRTVFD: " + bytesReturned);
                  throw new InternalErrorException(fd_.path_, InternalErrorException.UNKNOWN);
              }

               // Grab the "attribute bytes". These are the 2 bytes starting at offset 8.
              databaseFileAttributes_ = Integer.valueOf(BinaryConverter.byteArrayToUnsignedShort(outputData, 8));
          }
          catch (AS400Exception|AS400SecurityException|IOException e) {
              throw e;
          }
          catch (Exception e) {
              Trace.log(Trace.ERROR, "Error while retrieving database file attributes.", e);
              throw new ExtendedIOException(fd_.path_, ExtendedIOException.UNKNOWN_ERROR);
          }
      }

      return databaseFileAttributes_.intValue();
  }

  // Setup qualified file name program parameter object on first touch.  Synchronized to protect instance variables.  
  // This method can safely be called multiple times because it checks for a previous call before changing the instance variables.
  private synchronized byte[] composeQualifiedNameBytes() throws IOException
  {
      // If not already setup.
      if (qualifiedFileName_ == null)
      {
          CharConverter converter = new CharConverter(37); // converts Unicode chars to EBCDIC bytes

          // Start with 20 EBCDIC spaces.
          qualifiedFileName_ = new byte[] { 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40 };

          // Parse out the library and filename from the path.
          QSYSObjectPathName qsysPath = new QSYSObjectPathName(fd_.path_);
          String libraryName = qsysPath.getLibraryName();
          String fileName    = qsysPath.getObjectName();

          // Put the converted file name at the beginning of the array.
          converter.stringToByteArray(fileName, qualifiedFileName_, 0);
          // Put the converted library name at position ten.
          converter.stringToByteArray(libraryName, qualifiedFileName_, 10);
      }

      return qualifiedFileName_;
  }

  @Override
  public int isDirectory() throws IOException, AS400SecurityException 
  {
      // Ensure that we are connected to the server.
      fd_.connect();

      int returnCode = IFSReturnCodeRep.FILE_NOT_FOUND;

      if (attributesReply_ == null)
          attributesReply_ = getAttributeSetFromServer(fd_.path_);

      if (attributesReply_ != null)
          if (determineIsDirectory(attributesReply_))
              returnCode = IFSReturnCodeRep.SUCCESS;

      return returnCode;
  }

  @Override
  public int isFile() throws IOException, AS400SecurityException 
  {
      // Ensure that we are connected to the server.
      fd_.connect();

      int returnCode = IFSReturnCodeRep.FILE_NOT_FOUND;

      if (attributesReply_ == null)
          attributesReply_ = getAttributeSetFromServer(fd_.path_);

      if (attributesReply_ != null)
          if (determineIsFile(attributesReply_))
              returnCode = IFSReturnCodeRep.SUCCESS;

      return returnCode;
  }

  @Override
  public boolean isHidden() throws IOException, AS400SecurityException
  {
      // Ensure that we are connected to the server.
      fd_.connect();

      boolean result = false;

      // Attempt to get the attributes of this object if we do not have them.
      if (attributesReply_ == null)
          attributesReply_ = getAttributeSetFromServer(fd_.path_);

      // Determine if the file attributes indicate hidden.
      if (attributesReply_ != null)
          result = (attributesReply_.getFixedAttributes() & IFSListAttrsRep.FA_HIDDEN) != 0;

      return result;
  }
  
  private boolean isInQsys() 
  {
      // TODO This code is flawed, since it does not take into account iASP paths. Needs to be corrected.
      
      String lowercasePath = fd_.path_.toLowerCase().replaceAll("//+", "/");
      return lowercasePath.equals("/qsys.lib") || lowercasePath.startsWith("/qsys.lib/");
  }

  @Override
  public boolean isReadOnly() throws IOException, AS400SecurityException
  {
      // Ensure that we are connected to the server.
      fd_.connect();

      boolean result = false;

      // Attempt to get the attributes of this object if we do not have them.
      if (attributesReply_ == null)
          attributesReply_ = getAttributeSetFromServer(fd_.path_);

      // Determine if the file attributes indicate hidden.
      if (attributesReply_ != null)
          result = (attributesReply_.getFixedAttributes() & IFSListAttrsRep.FA_READONLY) != 0;

      return result;
  }

  @Override
  public boolean isSymbolicLink() throws IOException, AS400SecurityException
  {
      // Ensure that we are connected to the server.
      fd_.connect();

      if (fd_.getSystemVRM() < 0x00050300)
      {
          if (Trace.traceOn_) Trace.log(Trace.WARNING, "Server is V5R2 or lower, so isSymbolicLink() will always report false.");
          return false;
      }

      if (isSymbolicLink_ == null)
      {
          // QSYS doesn't support symbolic links, so no need to check
          if(isInQsys()) 
          {
              isSymbolicLink_ = false;
              return isSymbolicLink_;
          }
          
          // Note: As of V5R3, we can't get accurate symbolic link info by querying the attrs of a specific file.
          // Instead, we must query the contents of the parent directory.
          int pathLen = fd_.path_.length();
          if (pathLen <= 1)
          {
              if (Trace.traceOn_) 
                  Trace.log(Trace.DIAGNOSTIC, "Path length is less than 2, so assuming not a symbolic link: " + fd_.path_);
              
              isSymbolicLink_ = false;
          }
          else
          {
              // Do a wildcard search.
              StringBuffer wildCardPatternBuf = new StringBuffer(fd_.path_);
              wildCardPatternBuf.setCharAt(pathLen-1, '*');
              String wildCardPattern = wildCardPatternBuf.toString();
              String dirPath = wildCardPattern.substring(0,1+wildCardPattern.lastIndexOf('/'));

              byte[] pathBytes = fd_.converter_.stringToByteArray(wildCardPattern);
              IFSCachedAttributes[] attrList = listDirectoryDetails(wildCardPattern, dirPath, NO_MAX_GET_COUNT, pathBytes, IS_RESTART_NAME, !SORT_LIST);

              IFSCachedAttributes attrs = null;
              String filename = fd_.path_.substring(1+(fd_.path_.lastIndexOf('/')));
              
              for (int i=0; attrs == null && i> 8 != 0x00000502)
      {
          // Attempt to list the attributes of the specified file.
          // Note: Do not use cached attributes, since they may be out of date.
          IFSListAttrsRep attrs = getAttributeSetFromServer(fd_.path_);

          if (attrs != null)
          {
              attributesReply_ = attrs;
              size = attrs.getSize(fd_.serverDatastreamLevel_);
          }
      }
      else // the system is V5R2 (and therefore, datastream level is 3)
      {
          // Convert the path name to the server CCSID.
          byte[] pathname = fd_.converter_.stringToByteArray(fd_.path_);

          // Send the List Attributes request. Indicate that we want the "8-byte file size".
          IFSListAttrsReq req = new IFSListAttrsReq(pathname, fd_.preferredServerCCSID_,
                                                    IFSListAttrsReq.NO_AUTHORITY_REQUIRED, NO_MAX_GET_COUNT, 
                                                    null, true, null, 0, true, fd_.patternMatching_);
          Vector replys = fd_.listAttributes(req);

          if (replys == null)
          {
              if (fd_.errorRC_ != 0)
                  throw new ExtendedIOException(fd_.path_, fd_.errorRC_);
              else
                  throw new InternalErrorException(InternalErrorException.UNKNOWN);
          } 
          else if (replys.size() == 0)
          {
              // Assume this simply indicates that the file does not exist.
              if (Trace.traceOn_) Trace.log(Trace.WARNING, "Received no replies from listAttributes(req).");
          } 
          else 
          {
              if (replys.size() > 1 && Trace.traceOn_)
                  Trace.log(Trace.WARNING, "Received multiple replies from listAttributes(req) (" + replys.size() + ")");

              IFSListAttrsRep reply = (IFSListAttrsRep) replys.elementAt(0); // use first reply
              size = reply.getSize8Bytes(/* fd_.serverDatastreamLevel_ */);
          }
      }
      
      return size;
  }

  // Fetch list attributes reply(s) for the specified path.
  private Vector listAttributes(String path, int maxGetCount, byte[] restartNameOrID, boolean isRestartName, boolean sortList)
    throws IOException, AS400SecurityException
  {
      // Assume connect() has already been done.
      
      if (Trace.traceOn_) Trace.log(Trace.DIAGNOSTIC, "list attributes for: " + path);

      // Convert the pathname to the server CCSID.
      byte[] pathname = fd_.converter_.stringToByteArray(path);

      // Prepare the 'list attributes' request.
      IFSListAttrsReq req = new IFSListAttrsReq(pathname, fd_.preferredServerCCSID_,   
                                              IFSListAttrsReq.NO_AUTHORITY_REQUIRED, maxGetCount, 
                                              restartNameOrID, isRestartName,  null, 0, false, fd_.patternMatching_);
    
      if (sortList) 
          req.setSorted(true);
    
      return fd_.listAttributes(req);  // Note: This does setFD() on each returned IFSListAttrsRep..
  }


  @Override
  public String[] listDirectoryContents(String directoryPath) throws IOException, AS400SecurityException
  {
      // Ensure that we are connected to the server.
      fd_.connect();

      Vector replys = listAttributes(directoryPath, NO_MAX_GET_COUNT, null, NO_RESTART_NAME, sortLists_);
      String[] names = null;

      // Add the name for each file or directory in the specified directory,
      // to the array of names.

      // Changed the behavior of the list() to conform to that of the JDK1.1.x
      // so that a NULL is returned if and only if the directory or file represented
      // by this IFSFile object doesn't exist.
      if (replys != null)
      {
          names = new String[replys.size()];
          int j = 0;
          for (int i = 0; i < replys.size(); i++)
          {
              IFSListAttrsRep reply = (IFSListAttrsRep) replys.elementAt(i);
              String name = fd_.converter_.byteArrayToString(reply.getName(/* dsl */));
              
              if (!(name.equals(".") || name.equals("..")))
                  names[j++] = name;
          }

          if (j == 0) 
              names = new String[0];
          else if (names.length != j)
          {
              // Copy the names to an array of the exact size.
              String[] newNames = new String[j];
              System.arraycopy(names, 0, newNames, 0, j);
              names = newNames;
          }
      }

      return names;
  }

  // Added this function to support caching attributes.
  // List the files/directories details in the specified directory.
  // Returns null if specified file or directory does not exist.
  private IFSCachedAttributes[] listDirectoryDetails(String pathPattern, String directoryPath,
                                                    int maxGetCount,  byte[] restartNameOrID, boolean isRestartName,
                                                    boolean sortList) throws IOException, AS400SecurityException
  {
      // Ensure that we are connected to the server.
      fd_.connect();

      IFSCachedAttributes[] fileAttributes = null;

      try
      {
          // Design note: Due to a limitation in the File Server, if we specify a "filename pattern", we cannot get OA1
          // or OA2 structures in the reply.
          // Only "handle-based" requests can get OA* structures in the reply; and a handls is specific to a single
          // file.
          // This prevents us, for example, from obtaining the "name of file owner" values for an entire list of files
          // at once; rather, we must obtain that attribute one file at a time.
          Vector replys = listAttributes(pathPattern, maxGetCount, restartNameOrID, isRestartName, sortList);

          // Add each file or directory in the specified directory,
          // to the array of files.

          int j = 0;
          if (replys != null)
          {
              fileAttributes = new IFSCachedAttributes[replys.size()];
              int dsl = fd_.serverDatastreamLevel_;
            
              for (int i = 0; i < replys.size(); i++)
              {
                  IFSListAttrsRep reply = (IFSListAttrsRep) replys.elementAt(i);
                  String name = fd_.converter_.byteArrayToString(reply.getName(/*dsl*/));
                  if (!(name.equals(".") || name.equals("..")))
                  {
                      // isDirectory and isFile should be different unless the
                      // file is an invalid symbolic link (circular or points
                      // to a non-existent object). Such a link cannot
                      // be resolved and both determineIsDirectory and
                      // determineIsFile will return false. Regular symbolic links
                      // will resolve. For example, a symbolic link to a file will return
                      // true from isFile and false from determineIsDirectory.
                      boolean isDirectory = determineIsDirectory(reply);
                      boolean isFile = determineIsFile(reply);
                      IFSCachedAttributes attributes = new IFSCachedAttributes(reply.getAccessDate(),
                            reply.getCreationDate(), reply.getFixedAttributes(), reply.getModificationDate(),
                            reply.getObjectType(), reply.getSize(dsl), name, directoryPath, isDirectory, isFile, 
                            reply.getRestartID(), reply.isSymbolicLink(dsl), reply.getFileSystemType(dsl));
                      fileAttributes[j++] = attributes;
                  }
              }
          }

          if (j == 0)
              fileAttributes = new IFSCachedAttributes[0];
          else if (fileAttributes.length != j)
          {
              //Copy the attributes to an array of the exact size.
              IFSCachedAttributes[] newFileAttributes = new IFSCachedAttributes[j];
              System.arraycopy(fileAttributes, 0, newFileAttributes, 0, j);
              fileAttributes = newFileAttributes;
          }
      }
      catch (AS400SecurityException e) 
      {
          fileAttributes = null;
          throw e;
      }
      
      return fileAttributes;
  }

  @Override
  public IFSCachedAttributes[] listDirectoryDetails(String pathPattern, String directoryPath,
                                                    int maxGetCount, String restartName) throws IOException, AS400SecurityException
  {
      byte[] restartNameBytes = fd_.converter_.stringToByteArray(restartName);
      return listDirectoryDetails(pathPattern, directoryPath, maxGetCount, restartNameBytes, IS_RESTART_NAME, sortLists_);                                        //@D7C
  }


  @Override
  public IFSCachedAttributes[] listDirectoryDetails(String pathPattern, String directoryPath,
                                                    int maxGetCount, byte[] restartID,
                                                    boolean allowSortedRequests) throws IOException, AS400SecurityException
  {
      boolean sortParameter = (allowSortedRequests ? sortLists_ : false);
      return listDirectoryDetails(pathPattern, directoryPath, maxGetCount, restartID, !IS_RESTART_NAME, sortParameter);
  }

  @Override
  public int mkdir(String directory) throws IOException, AS400SecurityException
  {
      // Ensure that the path name is set.
      fd_.connect();

      int returnCode = IFSReturnCodeRep.FILE_NOT_FOUND;

      try
      {
          // Convert the directory name to the server CCSID.
          byte[] pathname = fd_.converter_.stringToByteArray(directory);

          // Send a create directory request.
          IFSCreateDirReq req = new IFSCreateDirReq(pathname, fd_.preferredServerCCSID_);
          ClientAccessDataStream ds = (ClientAccessDataStream) fd_.server_.sendAndReceive(req);

          // Verify the reply.
          if (ds instanceof IFSReturnCodeRep)
          {
              returnCode = ((IFSReturnCodeRep) ds).getReturnCode();
              if (returnCode == IFSReturnCodeRep.ACCESS_DENIED_TO_DIR_ENTRY
                      || returnCode == IFSReturnCodeRep.ACCESS_DENIED_TO_REQUEST)
                  throw new AS400SecurityException(directory, AS400SecurityException.DIRECTORY_ENTRY_ACCESS_DENIED);
          } 
          else 
          {
              Trace.log(Trace.ERROR, "Unknown reply data stream", ds.data_);
              throw new InternalErrorException(Integer.toHexString(ds.getReqRepID()), InternalErrorException.DATA_STREAM_UNKNOWN);
          }
      } 
      catch (ConnectionDroppedException e)
      {
          Trace.log(Trace.ERROR, "Byte stream server connection lost.");
          fd_.connectionDropped(e);
      } 
      catch (InterruptedException e)
      {
          Trace.log(Trace.ERROR, "Interrupted");
          InterruptedIOException throwException = new InterruptedIOException(e.getMessage());
          throwException.initCause(e);
          throw throwException;
      }

      return returnCode;
  }

  @Override
  public int mkdirs() throws IOException, AS400SecurityException
  {
      // Ensure that we are connected to the server.
      fd_.connect();

      // Traverse up the parent directories until the first parent
      // directory that exists is found, saving each parent directory
      // as we go.
      Vector nonexistentDirs = new Vector();
      String directory = fd_.path_;
      int returnCode = IFSReturnCodeRep.FILE_NOT_FOUND;

      returnCode = exists(directory);
      if (returnCode != IFSReturnCodeRep.SUCCESS)
      {
          do
          {
              nonexistentDirs.addElement(directory);
              directory = IFSFile.getParent(directory);
          } 
          while ((directory != null) && (exists(directory) != IFSReturnCodeRep.SUCCESS));
      } 
      else
          returnCode = IFSReturnCodeRep.DUPLICATE_DIR_ENTRY_NAME;

      // Create each parent directory in the reverse order that
      // they were saved.
      for (int i = nonexistentDirs.size(); i > 0; i--)
      {
          directory = (String) nonexistentDirs.elementAt(i - 1);

          // Create the next directory.
          returnCode = mkdir(directory);
          if (returnCode != IFSReturnCodeRep.SUCCESS)
          {
              // Unable to create a directory.
              break;
          }
      }

      return returnCode;
  }

  @Override
  public int renameTo(IFSFileImpl file) throws IOException, AS400SecurityException
  {
      // Ensure that we are connected to the server.
      fd_.connect();

      // Assume the argument has been validated by the public class.

       // Rename the file.
      boolean success = false;
      ClientAccessDataStream ds = null;
      IFSFileImplRemote otherFile = (IFSFileImplRemote)file;
      try
      {
          // Convert the path names to the server CCSID.
          byte[] oldName = fd_.converter_.stringToByteArray(fd_.path_);
          byte[] newName = fd_.converter_.stringToByteArray(otherFile.getAbsolutePath());

          // Issue a rename request.
          IFSRenameReq req = new IFSRenameReq(oldName, newName, fd_.preferredServerCCSID_, false);
          ds = (ClientAccessDataStream) fd_.server_.sendAndReceive(req);
      }
      catch(ConnectionDroppedException e)
      {
          Trace.log(Trace.ERROR, "Byte stream server connection lost.");
          fd_.connectionDropped(e);
      }
      catch(InterruptedException e)
      {
          Trace.log(Trace.ERROR, "Interrupted");
          InterruptedIOException throwException = new InterruptedIOException(e.getMessage());
          throwException.initCause(e);
          throw throwException;
      }

      int returnCode = IFSReturnCodeRep.FILE_NOT_FOUND;
      // Verify the reply.
      if (ds instanceof IFSReturnCodeRep)
      {
          returnCode = ((IFSReturnCodeRep) ds).getReturnCode();
          if (returnCode == IFSReturnCodeRep.SUCCESS)
              success = true;
          else if (Trace.traceOn_)
              Trace.log(Trace.ERROR, "Error renaming file: IFSReturnCodeRep return code", returnCode);
      }
      else
      {
          Trace.log(Trace.ERROR, "Unknown reply data stream", ds.data_);
          throw new InternalErrorException(InternalErrorException.DATA_STREAM_UNKNOWN, Integer.toHexString(ds.getReqRepID()), null);
      }

      if (success)
      {
          fd_.path_ = otherFile.getAbsolutePath();

          // Clear any cached attributes.
          attributesReply_ = null;
      }

      return returnCode;
  }



  @Override
  public boolean setCCSID(int ccsid) throws IOException, AS400SecurityException
  {
      // To change the file data CCSID, we need to get the file's current attributes (in an OA2 structure), reset the
      // 'CCSID' field, and then send back the modified OA2 struct in a Change Attributes request.

      fd_.connect();
      IFSListAttrsRep reply = fd_.listObjAttrs2(); // get current attributes (OA2 structure)
      if (reply == null)
      {
          if (fd_.errorRC_ != 0)
              throw new ExtendedIOException(fd_.path_, fd_.errorRC_);
          else
              throw new InternalErrorException(InternalErrorException.UNKNOWN);
      }

      boolean success = false;
      byte[] objAttrs2Null = new byte[reply.getObjAttrs2().getLength()];
      Arrays.fill(objAttrs2Null, (byte) 0xFF);
      IFSObjAttrs2 objAttrs = new IFSObjAttrs2(objAttrs2Null);

      // Sanity-check the length: If it's an OA2a or OA2b, the length will be 144 bytes. If it's an OA2c, the length
      // will be 160 bytes.
      if (Trace.traceOn_)
          Trace.log(Trace.DIAGNOSTIC, "Length of returned OA2* structure (should be 144 or 160): " + objAttrs.length());

      // Reset the "CCSID of the object" field in the OA2* structure.
      objAttrs.setCCSID(ccsid, fd_.serverDatastreamLevel_);

      // Issue a change attributes request.
      ClientAccessDataStream ds = null;
      try 
      {
          // Convert the path name to the server CCSID.
          byte[] pathname = fd_.converter_.stringToByteArray(fd_.path_);

          IFSChangeAttrsReq req = new IFSChangeAttrsReq(pathname, fd_.preferredServerCCSID_, objAttrs, fd_.serverDatastreamLevel_);
          ds = (ClientAccessDataStream) fd_.server_.sendAndReceive(req);
      }
      catch (ConnectionDroppedException e) {
          Trace.log(Trace.ERROR, "Byte stream server connection lost.");
          fd_.connectionDropped(e);
      }
      catch (InterruptedException e)
      {
          Trace.log(Trace.ERROR, "Interrupted");
          InterruptedIOException throwException = new InterruptedIOException(e.getMessage());
          throwException.initCause(e);
          throw throwException;
      }

      if (ds instanceof IFSReturnCodeRep)
      {
          int rc = ((IFSReturnCodeRep) ds).getReturnCode();
          if (rc == IFSReturnCodeRep.SUCCESS)
          {
              success = true;
              fd_.setCCSID(ccsid); // update the cached CCSID value in the fd_
          } 
          else if (Trace.traceOn_)
              Trace.log(Trace.ERROR, "Error setting file data CCSID: IFSReturnCodeRep return code", rc);
      } 
      else
      {
          Trace.log(Trace.ERROR, "Unknown reply data stream", ds.data_);
          throw new InternalErrorException(InternalErrorException.DATA_STREAM_UNKNOWN, Integer.toHexString(ds.getReqRepID()), null);
      }

      return success;
  }

  @Override
  public boolean setFixedAttributes(int attributes) throws IOException, AS400SecurityException
  {
      // Assume the argument has been validated by the public class.

      // Ensure that we are connected to the server.
      fd_.connect();

      // Issue a change attributes request.
      ClientAccessDataStream ds = null;
      try
      {
          // Convert the path name to the server CCSID.
          byte[] pathname = fd_.converter_.stringToByteArray(fd_.path_);

          IFSChangeAttrsReq req = new IFSChangeAttrsReq(pathname, fd_.preferredServerCCSID_, attributes, true,
                  fd_.serverDatastreamLevel_);
          ds = (ClientAccessDataStream) fd_.server_.sendAndReceive(req);
      }
      catch (ConnectionDroppedException e) {
          Trace.log(Trace.ERROR, "Byte stream server connection lost.");
          fd_.connectionDropped(e);
      }
      catch (InterruptedException e) {
          Trace.log(Trace.ERROR, "Interrupted");
          InterruptedIOException throwException = new InterruptedIOException(e.getMessage());
          throwException.initCause(e);
          throw throwException;
      }

      // Verify the reply.
      boolean success = false;
      if (ds instanceof IFSReturnCodeRep)
      {
          int rc = ((IFSReturnCodeRep) ds).getReturnCode();
          if (rc == IFSReturnCodeRep.SUCCESS)
              success = true;
          else if (Trace.traceOn_)
              Trace.log(Trace.ERROR, "Error setting file attributes: IFSReturnCodeRep return code", rc);
      } 
      else
      {
          Trace.log(Trace.ERROR, "Unknown reply data stream", ds.data_);
          throw new InternalErrorException(InternalErrorException.DATA_STREAM_UNKNOWN, Integer.toHexString(ds.getReqRepID()), null);
      }

      // Clear any cached attributes.
      attributesReply_ = null;

      return success;
  }

  @Override
  public boolean setHidden(boolean attribute) throws IOException, AS400SecurityException
  {
      // Assume the argument has been validated by the public class.

      // Ensure that we are connected to the server.
      fd_.connect();

      boolean success = false;

      // Setting the fixed attributes of a file involves
      // replacing the current set of attributes. The first
      // set is to get the current set.
      IFSListAttrsRep attributes = getAttributeSetFromServer(fd_.path_);

      if (attributes != null)
      {
          // Now that we have the current set of attributes, figure
          // out if the bit is currently on.
          int currentFixedAttributes = attributes.getFixedAttributes();
          boolean currentHiddenBit = (currentFixedAttributes & 2) != 0;

          // If current does not match what the user wants we need to go
          // to the server to fix it.
          if (currentHiddenBit != attribute)
          {
              int newAttributes;

              // If the user wants hidden on set the bit else the
              // user wants the bit off so clear it.
              if (attribute)
                  newAttributes = currentFixedAttributes + 2;
              else
                  newAttributes = currentFixedAttributes & 0x7ffffffd;

              // Issue a change attributes request.
              ClientAccessDataStream ds = null;
              try
              {
                  // Convert the path name to the server CCSID.
                  byte[] pathname = fd_.converter_.stringToByteArray(fd_.path_);

                  IFSChangeAttrsReq req = new IFSChangeAttrsReq(pathname, fd_.preferredServerCCSID_, newAttributes, true, fd_.serverDatastreamLevel_);
                  ds = (ClientAccessDataStream) fd_.server_.sendAndReceive(req);
              } 
              catch (ConnectionDroppedException e) {
                  Trace.log(Trace.ERROR, "Byte stream server connection lost.");
                  fd_.connectionDropped(e);
              }
              catch (InterruptedException e) {
                  Trace.log(Trace.ERROR, "Interrupted");
                  InterruptedIOException throwException = new InterruptedIOException(e.getMessage());
                  throwException.initCause(e);
                  throw throwException;
              }

              if (ds instanceof IFSReturnCodeRep)
              {
                  int rc = ((IFSReturnCodeRep) ds).getReturnCode();
                  if (rc == IFSReturnCodeRep.SUCCESS)
                      success = true;
                  else if (Trace.traceOn_)
                      Trace.log(Trace.ERROR, "Error setting hidden attribute: " + "IFSReturnCodeRep return code", rc);
              }
              else
              {
                  Trace.log(Trace.ERROR, "Unknown reply data stream", ds.data_);
                  throw new InternalErrorException(Integer.toHexString(ds.getReqRepID()), InternalErrorException.DATA_STREAM_UNKNOWN);
              }

              // Clear any cached attributes.
              attributesReply_ = null;

          } 
          else
              success = true;
      }
      
      return success;
  }

  @Override
  public boolean setLastModified(long time) throws IOException, AS400SecurityException
  {
      // Assume the argument has been validated by the public class.

      int fileHandle = UNINITIALIZED;
      boolean success = false;

      // Ensure that we are connected to the server.
      try
      {
          fd_.connect();

          if (time == -1)
          {
              // We are setting modification time to "current system time".
              // To do that, we will simply read and write-back the first byte in the file.

              // Open the file for read/write.
              fileHandle = fd_.createFileHandle(IFSOpenReq.WRITE_ACCESS, IFSOpenReq.OPEN_OPTION_FAIL_OPEN);
              if (fileHandle == UNINITIALIZED)
                  return false;
              else
                  fd_.setOpen(true, fileHandle); // inform the descriptor of the file handle

              byte[] buffer = new byte[1]; // buffer for reading/writing a single byte

              // If we're setting timestamp to "current system time", we'll need to know how big the file is.
              boolean fileIsEmpty = false; // @B8a
              if (time == -1)
                  fileIsEmpty = (length() == 0 ? true : false); // @B8a

              if (fileIsEmpty)
              {
                  // Update last-modification date by writing one byte (the value doesn't matter).
                  fd_.writeBytes(buffer, 0, 1, true);

                  // Reset the file size to zero.
                  success = fd_.setLength(0);
              }
              else // the file is not empty
              {
                  // Read the first byte.
                  if (1 == fd_.read(buffer, 0, 1))
                  {
                      // Write back the first byte.
                      fd_.setFileOffset(0);
                      fd_.writeBytes(buffer, 0, 1, true);
                      success = true;
                  }
                  else
                  {
                      if (Trace.traceOn_) Trace.log(Trace.ERROR, "Unable to read first byte of file.");
                      success = false;
                  }
              }
          }
          else // the caller specified a last-modified time
          {
              // Issue a change attributes request.
              ClientAccessDataStream ds = null;
              try
              {
                  // Convert the path name to the server CCSID.
                  byte[] pathname = fd_.converter_.stringToByteArray(fd_.path_);

                  IFSChangeAttrsReq req = new IFSChangeAttrsReq(pathname, fd_.preferredServerCCSID_, 0, time, 0, fd_.serverDatastreamLevel_);
                  ds = (ClientAccessDataStream) fd_.server_.sendAndReceive(req);
              } 
              catch (ConnectionDroppedException e) {
                  Trace.log(Trace.ERROR, "Byte stream server connection lost.");
                  fd_.connectionDropped(e);
              }
              catch (InterruptedException e) {
                  Trace.log(Trace.ERROR, "Interrupted");
                  InterruptedIOException throwException = new InterruptedIOException(e.getMessage());
                  throwException.initCause(e);
                  throw throwException;
              }

              // Verify the reply.
              if (ds instanceof IFSReturnCodeRep)
              {
                  int rc = ((IFSReturnCodeRep) ds).getReturnCode();
                  if (rc == IFSReturnCodeRep.SUCCESS)
                      success = true;
                  else if (Trace.traceOn_)
                      Trace.log(Trace.ERROR, "Error setting last-modified date: IFSReturnCodeRep return code", rc);
              } 
              else
              {
                  Trace.log(Trace.ERROR, "Unknown reply data stream", ds.data_);
                  throw new InternalErrorException(Integer.toHexString(ds.getReqRepID()), InternalErrorException.DATA_STREAM_UNKNOWN);
              }
          }

          // Clear any cached attributes.
          attributesReply_ = null;
      }
      finally
      {
          if (fileHandle != UNINITIALIZED)
              fd_.close(fileHandle);
      }

      return success;
  }


  @Override
  public boolean setLength(int length) throws IOException, AS400SecurityException
  {
      // Assume the argument has been validated by the public class.

      // Ensure that we are connected to the server.
      fd_.connect();

      // Clear any cached attributes.
      attributesReply_ = null;

      return fd_.setLength(length);
  }

  @Override
  public void setPath(String path)
  {
      // Assume the argument has been validated by the public class.

      // Ensure that the path is not altered after the connection is
      // established.
      if (fd_.server_ != null)
          throw new ExtendedIllegalStateException("path", ExtendedIllegalStateException.PROPERTY_NOT_CHANGED);

      // If the specified path doesn't start with the separator character,
      // add one. All paths are absolute for IFS.
      String newPath;
      if (path.length() == 0 || path.charAt(0) != IFSFile.separatorChar)
          newPath = IFSFile.separator + path;
      else
          newPath = path;

      // Update the path value.
      fd_.path_ = newPath;
  }

  @Override
  public void setPatternMatching(int patternMatching)
  {
      fd_.patternMatching_ = patternMatching;
  }

  @Override
  public boolean setReadOnly(boolean attribute) throws IOException, AS400SecurityException
  {
      // Assume the argument has been validated by the public class.

      // Ensure that we are connected to the server.
      fd_.connect();

      boolean success = false;
      IFSListAttrsRep attributes = getAttributeSetFromServer(fd_.path_);

      // Same as setHidden -- setting fixed attributes is a total replacement
      // of the fixed attributes.  So, we have to get the current set, fix up
      // the readonly bit, then put them back.
      if (attributes != null)
      {
          int currentFixedAttributes = attributes.getFixedAttributes();
          boolean currentReadOnlyBit = (currentFixedAttributes & 1) != 0;

          // If the bit is not currently set to what the user wants.
          if (currentReadOnlyBit != attribute)
          {
              int newAttributes;

              // If the user wants readonly on, add to the current set.
              // If the user wants it off, clear the bit.
              if (attribute)
                  newAttributes = currentFixedAttributes + 1;
              else
                  newAttributes = currentFixedAttributes & 0x7ffffffe;

              // Issue a change attributes request.
              ClientAccessDataStream ds = null;
              try
              {
                  // Convert the path name to the server CCSID.
                  byte[] pathname = fd_.converter_.stringToByteArray(fd_.path_);

                  IFSChangeAttrsReq req = new IFSChangeAttrsReq(pathname, fd_.preferredServerCCSID_,
                                                                newAttributes, true, fd_.serverDatastreamLevel_);
                  ds = (ClientAccessDataStream) fd_.server_.sendAndReceive(req);
              }
              catch(ConnectionDroppedException e)
              {
                  Trace.log(Trace.ERROR, "Byte stream server connection lost.");
                  fd_.connectionDropped(e);
              }
              catch(InterruptedException e)
              {
                  Trace.log(Trace.ERROR, "Interrupted");
                  InterruptedIOException throwException = new InterruptedIOException(e.getMessage());
                  throwException.initCause(e);
                  throw throwException;
              }

              if (ds instanceof IFSReturnCodeRep)
              {
                  int rc = ((IFSReturnCodeRep) ds).getReturnCode();
                  if (rc == IFSReturnCodeRep.SUCCESS)
                      success = true;
                  else if (Trace.traceOn_) 
                      Trace.log(Trace.ERROR, "Error setting read-only attribute: IFSReturnCodeRep return code", rc);
              }
              else
              {
                  Trace.log(Trace.ERROR, "Unknown reply data stream", ds.data_);
                  throw new InternalErrorException(Integer.toHexString(ds.getReqRepID()), InternalErrorException.DATA_STREAM_UNKNOWN);
              }

              // Clear any cached attributes.
              attributesReply_ = null;
          }
          else
              success = true;
      }
      
      return success;
  }

  @Override
  public void setSorted(boolean sort)
  {
      sortLists_ = sort;
  }

  @Override
  public void setSystem(AS400Impl system)
  {
    // Assume the argument has been validated by the public class.

    fd_.system_ = (AS400ImplRemote)system;
  }

  // Setup remote service program object on first touch.  Synchronized to protect instance variables.  
  // This method can safely be called multiple times because it checks for a previous call before changing the instance variables.
  protected synchronized void setupRemoteCommand() throws IOException
  {
      if (rmtCmd_ != null) 
          return;
      
      if (fd_.system_.canUseNativeOptimizations())
      {
          try {
              // Avoid direct reference - it can cause NoClassDefFoundError at class loading time on Sun JVM's.
              rmtCmd_ = (RemoteCommandImpl) Class.forName("com.ibm.as400.access.RemoteCommandImplNative").newInstance();
          } 
          catch (Throwable e) {
              Trace.log(Trace.WARNING, "Unable to instantiate class RemoteCommandImplNative.", e);
          }
      }
      
      // Use remote implementation if not set.
      if (rmtCmd_ == null)
          rmtCmd_ = new RemoteCommandImplRemote();

      rmtCmd_.setSystem(fd_.system_);
  }

  @Override
  public String getDescription() throws IOException, AS400SecurityException
  {
      // We must use doQp0lGetAttr to get the description in order to get 
      // the description in CCSID of the job for QSYS.LIB objects, and not 
      // in the CCSID of the file server.  That is how QSYS.LIB object 
      // gives back the description. So if you have NLS characters in 
      // the description, it will be converted to the file server job, which 
      // is not what we want. 
      String descr = (String)doQp0lGetAttr(QP0L_ATTR_TEXT);

      return (descr != null) ? descr : "";
  }
  
  /*
   * INTERNAL USE ONLY
   * Checks to see if names in path contain forward slash, which is problematic when 
   * interacting with file server. Quoted names for QSYS.LIB objects can have a forward slash.
   */
  static boolean isPathProblematic(QSYSObjectPathName p) 
  {
      return (   p.getLibraryName().indexOf('/') != -1 
              || p.getObjectName().indexOf('/')  != -1
              || p.getMemberName().indexOf('/')  != -1);
  }
  
  static String trimRight(String s) 
  {
      if (s == null || s.length() == 0)
          return "";
      
      int endIndex = 0;
      int len = s.length();
      for (endIndex=len -1; endIndex > -1; endIndex--) 
      {
          char c = s.charAt(endIndex);
          if (c != ' ')
              break;
      }
      return s.substring(0, endIndex +1);
  }
  
  /*
   * IFS has problems with getting description which is DBCS from QSYS objects.
   */
  
  private static final byte              ebcdicDelim   = 97;
  private static final byte              nullDelim     = 0;
  private static final byte[]            hexZeros      = new byte[50];
  private static final AS400Bin4         intConverter  = new AS400Bin4();
  private static final AS400UnsignedBin4 uintConverter = new AS400UnsignedBin4();
  private static final AS400Bin8         longConverter = new AS400Bin8();

  private static final int QP0L_ATTR_ASP          = 13;
  private static final int QP0L_ATTR_DATA_SIZE_64 = 14;
  private static final int QP0L_ATTR_CCSID        = 27;
  private static final int QP0L_ATTR_TEXT         = 48;
  
  private Object doQp0lGetAttr(int attr) throws IOException, AS400SecurityException
  {    
      // Create the pgm call object
      if (rmtCmd_ == null)
          setupRemoteCommand();
      
      Object result = null;

      try 
      {
          ByteArrayOutputStream bo =  new ByteArrayOutputStream();
    
          ConvTable conv = ConvTable.getTable(fd_.system_.getCcsid(), null);
          
          ProgramParameter[] parms = new ProgramParameter[7];
          
          // =======
          // PARAMETER #1 ===== Path name format
          
          // QSYS.LIB paths may contain quoted object names that has '/' character in the name, 
          // such as '/qsys.lib/"new/lib".lib'.  When using IFS APIs, we need to replace delimiter
          // with something else, which in this case is hex 00. 
          byte[] pathBytes = null;
          byte delim       = nullDelim;
    //
    //      if (sysObject.isQSYSObject() && isPathProblematic(sysObject.getQSYSPath()))
    //      {
    //          QSYSObjectPathName qsysPath  = sysObject.getQSYSPath();
    //          
    //          String asp = qsysPath.getAspName();
    //          String lib = qsysPath.getLibraryName();
    //          String obj = qsysPath.getObjectName();
    //          String mbr = qsysPath.getMemberName();
    //          
    //          // Use hex 00 as delimiter
    //          if (!asp.isEmpty())
    //          {
    //              bo.write(delim);
    //              if (asp.charAt(0) == '/')
    //                  bo.write(conv.stringToByteArray(asp.substring(1)));
    //              else
    //                  bo.write(conv.stringToByteArray(asp));
    //          }
    //          
    //          if (!lib.isEmpty()) 
    //          {
    //              bo.write(delim);
    //              bo.write(conv.stringToByteArray(lib + ".LIB"));
    //          }
    //
    //          if (!obj.isEmpty())
    //          {
    //              bo.write(delim);
    //              bo.write(conv.stringToByteArray(obj + "." + qsysPath.getObjectType()));
    //          }
    //
    //          if (!mbr.isEmpty())
    //          {
    //              bo.write(delim);
    //              bo.write(conv.stringToByteArray(mbr + ".MBR"));
    //          }
    //          
    //          pathBytes = bo.toByteArray();
    //      }
    //      else 
          {
              delim     = ebcdicDelim;
              pathBytes = conv.stringToByteArray(fd_.path_);
          }
                
          bo.reset();
    
          bo.write(hexZeros, 0, 16);
          bo.write(BinaryConverter.intToByteArray((int)pathBytes.length)); // length of path
          bo.write(delim);   // path delimiter character
          bo.write(hexZeros, 0, 11);
          bo.write(pathBytes);   // path 
          
          parms[0] = new ProgramParameter(ProgramParameter.PASS_BY_REFERENCE, bo.toByteArray());
          
          // =======
          // PARAMETER #2 ===== attribute array
          int numAttrs = 1;
          bo.reset();
          bo.write(BinaryConverter.intToByteArray(numAttrs));     // Number of attributes to return
          bo.write(BinaryConverter.intToByteArray(attr));    
    
          parms[1] = new ProgramParameter(ProgramParameter.PASS_BY_REFERENCE, bo.toByteArray());
    
          // =======
          // PARAMETER #3 ===== output buffer
          parms[2] = new ProgramParameter(ProgramParameter.PASS_BY_REFERENCE, 200);
    
          // =======
          // PARAMETER #4 ===== output buffer size
          parms[3] = new ProgramParameter(ProgramParameter.PASS_BY_VALUE, BinaryConverter.intToByteArray((int)200));
    
          // =======
          // PARAMETER #5 ===== output buffer size needed
          parms[4] = new ProgramParameter(ProgramParameter.PASS_BY_REFERENCE, 4);
          
          // =======
          // PARAMETER #6 ===== number of bytes returned
          parms[5] = new ProgramParameter(ProgramParameter.PASS_BY_REFERENCE, 4);
    
          // =======
          // PARAMETER #7 ===== follow symbolic link
          parms[6]  = new ProgramParameter(ProgramParameter.PASS_BY_VALUE, BinaryConverter.intToByteArray((int)1));
    
          // Call the service program.
          byte[] returnedBytes = rmtCmd_.runServiceProgram("QSYS", "QP0LLIB2", "Qp0lGetAttr", parms);
          if (returnedBytes == null)
          {
              Trace.log(Trace.ERROR, "Call to Qp0lGetAttr() returned null.");
              throw new AS400Exception(rmtCmd_.getMessageList());
          }

          int returnValue = BinaryConverter.byteArrayToInt(returnedBytes, 0);
          if (returnValue != 0)
          {
              int errno = BinaryConverter.byteArrayToInt(returnedBytes, 4);
              Trace.log(Trace.ERROR, "Qp0lGetAttr errno=: " + errno);
              return null;
          }
          
          /** 
           * Output buffer format
           * 0   0   BINARY(4)   Offset to next attribute entry
           * 4   4   BINARY(4)   Attribute identification
           * 8   8   BINARY(4)   Size of attribute data
           * 12  C   CHAR(4)     Reserved
           * 16  10  CHAR(*)     Attribute data
           */
    
          byte[] outBuf = parms[2].getOutputData();
    
          int offset=0;
          long nextOffset=-1;
          while (nextOffset != 0)
          {
              nextOffset        = uintConverter.toLong(outBuf, offset+0);
              long attrID       = uintConverter.toLong(outBuf, offset+4);
              long attrDataSize = uintConverter.toLong(outBuf, offset+8);
              
              if (attrDataSize > 0)
              {
                  if (attrID == QP0L_ATTR_DATA_SIZE_64)
                      result = longConverter.toObject(outBuf, offset+16); // long long
                  else if (attrID == QP0L_ATTR_CCSID)
                      result  = intConverter.toObject(outBuf, offset+16); 
                  else if (attrID == QP0L_ATTR_ASP)
                      result  = intConverter.toObject(outBuf, offset+16);
                  else if (attrID == QP0L_ATTR_TEXT) 
                  {
                      String description = conv.byteArrayToString(outBuf, offset+16, (int)attrDataSize); // max of 50
                      
                      // Cobol sets text field to hex zeros....sigh
                      description = description.replace('\0', ' ');
                      description = trimRight(description);
                      
                      result = description;
                  }
              }
              
              if (result != null)
                  break;
              
              offset = (int)nextOffset;
          }
      }
      catch (IOException | AS400SecurityException e) {
          throw e;
      }
      catch (Exception e) {
          if (Trace.traceOn_) Trace.log(Trace.DIAGNOSTIC, "Qp0lGetAttr exception ", e);
      }
      
      return result;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy