#!/usr/bin/env ruby.ruby3.4

# Copyright 2025 - 2026 Open Text
#
# ------------------------------------------------------------------------------
# The only warranties for products and services of Open Text and its
# affiliates and licensors (“Open Text”) are as may be set forth in the express
# warranty statements accompanying such products and services.  Nothing herein
# should be construed as constituting an additional warranty. Open Text shall not
# be liable for technical or editorial errors or omissions contained herein. The
# information contained herein is subject to change without notice.
#
# Except as specifically indicated otherwise, this document contains
# confidential information and a valid license is required for possession, use or
# copying. If this work is provided to the U.S. Government, consistent with FAR
# 12.211 and 12.212, Commercial Computer Software, Computer Software
# Documentation, and Technical Data for Commercial Items are licensed to the U.S.
# Government under vendor's standard commercial license.
# ------------------------------------------------------------------------------

# (C) Copyright 1995 - 2022 Micro Focus or one of its affiliates.

# The only warranties for products and services of Micro Focus and its affiliates and licensors
# (“Micro Focus”) are as may be set forth in the express warranty statements accompanying such
# products and services. Nothing herein should be construed as constituting an additional
# warranty. Micro Focus shall not be liable for technical or editorial errors or omissions contained
# herein. The information contained herein is subject to change without notice.

# Except as specifically indicated otherwise, this document contains confidential information
# and a valid license is required for possession, use or copying. If this work is provided to the
# U.S. Government, consistent with FAR 12.211 and 12.212, Commercial Computer Software,
# Computer Software Documentation, and Technical Data for Commercial Items  are licensed
# to the U.S. Government under vendor's standard commercial license.

# Copyright (C) [2007-2009] Novell, Inc.  All Rights Reserved.

# THIS WORK IS SUBJECT TO U.S. AND INTERNATIONAL COPYRIGHT LAWS AND TREATIES.
# IT MAY NOT BE USED, COPIED, DISTRIBUTED, DISCLOSED, ADAPTED, PERFORMED,
# DISPLAYED, COLLECTED, COMPILED, OR LINKED WITHOUT NOVELL'S PRIOR WRITTEN
# CONSENT.  USE OR EXPLOITATION OF THIS WORK WITHOUT AUTHORIZATION COULD
# SUBJECT THE PERPETRATOR TO CRIMINAL AND CIVIL LIABILITY.

# NOVELL PROVIDES THE WORK "AS IS," WITHOUT ANY EXPRESS OR IMPLIED WARRANTY,
# INCLUDING WITHOUT THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
# PARTICULAR PURPOSE, AND NON-INFRINGEMENT. NOVELL, THE AUTHORS OF THE WORK,
# AND THE OWNERS OF COPYRIGHT IN THE WORK ARE NOT LIABLE FOR ANY CLAIM, DAMAGES,
# OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE,
# ARISING FROM, OUT OF, OR IN CONNECTION WITH THE WORK OR THE USE OR OTHER
# DEALINGS IN THE WORK.
require 'rubygems'
require 'mig'
require 'etc'
require 'expect'
require 'logger'
require 'open3'
require 'fileutils'
require 'date'

$:.unshift File.expand_path(File.join(File.dirname(__FILE__)))
require 'migration/safe_pty'
$:.shift

include Migration
NBACKUP = "LC_ALL=en_US.UTF-8 /opt/novell/sms/bin/nbackup"
SMSTOOL = "LC_ALL=en_US.UTF-8 /opt/novell/sms/bin/smstool"
SOURCE_PATH_FILE = "/tmp/sourcePathListFile.#{Process.pid}"
SOURCE_DEST_PATH_FILE = "/tmp/sourceDestPathFile.#{Process.pid}"
SOURCE_DEST_PATH_FILE_FOR_SMSTOOL = "/tmp/sourceDestPathFileForSMSTOOL.#{Process.pid}"
SMSTOOL_OUT_FILE = "/tmp/smstoolOutFile.#{Process.pid}"
SMSTOOL_META_OUT_FILE = "/tmp/smstoolMetaOutFile.#{Process.pid}"
ROOT_USER = "root"

def disableQuotaChecking
  # Use nsscmd command NOQUOTACHECKING to disanle quotas on server
  `echo /NOQUOTACHECKING > /dev/nsscmd`
end

def enableQuotaChecking
  # Use nsscmd command NOQUOTACHECKING to disanle quotas on server
  `echo /QUOTACHECKING > /dev/nsscmd`
end

# This function writes the status of the migfiles to the given session file
def writeSessionFile
  $lastChkPt.gsub!(":/", ":")
  File.open(@opt['c'], File::CREAT|File::WRONLY|File::TRUNC, 0600) { |fd|
# Progress changes
    fd.write($session + "  status: stopped\n  stopped-at: \"#{$lastChkPt}\"\n  Bytes Processed: #{@bytesCopiedAtLastChkPt}\n  File Count in Dir: #{@dirFileCntChkPt}\n  File Tot Count in Dir: #{@dirTotFileCntChkPt}\n  Total DataSet Copied: #{@totDataSetsCopiedChkPt}\n  Pair Processing: #{@pairChkPt}\n")
  }
end

def verifyAndFixWinSrcPath
  begin
    samba = Migration::Windows::Samba.new( @opt['s'], @opt['V'], @opt['use-casa'] )
  rescue Migration::Windows::SambaError => discription
    print_message(FATAL, "--source-path #{@opt['V']} doesn't exist on the #{@opt['s']} server or invalid credentials")
    exit 1
  end
  return @opt['V']
end

def validateArgs
  if @opt['v'] and @opt['x']
    print_message(FATAL, "Cannot specify --destination-path(-v) and --destination-full-path(-x) options together")
    printUsage($argdef)
    exit 1
  end
  if not @opt['v'] and not @opt['x']
    print_message(FATAL, "Need either of --destination-path(-v) or --destination-full-path(-x) option")
    printUsage($argdef)
    exit 1
  end
  if @opt['p'] and not @opt['x']
    print_message(FATAL, "Need --destination-full-path(-x) with --posix(-p) option")
    printUsage($argdef)
    exit 1
  end
  if not @opt['progress'] and @opt['progress-interval']
    print_message(FATAL, "--progress-interval needs --progress switch")
    printUsage($argdef)
    exit 1
  end
  if @opt['W'] and @opt['p']
    print_message(FATAL, "Migrating files from Windows to POSIX is not supported")
    printUsage($argdef)
    exit 1
  end
  if @opt['P'] and not @opt['X']
    print_message(FATAL, "Need to specify --source-full-path(-X) along with --source-posix(-P) option")
    printUsage($argdef)
    exit 1
  end
  if not @opt['V'] and not @opt['X']
    print_message(FATAL, "Need either of --source-path(-V) or --source-full-path(-X) option")
    printUsage($argdef)
    exit 1
  end
  if @opt['V'] and @opt['X']
    print_message(FATAL, "Cannot specify --source-path(-V) and --source-full-path(-X) options together")
    printUsage($argdef)
    exit 1
  end
  if @opt['delete'] and not @opt['sync']
    # --delete option is given but --sync option is not given. In this case just throw error.
    # Presently smstool will do the full sync of source path and destination path, that means it brings source only files (to be copied)
    # and displays destination only files (to be deleted)
    print_message(FATAL, "Cannot specify --delete option without specifying --sync option")
    printUsage($argdef)
    exit 1
  end
#  if @opt['delete-existing-trustees'] and not @opt['trustees-only']
#    print_message(FATAL, "Cannot specify --delete-existing-trustees option without specifying --trustees-only option")
#    printUsage($argdef)
#    exit 1
#  end
#  if @opt['delete-file-on-restore-error'] and @opt['trustees-only']
#    print_message(FATAL, "Cannot specify --delete-file-on-restore-error option with --trustees-only option")
#    printUsage($argdef)
#    exit 1
#  end
  if @opt['trustees-only'] and @opt['sync']
    print_message(FATAL, "Cannot specify --trustees-only option along with --sync")
    printUsage($argdef)
    exit 1
  end

  if @opt['trustees-only'] and @opt['P']
    print_message(FATAL, "Cannot specify --trustees-only option along with --source-posix")
    printUsage($argdef)
    exit 1
  end
  if @opt['fs-data-dist'] and ((not @opt['session-file']) and (not @opt['c']))
    print_message(FATAL, "Need --session-file(-c) with --fs-data-dist option")
    printUsage($argdef)
    exit 1
  end
  if @opt['fs-data-dist'] and (@opt['trustees-only'] or @opt['sync'])
    print_message(FATAL, "Cannot specify --fs-data-dist option with --trustees-only or --sync option")
    printUsage($argdef)
    exit 1
  end
end

def deleteTempFiles
  File.unlink(SOURCE_PATH_FILE) if File.exist?(SOURCE_PATH_FILE)
  File.unlink(SOURCE_DEST_PATH_FILE) if File.exist?(SOURCE_DEST_PATH_FILE)
  File.unlink(SOURCE_DEST_PATH_FILE_FOR_SMSTOOL) if File.exist?(SOURCE_DEST_PATH_FILE_FOR_SMSTOOL)
  File.unlink(SMSTOOL_OUT_FILE) if File.exist?(SMSTOOL_OUT_FILE)
  File.unlink("#{SMSTOOL_OUT_FILE}.delete") if File.exist?("#{SMSTOOL_OUT_FILE}.delete")
  File.unlink(SMSTOOL_META_OUT_FILE) if File.exist?(SMSTOOL_META_OUT_FILE)
  File.unlink("#{SMSTOOL_META_OUT_FILE}.delete") if File.exist?("#{SMSTOOL_META_OUT_FILE}.delete")
end

# This function is wrapper to docmd function. It is mainly seperated beacuse this part of code
# handles failover case and this is called from several places
def callNbackup cmd, srcPaths, destPaths
  print_message(DEBUG, "nbackup cmd = #{cmd}")
  $complete = false
  @isFailover = false
  @retryCnt = 1
  print_message(DEBUG, "Calling function doCmd")
  $nbackup_command = cmd
  # To support NCP as source, nbackup should execute with the root credentials. So setting the ENV variable to 
  # root user password is either source or target is NCP. This is to provide fix for bug #824782
  tempRemotePwd = ENV["SMS_REMOTE_PASSWORD"]
  if $isSrcNCP
    ENV['SMS_REMOTE_PASSWORD'] = $remoteRootPwd
  end
  doCmd(cmd, srcPaths, destPaths)
  # Setting the back the SMS_REMOTE_PASSWORD to the original password it had. This is to provide fix for bug #824782
  ENV['SMS_REMOTE_PASSWORD'] = tempRemotePwd
end

def setFlagsForSmstool credentialDetails, excludePath, srcPaths, destPaths
  if $isSrcLinux
    # To support NCP as source, nbackup should execute with the root credentials. So setting the user to 
    # root if either source or target is NCP. This is to provide fix for bug #824782
    if $isSrcNCP
      flags = " --srcuser \"#{ROOT_USER}\""
    else
      flags = " --srcuser \"#{credentialDetails['source-username']}\""
    end
  else
    flags = " --srcuser \".#{credentialDetails['source-username']}\""
  end
  flags += " --destuser \"#{credentialDetails['dest-username']}\" -R #{@opt['s']}"
  # set date filters
  if @opt['u']
    flags += " --modified-after \"#{@opt['u']}\""
  end

  flags += " --modified-before \"#{@opt['modified-before']}\"" if @opt['modified-before']
  flags += " --accessed-after \"#{@opt['accessed-after']}\"" if @opt['accessed-after']
  flags += " --accessed-before \"#{@opt['accessed-before']}\"" if @opt['accessed-before']
  flags += " --sync-time \"#{@opt['sync']}\"" if @opt['sync']

  flags += " --print-deleted-paths" if @opt['delete']
  flags += " --output-file #{SMSTOOL_OUT_FILE}"
  flags += " --metaoutput-file #{SMSTOOL_META_OUT_FILE}"
  flags += " --trustees-dirs-only" if @opt['trustees-dirs-only']
  if @multiplePathsSpecified
    flags += " --finddiff \"@#{SOURCE_DEST_PATH_FILE_FOR_SMSTOOL}\""
  else
    shadowFSDestPath = getShadowFSPath(destPaths[0])
    if shadowFSDestPath == nil
      flags += " --finddiff \"#{srcPaths[0]} #{destPaths[0]}\""
    else
      # The ShadowFS path is passed to smstool. So that missed file comparison is done
      # against the shadowfs Path
      print_message(DEBUG, "shadowFSDestPath = #{shadowFSDestPath} for destPath = #{destPaths[0]}")
      flags += " --finddiff \"#{srcPaths[0]} #{shadowFSDestPath}\""
    end
  end

  if @opt['e']
    if @opt['e'].class == Array
      @opt['e'].each { |i| flags += " --exclude-file=\"#{i}\"" }
    else
      flags += " --exclude-file=\"#{@opt['e']}\""
    end
  end

  if excludePath != nil and excludePath != ""
    if excludePath.class == Array
      excludePath.each { |i| flags += " --exclude-path=\"#{i}\"" }
    else
      flags += " --exclude-path \"#{excludePath}\""
    end
  end

  flags += " --continue-after-failover" if @opt['continue-after-failover']

  flags += " --usecodeset \"#{@opt['usecodeset']}\"" if @opt['usecodeset']

  return flags
end

def getDestIndexForSrcFile srcFileName, srcPaths
 srcPaths.each_index do |index|
   if srcFileName.index(srcPaths[index]) == 0
     return index
   end
 end
 return -1
end

# This is how we do it.
# For each path in output file, we call function get associated destpath index, once we get index we
# we can figure out whether this dest path is compression enabled or not, If it is not we will prepend
# --uncompress flag to the file name and write it to tne new temp file. This temp file will be renamed
# SMSTOOL_OUT_FILE once we done the processing.
def modifyOutputFile srcPaths, destPaths, changeAll
  tempFile = "/tmp/srcPaths.#{Process.pid}"
  tempFp = File.open(tempFile, File::CREAT|File::WRONLY|File::TRUNC, 0600)
  File.readlines(SMSTOOL_OUT_FILE).each do |srcFileName|
    if not changeAll
      destIndex = getDestIndexForSrcFile(srcFileName, srcPaths)
      if destIndex == -1
        print_message(FATAL, "Failed to modify the output file of smstool")
        exit 1
      end
      if @compressInfo[destPaths[destIndex]] == true
        # This destpath is compression enabled, so dont have to chage write as it is
        tempFp.puts(srcFileName)
      else
        tempFp.puts("--uncompress"+"\t"+srcFileName)
      end
    else
       # No need to process, just read lines from one file and write to other with --uncompress at the beginning
       tempFp.puts("--uncompress"+"\t"+srcFileName)
    end
  end
  tempFp.close()
  File.rename(tempFile, SMSTOOL_OUT_FILE)
end

def modifyMetaOutputFile srcPaths, destPaths, changeAll
  tempFile = "/tmp/srcMetaPaths.#{Process.pid}"
  tempFp = File.open(tempFile, File::CREAT|File::WRONLY|File::TRUNC, 0600)
  File.readlines(SMSTOOL_META_OUT_FILE).each do |srcFileName|
    if not changeAll
      destIndex = getDestIndexForSrcFile(srcFileName, srcPaths)
      if destIndex == -1
        print_message(FATAL, "Failed to modify the output file of smstool")
        exit 1
      end
      if @compressInfo[destPaths[destIndex]] == true
        # This destpath is compression enabled, so dont have to chage write as it is
        tempFp.puts(srcFileName)
      else
        tempFp.puts("--uncompress"+"\t"+srcFileName)
      end
    else
       # No need to process, just read lines from one file and write to other with --uncompress at the beginning
       tempFp.puts("--uncompress"+"\t"+srcFileName)
    end
  end
  tempFp.close()
  File.rename(tempFile, SMSTOOL_META_OUT_FILE)
end

def listExtensions credentialDetails, paths, isSrc

  if (isSrc == 1)
     flags = " -R #{@opt['s']} -U \"#{credentialDetails['source-username']}\" --list-extensions \"#{paths}\" "
# For list-extenstions option smstool accepts one passwd, hence setting both source and dest env variable to same pwd
     ENV['USER_PASSWORD_REMOTE'] = credentialDetails['source-password']
     ENV['USER_PASSWORD_LOCAL'] = credentialDetails['source-password']
  else
     flags = " -U \"#{credentialDetails['dest-username']}\" --list-extensions \"#{paths}\" "
# For list-extenstions option smstool accepts one passwd, hence setting both source and dest env variable to same pwd
     ENV['USER_PASSWORD_REMOTE'] = credentialDetails['dest-password']
     ENV['USER_PASSWORD_LOCAL'] = credentialDetails['dest-password']
  end
  cmd = "#{SMSTOOL} #{flags} 2>&1"
  print_message(DEBUG, "smstool cmd = #{cmd}\n")

  # smstool lists the extenssions supported by given source/Target

  smstoolSuccess = false
  io_reader = ""
  begin
    Open3.popen3(cmd) do | input, output, err |
      while 1
        io_reader = output.gets
        if(io_reader == nil)
          break
        end
        io_reader.strip!
        if io_reader.include?("Connection denied")
          raise MigrationException.new("smstool failed to execute, #{io_reader}")
        elsif io_reader.include?("smstool: server's codepage is missing, use 'usecodeset' option")
          print_message(FATAL, "Source server is NetWare 6.0. Load latest smsut.nlm and tsafs.nlm on source server")
        elsif io_reader.include?("Generating dataset list")
          print_message(INFO, "Generating list of files and folders to be synced")
          smstoolSuccess = true
        elsif io_reader.include?("Resource failover/failback") or io_reader.include?("reconnecting")
          if @opt['continue-after-failover']
            print_message(ERROR, "Resource failover happened. Reconnecting...")
            sleep 30
          else
            print_message(FATAL, "Cluster resource appears to have migrated. Cannot continue as option --continue-after-failover is not specified")
            #as the continue-after-failover option is not passed to smstool it will come out and hence no killing!
            return
          end
	    elsif io_reader.include?("Scanning") or io_reader.include?("Starting scan on")
		print_message(PROGRESS, "#{io_reader}")
		elsif io_reader.include?("NWSM_RESOURCE_INFO_EXTN")
			print_message(INFO, "#{io_reader}")
#			fpForExt.puts "#{io_reader}"

        elsif io_reader != ""
          print_message(ERROR, "#{io_reader}")
        end
      end #while
    end #Open3
  rescue Errno::EIO
  end

#  if not smstoolSuccess
#    raise MigrationException.new("smstool command failed to execute: #{io_reader}")
#  end
end


# This function checks if there are any files missed during migration.
# Files can be missed because few reasons like:
# Few files were opened during migration
# This function also deletes files which are not present on source but present on destination.
def syncfiles credentialDetails, srcPaths, destPaths, srcFlags, destFlags, excludePath

  flags = setFlagsForSmstool(credentialDetails, excludePath, srcPaths, destPaths)
  cmd = "#{SMSTOOL} #{flags} 2>&1"
  ENV['USER_PASSWORD_LOCAL'] = credentialDetails['dest-password']
  # To support NCP as source, smstool should execute with the root credentials. So setting the ENV variable to 
  # root user password is either source or target is NCP. This is to provide fix for bug #824782
  if $isSrcNCP
    ENV['USER_PASSWORD_REMOTE'] = $remoteRootPwd
  else
    ENV['USER_PASSWORD_REMOTE'] = credentialDetails['source-password']
  end
  print_message(INFO, "smstool cmd = #{cmd}\n")

  # smstool creates two files, one which has list of files to be copied from source
  # and the other one will have list of files to be deleted from destination

  smstoolSuccess = false
  io_reader = ""
  begin
    Open3.popen3(cmd) do | input, output, err |
      while 1
        io_reader = output.gets
        if(io_reader == nil)
          break
        end
        io_reader.strip!
        if io_reader.include?("Connection denied")
          raise MigrationException.new("smstool failed to execute, #{io_reader}")
        elsif io_reader.include?("smstool: server's codepage is missing, use 'usecodeset' option")
          print_message(FATAL, "Source server is NetWare 6.0. Load latest smsut.nlm and tsafs.nlm on source server")
        elsif io_reader.include?("Generating dataset list")
          print_message(INFO, "Generating list of files and folders to be synced")
          smstoolSuccess = true
        elsif io_reader.include?("Resource failover/failback") or io_reader.include?("reconnecting")
          if @opt['continue-after-failover']
            print_message(ERROR, "Resource failover happened. Reconnecting...")
            sleep 30
          else
            print_message(FATAL, "Cluster resource appears to have migrated. Cannot continue as option --continue-after-failover is not specified")
            #as the continue-after-failover option is not passed to smstool it will come out and hence no killing!
            return
          end
	elsif io_reader.include?("Scanning") or io_reader.include?("Starting scan on")
		print_message(PROGRESS, "#{io_reader}")
        elsif io_reader != ""
          print_message(ERROR, "#{io_reader}")
        end
      end #while
    end #Open3
  rescue Errno::EIO
  end

  if not smstoolSuccess
    raise MigrationException.new("smstool command failed to execute: #{io_reader}")
  end

  # Delete files from destination server which does not exist on source server.
  if @opt['delete']
    if File.exist?(SMSTOOL_OUT_FILE+".delete") and File.size(SMSTOOL_OUT_FILE+".delete") > 0
      File.readlines(SMSTOOL_OUT_FILE+".delete").each do |singleFile|
        if singleFile != nil and singleFile != "" and singleFile != "/"
          singleFile.strip!
          print_message(INFO, "Deleting #{singleFile}")
          deleteFlag = false
          begin
            origFile = String.new(singleFile)
            # We will be changing(Doing few global replacements) singleFile below for attrib command to work.
            # Whereas, File.delete and FileUtils.remove_dir requires the file names as it is (without change).
            # This is the reason we used new string origFile above.
            if not File.directory?(singleFile)
              File.delete(singleFile)
              deleteFlag = true
            else
              FileUtils.remove_dir(singleFile)
              deleteFlag = true
            end
          rescue
            # Failed to delete the file/directory. May be DELETE INHIBIT attribute is set.
            # If so clear this bit and delete the object
            if singleFile.include?("'")
              singleFile.gsub!("'", "\\\\'")
            end
            if singleFile.include?("`")
              singleFile.gsub!("`", "\\\\`")
            end
            if singleFile.include?("\"")
              singleFile.gsub!("\"", "\\\"")
            end
            # Not referring to the attrib command's full path was giving some issues. Corrected the same. This is the fix for the bug#837183.
            `/opt/novell/nss/sbin/attrib -c di -r \"#{singleFile}\" > /dev/null 2>&1`
            # Just ignore the attrib command's error messages
            # Now try to delete the file
            begin
              if not File.directory?(origFile)
                File.delete(origFile)
                deleteFlag = true
              else
                FileUtils.remove_dir(origFile)
                deleteFlag = true
              end
            rescue
              print_message(WARN, "Failed to delete file #{singleFile}, #{$!.message}")
              deleteFlag = false
            end #begin
          end # begin
          if deleteFlag
            print_message(PROGRESS, "Deleting #{singleFile}")
          end
        end
      end
    else
      print_message(DEBUG, "smstool's delete file list is empty. There are no datasets to delete.")
      print_message(INFO, "No datasets to be deleted")
    end
  end

# Now copy files from source to destination server using nbackup.
  if File.exist?(SMSTOOL_OUT_FILE) and File.size(SMSTOOL_OUT_FILE) > 0
    if @multiplePathsSpecified
      destFlags += " -r @#{SOURCE_DEST_PATH_FILE}"
    else
      destFlags += " -r \"#{srcPaths[0]} #{destPaths[0]}\""
    end
    # To take care of compressoion/not-compression related stuff we may have to change the output file of smstool.
    changeAll = false
    if @compress and @uncompress
      modifyOutputFile(srcPaths, destPaths, changeAll)
    elsif @uncompress
      changeAll = true
      modifyOutputFile(srcPaths, destPaths, changeAll)
    #else
      # No need to change the output
    end

    # --progress and --progress-interval options should be passed to nbackup only during data copy. During trustee sync, these options should not be passed. Otherwise
    # nbackup returns total number of data sets to be copied and size of data to be copied which is not required during trustee sync. Fix for bug#823611, 824095
    progressFlags = " --progress " if @opt['progress']
    progressFlags += " --progress-interval \"#{@opt['progress-interval']}\"" if @opt['progress-interval']
    nbackupCmd = "#{NBACKUP} #{srcFlags} #{progressFlags} -R #{@opt['s']} -T #{SMSTOOL_OUT_FILE} | #{NBACKUP} #{destFlags}"
    print_message(DEBUG, "Executing nbackup command from syncfiles: #{nbackupCmd}")
    callNbackup(nbackupCmd, srcPaths, destPaths)
    if @isFailover and @opt['continue-after-failover']
      begin
        print_message(ERROR, "reconnecting...")
        sleep 30 * @retryCnt
        @retryCnt = @retryCnt + 1
        # Here we do not have checkpoint information, so copy will start from beginning.
        # Reason/TODO: Since we pass -T file option to nbackup in this presently we dont know how to get the
        # source file name which can be stored as checkpoint.
        @bytesProcessed = @bytesCopied
	# To support NCP as source, nbackup should execute with the root credentials. So setting the ENV variable to 
 	# root user password is either source or target is NCP. This is to provide fix for bug #824782
        tempRemotePwd = ENV["SMS_REMOTE_PASSWORD"]
        if $isSrcNCP
    	  ENV['SMS_REMOTE_PASSWORD'] = $remoteRootPwd
 	end
  	doCmd(cmd, srcPaths, destPaths)
  	# Setting the back the SMS_REMOTE_PASSWORD to the original password it had. This is to provide fix for bug #824782
  	ENV['SMS_REMOTE_PASSWORD'] = tempRemotePwd
      end while @isFailover and @retryCnt <= 5
    end
  else
    print_message(DEBUG, "smstool's output file is empty. There are no datasets to sync.")
    print_message(INFO, "No datasets to be copied")
    $complete = true
  end


# Now copy files Metadata from source to destination server using nbackup.
   if @opt['no-trustees']
    print_message(INFO, "Skipping Trutsees")
   elsif File.exist?(SMSTOOL_META_OUT_FILE) and File.size(SMSTOOL_META_OUT_FILE) > 0
   if @multiplePathsSpecified
      destFlags += " -r @#{SOURCE_DEST_PATH_FILE}"
    else
      destFlags += " -r \"#{srcPaths[0]} #{destPaths[0]}\""
    end
    # To take care of compressoion/not-compression related stuff we may have to change the output file of smstool.
    changeAll = false
    if @compress and @uncompress
      modifyMetaOutputFile(srcPaths, destPaths, changeAll)
    elsif @uncompress
      changeAll = true
      modifyMetaOutputFile(srcPaths, destPaths, changeAll)
    #else
      # No need to change the output
    end

    if @opt['trustees-dirs-only']
      srcFlags += " --trustees-dirs-only"
    else
      srcFlags += " --exclude-file-data"
      # Pass "no-dir-traverse" option to nbackup to stop full directory data from getting synced when only trustees are modified for that directory.
      # This is the fix provided for the bug #836958
      srcFlags += " --no-dir-traverse"
    end
    $isTrusteeSync = true
    #If 'update-ifnewer' option is set, remove 'restore-ifnewer' from the destFlags as it cannot be used for syncing trustees
    if @opt['update-ifnewer']
	while !destFlags["--restore-ifnewer"].nil?
	   destFlags["--restore-ifnewer"]=""
	end
    end

    destFlags += " --exclude-file-data"
    destFlags += " --delete-existing-trustees" if @opt['delete-existing-trustees']
    print_message(DEBUG, "Executing nbackup command to copy Trustee's: #{nbackupCmd}")
    print_message(INFO, "Starting Trustee Sync")
    nbackupCmd = "#{NBACKUP} #{srcFlags} -R #{@opt['s']} -T #{SMSTOOL_META_OUT_FILE} | #{NBACKUP} #{destFlags}"
    callNbackup(nbackupCmd, srcPaths, destPaths)

    if @isFailover and @opt['continue-after-failover']
      begin
        print_message(ERROR, "reconnecting...")
        sleep 30 * @retryCnt
        @retryCnt = @retryCnt + 1
        # Here we do not have checkpoint information, so copy will start from beginning.
        # Reason/TODO: Since we pass -T file option to nbackup in this presently we dont know how to get the
        # source file name which can be stored as checkpoint.
        @bytesProcessed = @bytesCopied
  	# To support NCP as source, nbackup should execute with the root credentials. So setting the ENV variable to 
  	# root user password is either source or target is NCP. Once the command is executed setting back to the original password. This is to provide fix for bug #824782
        tempRemotePwd = ENV["SMS_REMOTE_PASSWORD"]
        if $isSrcNCP
          ENV['SMS_REMOTE_PASSWORD'] = $remoteRootPwd
        end
        doCmd(cmd, srcPaths, destPaths)
        ENV['SMS_REMOTE_PASSWORD'] = tempRemotePwd
      end while @isFailover and @retryCnt <= 5
    end
  else
    print_message(DEBUG, "smstool's metadata output file is empty. There are no trustees to sync.")
    $complete = true
  end
end

def doWindowsMigration
  flags = "-aS"
  flags += "vv"
  if @opt['e']
    if @opt['e'].class == Array
      @opt['e'].each { |i| flags += " --exclude=#{i}" }
    else
      flags += " --exclude=#{@opt['e']}"
    end
  end
  if @opt['A']
    flags += " --stats"
  end
  if @opt['never-overwrite']
    flags += " --ignore-existing"
  end
  credentials = Migration::KeyStore.credential( @opt['s'], :cn, @opt['use-casa'] )
  srcPath = verifyAndFixWinSrcPath
  if @opt['v']
    $log.debug {"@opt['v'] = #{@opt['v']}"}
    destPath, nss = verifyAndFixDestVol @opt['v']
  else
    #has to have -x
    $log.debug {"@opt['x'] = #{@opt['x']}"}
    destPath, nss = verifyAndFixDestPath @opt['x']
  end
  src_path = MNT_DIR + "/"
  Migration.smbfs_mount( @opt['s'], @opt['V'], credentials[:username], credentials[:password] )
  print_message(DEBUG, "Successfully mounted Windows share")
  if @opt['precheck']
    Migration.ncp_umount
    $stderr.puts "Information: Validation for migfiles done successfully\n"
    exit 0
  end
  # Windows share needs to be unmounted after migfiles. Setting the ncpMounted will ensure the unmount
  # Do not migrate these two directories. These directories will be empty. Exclude them
  if File.exist?(src_path+"/System\ Volume\ Information") and File.exist?(src_path+"/RECYCLER")
    flags += " --exclude /System\\ Volume\\ Information --exclude /RECYCLER"
  end
  cmd = "rsync #{flags} \"#{src_path}\" \"#{destPath}\" 2>&1"
  print_message(DEBUG,"rsync cmd = #{cmd}")
  source= @opt['s']
  print_message(DEBUG, "Source path: \"#{srcPath}\"")
  print_message(DEBUG, "Destination path: \"#{destPath}\"")
  date = DateTime.now
  dateStr = "#{date.mday}-#{date.mon}-#{date.year} #{date.hour}:#{date.min}:#{date.sec}"
  print_message(DEBUG, "Started on: \"#{dateStr}\"")

  begin
    Open3.popen3(cmd) do | input, output, err |
      cnt = 0
      while 1
        io_reader = output.gets
        if(io_reader == nil)
          break
        end
        io_reader.strip!
        if io_reader.include? "rsync  version"
          raise SystemCallError.new("error" ,Errno::ENOENT::Errno)
        end
        unless io_reader.include? "total:" or io_reader.include? "wrote" or io_reader.include? "building file list" or io_reader.include? "total size" or io_reader.include? "[sender]" or io_reader.include? "done" or io_reader.include? "[receiver]" or io_reader.include? "delta-transmission" or io_reader.include? "sent"
        if io_reader.include? "is uptodate"
          io_reader = io_reader.slice(0,io_reader.index(" is uptodate"))
        end
        if io_reader.include? "Permission denied"
          io_reader.gsub!(/#{MNT_DIR}/,srcPath+":")
          print_message(ERROR, "rsync: "+io_reader)
        end
        if @opt['i']
          $stdout.puts "Information: Copying #{srcPath}:#{io_reader} to #{destPath}#{io_reader}\n"
        else
          if cnt % 100 == 0
            print "#"
            $stdout.flush
          end
      end # while
    end #popen3
    cnt = cnt + 1
  end
end
  if not @opt['i']
    print "\n"
  end
  rescue Errno::EIO
  #apparently, it randomly gives this for no reason
  end
  print_message(DEBUG, "migfiles completed successfully at #{`date +%d-%m-%y\\ %H:%M:%S`}")
end

def doCmd cmd, srcPath, destPath
  fpForSmstool = File.open(SMSTOOL_OUT_FILE, "r") if @opt['sync']
  readingDone = false
  failedPaths = []
  firstTime = true
  prevFile = ""
  prevPairChkPt = -1
  chkPtNow = true
# Code changes for data collection for sync - Thangaraj
#  dirName = ""
#  prevTotDataSetsCopied = -1
#  prevFileCntInDir = -1
#  fileCntInDir = 1
#  prevTotFileCntDir = -1
#  fileTotCntDir = -1
#  tmpFileTotCnt = 0
#  @prevPair = 0
#  @prevPair = @pairNumberProcessed if @pairNumberProcessed > 0
#  fpForDataColForSyncTmp = nil
#  fpForDataColForSync = nil
#  if ((not @opt['trustees-only']) and (not @opt['sync']) and (@opt['c'] != nil) and @opt['fs-data-dist'] )
#    @syncFileName = @opt['c'].slice(0,@opt['c'].rindex("/")+1)+"dataForSync"
#    @syncFileNameTmp = "#{@syncFileName}"+".Tmp"
#    dirName = @dirNameatLastChkPt if @dirNameatLastChkPt != ""
#    fileCntInDir = @dirFileCntatLastCkhPt if @dirFileCntatLastCkhPt > 0
#    fileTotCntDir = @dirTotFileCntatLastCkhPt if @dirTotFileCntatLastCkhPt > 0
#    fpForDataColForSyncTmp = File.open("#{@syncFileNameTmp}"+".#{@pairNumberProcessed}", File::CREAT|File::RDWR|File::TRUNC, 0600)
#    if @bytesProcessed == 0
#      fpForDataColForSync = File.open("#{@syncFileName}"+".#{@pairNumberProcessed}", File::CREAT|File::WRONLY|File::TRUNC, 0600)
#    else
#      fpForDataColForSync = File.open("#{@syncFileName}"+".#{@pairNumberProcessed}", File::WRONLY | File::APPEND, 0600)
#    end
#    if (@bytesProcessed == 0) and (@pairNumberProcessed ==0)
#      fpForDataColForSyncTot = File.open("#{@syncFileName}", File::CREAT | File::RDWR | File::TRUNC, 0600)
#      fpForDataColForSyncTot.puts "PAIR DATASET_COUNT SOURCE DESTINATION"
#    else
#      fpForDataColForSyncTot = File.open("#{@syncFileName}", File::CREAT | File::RDWR | File::APPEND, 0600)
#    end
#  end
# Code changes for data collection for sync - end
  begin
    flag = false if not @isFailover
    error = false
    i = 0
    i = @pairNumberProcessed if @pairNumberProcessed > 0
    Open3.popen3(cmd) do | input, output, err |
      cnt = 0
    #Spanning a thread to read and process the error stream. If we won't create this thread then the flow will wait on "err.gets" until nbackup returns something. Due to this, there is a delay in invoking SIGINT trap (by migfiles) when user stops/interrupts migration process. This fix is provided for the bug #874287
    t = Thread.new {
      # BEGIN EXCEPTION HANDLING - FIX FOR RUBY 2.5.9 SIGABRT CRASH
      begin
        while 1
        line = err.gets
        break if line == nil
        line = line.force_encoding('ASCII-8BIT').encode('UTF-8', invalid: :replace, undef: :replace, replace: '?')
        line.strip!
        filename = ""
        if (line.index("nbackup:") == 0  and not line.include?("Please see nbackup_restore.warn file for details") and not line.include?("Please see nbackup_backup.warn file for details"))
          #I believe all nbackup errors beging with "nbackup:"
          #so we have an error
          #raise MigrationException.new("SMS error: #{line}")
          #but it maybe a non stopping error as well
          if line.include?("precomputing for displaying progress")
            print_message(INFO, "#{line}")
          elsif line.include?("Total dataSets = ")
            print_message(INFO, "#{line}")
# Progress changes
            print_message(PROGRESS, "#{line}")
          elsif line.include?("The system is out of disk space")
            print_message(FATAL, "Failed to migrate data. The system is out of disk space")
            exit 1
          elsif line =~ /(.*)0xfffdffe5(.*)The data set handle is invalid.(.*)/
            print_message(FATAL, "Failed to restore data. Could not find destination path. If the destination path is a cluster resource, then check whether the resource is moved.")
            print_message(FATAL, "#{line}")
            exit 1
          elsif line =~ /nbackup: Failed to open dataset (.*) for backup/
            print_message(FATAL, "Cannot migrate source path #{$1}: #{line}")
            failedPaths << $1
            if not @opt['sync'] and $1.casecmp(srcPath[i]) == 0
              print_message(WARN, "Going to next data set")
              # For option 'sync' we need not store this info, as it is not needed..
              i = i + 1
              @pairNumberProcessed = @pairNumberProcessed + 1
# Code changes for data collection for sync - Thangaraj
#              if ((not @opt['trustees-only']) and (not @opt['sync']) and (@opt['c'] != nil) and @opt['fs-data-dist'])
#                if(File.exist?("#{@syncFileNameTmp}"+".#{@prevPair}"))
#                  if(File.exist?("#{@syncFileNameTmp}"+"prev"+".#{@prevPair}"))
#                    fpForDataColForSyncTmp.seek(0,IO::SEEK_SET)
#                    tmpToPrevFileFP = File.open("#{@syncFileNameTmp}"+"prev"+".#{@prevPair}", File::WRONLY | File::APPEND, 0600);
#                    while tmpToPrevFile = fpForDataColForSyncTmp.gets
#                      tmpToPrevFileFP.puts tmpToPrevFile if tmpToPrevFile != ""
#                    end
#                    fileTotCntDir += fileCntInDir
#                    tmpToPrevFileFP.puts "#{fileCntInDir} #{dirName}" if dirName != ""
#                    fpForDataColForSyncTmp.close if not fpForDataColForSyncTmp.nil?
#                    tmpToPrevFileFP.close if not tmpToPrevFileFP.nil?
#                    File.delete("#{@syncFileNameTmp}"+".#{@prevPair}")
#                  else
#                    fileTotCntDir += fileCntInDir
#                    fpForDataColForSyncTmp.puts "#{fileCntInDir} #{dirName}" if dirName != ""
#                    fpForDataColForSyncTmp.close if not fpForDataColForSyncTmp.nil?
#                    File.rename("#{@syncFileNameTmp}"+".#{@prevPair}","#{@syncFileNameTmp}"+"prev"+".#{@prevPair}")
#                  end
#                  dirName = ""
#                  fileCntInDir = 1
#                end
#                tmpFileTotCnt = fileTotCntDir
#                fileTotCntDir = -1
#                if(File.exist?("#{@syncFileName}"+".#{@prevPair}"))
#                  fpForDataColForSync.close if not fpForDataColForSync.nil?
#                end
#
#                fpForDataColForSyncTmp = File.open("#{@syncFileNameTmp}"+".#{@pairNumberProcessed}", File::CREAT|File::RDWR|File::TRUNC, 0600)
#                fpForDataColForSync = File.open("#{@syncFileName}"+".#{@pairNumberProcessed}", File::CREAT|File::WRONLY|File::TRUNC, 0600)
#              end
# Code changes for data collection for sync - end
            end
            next
          elsif line.include?("nbackup: source server codepage is missing use 'usecodeset' option")
            print_message(FATAL, "Source server is NetWare 6.0. Load latest smsut.nlm and tsafs.nlm on source server")
          elsif line.include?("nbackup: Fail over / Fail back...")
            print_message(ERROR, "Resource Fail over / Fail back...")
          elsif line.include?("nbackup: Connection denied") or line.include?("nbackup: Unable to retrieve the Target Service Name")
            raise MigrationException.new(line)
          elsif line.include?("nbackup: reconnecting...")
            if @opt['continue-after-failover']
              #reconnection is supported only if --continue-after-failover is specified
              sleep 30   #Give nbackups some time to flush what's in the pipe
              killAllnbackups("sh -c #{$nbackup_command}", false)
              print_message(ERROR, "reconnecting...")
              @isFailover = true
              return
            else
              #Cannot restart, so just exit
              print_message(FATAL, "Cluster resource appears to have migrated. Cannot continue as option --continue-after-failover is not specified")
              killAllnbackups("sh -c #{$nbackup_command}", false)
              #exit 1
              return
            end
          elsif line.include?("0xfffdffc4") and line.include?("NWSMTSScanNextDataSet")
            #this is the connect to physical node and cluster resource failing over case
            #so migration cannot be complete
            print_message(ERROR, "#{line}")
            error = true
          else
            #if we are connected to cluster and its faiover then don't show error msgs
            print_message(ERROR, "#{line}") if not @isFailover
          end
        elsif line != "" and not line.include?("Number of directories:") \
                         and not line.include?("Number of files:") \
                         and not line.include?("Total data size:") \
                         and not line.include?("Elapsed time:") \
                         and not line.include?("Throughput:") and not line.include?("Please see nbackup_restore.warn file for details") and not line.include?("Please see nbackup_backup.warn file for details")

          if (line.index("Processed") == 0)
            if @opt['c']
              if line =~ /(\d+)MB of (\d+)/
                @totalBytesProcessed = @bytesProcessed + $2.to_i if flag == false
                @bytesCopied = @bytesProcessed + $1.to_i
                flag = true
# Progress changes
                print_message(PROGRESS, "Processed #{@bytesCopied}MB of #{@totalBytesProcessed}MB dataSetsCopied #{@totDataSetsCopied}") 
              end
            else
# Progress changes
              print_message(PROGRESS, "#{line} dataSetsCopied #{@totDataSetsCopied}")
            end
          else
            @isFailover = false
            @retryCnt = 1
            if not @opt['sync']
              # For option 'sync' this is not needed.
              if i < (destPath.length - 1)
                if line.index(destPath[i]) == 0 and (line.casecmp(destPath[i+1]) != 0 or firstTime)
                   #Force it to UTF-8, throwing out invalid bits in below code line
                   line.encode!("UTF-8", undef: :replace, invalid: :replace, replace: "")
                   filename = line.sub("#{destPath[i]}","")
                   firstTime = false
                else
                   @pairNumberProcessed = @pairNumberProcessed + 1
                   i = i + 1
# Code changes for data collection for sync - Thangaraj
#                  if ((not @opt['trustees-only']) and (not @opt['sync']) and (@opt['c'] != nil) and @opt['fs-data-dist'])
#                    if(File.exist?("#{@syncFileNameTmp}"+".#{@prevPair}"))
#                      if(File.exist?("#{@syncFileNameTmp}"+"prev"+".#{@prevPair}"))
#                        fpForDataColForSyncTmp.seek(0,IO::SEEK_SET)
#                        tmpToPrevFileFP = File.open("#{@syncFileNameTmp}"+"prev"+".#{@prevPair}", File::WRONLY | File::APPEND, 0600);
#                        while tmpToPrevFile = fpForDataColForSyncTmp.gets
#                          tmpToPrevFileFP.puts tmpToPrevFile if tmpToPrevFile != ""
#                        end
#                        fileTotCntDir += fileCntInDir
#                        tmpToPrevFileFP.puts "#{fileCntInDir} #{dirName}" if dirName != ""
#                        fpForDataColForSyncTmp.close if not fpForDataColForSyncTmp.nil?
#                        tmpToPrevFileFP.close if not tmpToPrevFileFP.nil?
#                        File.delete("#{@syncFileNameTmp}"+".#{@prevPair}")
#                      else
#                        fileTotCntDir += fileCntInDir
#                        fpForDataColForSyncTmp.puts "#{fileCntInDir} #{dirName}" if dirName != ""
#                        fpForDataColForSyncTmp.close if not fpForDataColForSyncTmp.nil?
#                        File.rename("#{@syncFileNameTmp}"+".#{@prevPair}","#{@syncFileNameTmp}"+"prev"+".#{@prevPair}")
#                      end
#                      dirName = ""
#                      fileCntInDir = 1
#                    end
#
#                    if(File.exist?("#{@syncFileName}"+".#{@prevPair}"))
#                      fpForDataColForSync.close if not fpForDataColForSync.nil?
#                    end
#                    tmpFileTotCnt = fileTotCntDir
#                    fileTotCntDir = -1
#                   fpForDataColForSyncTmp = File.open("#{@syncFileNameTmp}"+".#{@pairNumberProcessed}", File::CREAT|File::RDWR|File::TRUNC, 0600)
#                    fpForDataColForSync = File.open("#{@syncFileName}"+".#{@pairNumberProcessed}", File::CREAT|File::WRONLY|File::TRUNC, 0600)
#                  end
# Code changes for data collection for sync - end
                   if failedPaths.include?(srcPath[i])
                     @pairNumberProcessed = @pairNumberProcessed + 1
                     i = i + 1
# Code changes for data collection for sync - Thangaraj
#                      if ((not @opt['trustees-only']) and (not @opt['sync']) and (@opt['c'] != nil) and @opt['fs-data-dist'])
#                        if(File.exist?("#{@syncFileNameTmp}"+".#{@prevPair}"))
#                          if(File.exist?("#{@syncFileNameTmp}"+"prev"+".#{@prevPair}"))
#                            fpForDataColForSyncTmp.seek(0,IO::SEEK_SET)
#                            tmpToPrevFileFP = File.open("#{@syncFileNameTmp}"+"prev"+".#{@prevPair}", File::WRONLY | File::APPEND, 0600);
#                            while tmpToPrevFile = fpForDataColForSyncTmp.gets
#                              tmpToPrevFileFP.puts tmpToPrevFile if tmpToPrevFile != ""
#                            end
#                            fileTotCntDir += fileCntInDir
#                            tmpToPrevFileFP.puts "#{fileCntInDir} #{dirName}" if dirName != ""
#                            fpForDataColForSyncTmp.close if not fpForDataColForSyncTmp.nil?
#                            tmpToPrevFileFP.close if not tmpToPrevFileFP.nil?
#                            File.delete("#{@syncFileNameTmp}"+".#{@prevPair}")
#                          else
#                            fileTotCntDir += fileCntInDir
#                            fpForDataColForSyncTmp.puts "#{fileCntInDir} #{dirName}" if dirName != ""
#                            fpForDataColForSyncTmp.close if not fpForDataColForSyncTmp.nil?
#                            File.rename("#{@syncFileNameTmp}"+".#{@prevPair}","#{@syncFileNameTmp}"+"prev"+".#{@prevPair}")
#                          end
#                          dirName = ""
#                          fileCntInDir = 1
#                        end

#                        if(File.exist?("#{@syncFileName}"+".#{@prevPair}"))
#                          fpForDataColForSync.close if not fpForDataColForSync.nil?
#                        end
#                        tmpFileTotCnt = fileTotCntDir
#                        fileTotCntDir = -1
#                        fpForDataColForSyncTmp = File.open("#{@syncFileNameTmp}"+".#{@pairNumberProcessed}", File::CREAT|File::RDWR|File::TRUNC, 0600)
#                        fpForDataColForSync = File.open("#{@syncFileName}"+".#{@pairNumberProcessed}", File::CREAT|File::WRONLY|File::TRUNC, 0600)
#                      end
# Code changes for data collection for sync - end
                   end
                   chkPtNow = true
                   #Force it to UTF-8, throwing out invalid bits in below code line
                   line.encode!("UTF-8", undef: :replace, invalid: :replace, replace: "")
                   filename = line.sub("#{destPath[i]}","")
                end
              else
                #Force it to UTF-8, throwing out invalid bits in below code line
                line.encode!("UTF-8", undef: :replace, invalid: :replace, replace: "")
                filename = line.sub("#{destPath[i]}", "") if line.index(destPath[i]) == 0
              end
              if @opt['i']
                if not @opt['trustees-only']
                  print_message(INFO, "Copying #{srcPath[i]}#{filename} to #{line}")
# Progress changes
                  @totDataSetsCopied += 1
# Code changes for data collection for sync - Thangaraj
#                    if ((not @opt['trustees-only']) and (not @opt['sync']) and (@opt['c'] != nil) and @opt['fs-data-dist'])
#                      tmpStr = "#{srcPath[i]}#{filename}"
#                      tmpStr.chomp
#                      tmpStr.strip
#                      if tmpStr[-1,1] == '/' || tmpStr[-1,1] == ':'
#                        if dirName != tmpStr
#                          fileTotCntDir += fileCntInDir
#                          fpForDataColForSyncTmp.puts "#{fileCntInDir} #{dirName}\n" if dirName != ""
#                        end
#                        dirName = tmpStr
#                        fileCntInDir = 1
#                      else
#                        fileCntInDir += 1 if dirName !=""
#                      end
#                    end
# Code changes for data collection for sync - end
                else
                  print_message(INFO, "Restoring trustees to #{line}")
                end
              end
            else
              # option 'sync' is given..
              # This is needed to print verbose message to stdout.
              if readingDone == false
                $lastChkPt = fpForSmstool.gets
                $lastChkPt.strip! if $lastChkPt != nil and $lastChkPt != ""
                @pairChkPt = 0
                readingDone = true
              end
              print_message(INFO, "Copied #{line}")
# Progress changes
              @totDataSetsCopied += 1
            end
          end
        elsif line != "" and not error and (line.include?("Throughput:") or line.include?("Elapsed time:"))
          $complete = true
        end
        if chkPtNow or ((@opt['c'] != nil or @opt['continue-after-failover']) and cnt % 10 == 0 and line != "" and filename != nil and filename != "")
          if not @opt['sync']
            $lastChkPt = prevFile if prevFile != ""
            @pairChkPt = prevPairChkPt if prevPairChkPt != -1
# Code changes for data collection for sync - Thangaraj
#              @totDataSetsCopiedChkPt = prevTotDataSetsCopied if prevTotDataSetsCopied != -1
#              @dirFileCntChkPt = prevFileCntInDir if prevFileCntInDir != -1
#              @dirTotFileCntChkPt = prevTotFileCntDir if prevTotFileCntDir != -1
#              prevTotDataSetsCopied = @totDataSetsCopied
#              prevFileCntInDir = fileCntInDir
#              prevTotFileCntDir = fileTotCntDir
# Code changes for data collection for sync - end
            prevFile = "#{srcPath[i]}#{filename}"
            prevPairChkPt = @pairNumberProcessed
            @bytesCopiedAtLastChkPt = @bytesCopied
# Code changes for data collection for sync - Thangaraj
#            if ((not @opt['trustees-only']) and (not @opt['sync']) and (@opt['c'] != nil) and @opt['fs-data-dist'])
#              if(@prevPair != @pairNumberProcessed)
#                if(File.exist?("#{@syncFileNameTmp}"+"prev"+".#{@prevPair}"))
#                  syncFileFP = File.open("#{@syncFileName}"+".#{@prevPair}", File::WRONLY | File::APPEND, 0600)
#                  tmpToOrgFileFP = File.open("#{@syncFileNameTmp}"+"prev"+".#{@prevPair}", File::RDONLY, 0600);
#                  while tmpToOrgFile = tmpToOrgFileFP.gets
#                    syncFileFP.puts tmpToOrgFile if tmpToOrgFile != ""
#                  end
#                  tmpToOrgFileFP.close if not tmpToOrgFileFP.nil?
#                  syncFileFP.close if not syncFileFP.nil?
#                  File.delete("#{@syncFileNameTmp}"+"prev"+".#{@prevPair}")
#                end
#                fpForDataColForSyncTot.puts "\"#{@prevPair}\" \"#{tmpFileTotCnt}\" \"#{srcPath[@prevPair]}\" \"#{destPath[@prevPair]}\""
#                @prevPair = @pairNumberProcessed
#              end
#
#              if(File.exist?("#{@syncFileNameTmp}"+"prev"+".#{@pairNumberProcessed}"))
#                tmpToOrgFileFP = File.open("#{@syncFileNameTmp}"+"prev"+".#{@pairNumberProcessed}", File::RDONLY, 0600);
#                while tmpToOrgFile = tmpToOrgFileFP.gets
#                  fpForDataColForSync.puts tmpToOrgFile if tmpToOrgFile != ""
#                end
#                tmpToOrgFileFP.close if not tmpToOrgFileFP.nil?
#                File.delete("#{@syncFileNameTmp}"+"prev"+".#{@pairNumberProcessed}")
#              end
#
#              if(File.exist?("#{@syncFileNameTmp}"+".#{@pairNumberProcessed}"))
#                fpForDataColForSyncTmp.close if not fpForDataColForSyncTmp.nil?
#                File.rename("#{@syncFileNameTmp}"+".#{@pairNumberProcessed}","#{@syncFileNameTmp}"+"prev"+".#{@pairNumberProcessed}")
#                fpForDataColForSyncTmp = File.open("#{@syncFileNameTmp}"+".#{@pairNumberProcessed}", File::CREAT|File::RDWR|File::TRUNC, 0600)
#              end
#            end
# Code changes for data collection for sync - end
          end
          print_message(DEBUG, "Checkpoined at #{$lastChkPt}")
          print_message(DEBUG, "Checkpoined pair #{@pairChkPt}")
          if cnt % 20 and @opt['c'] != nil
            writeSessionFile
          end
          chkPtNow = false if chkPtNow
        end
        cnt = cnt + 1 if filename != ""
      end # while

      # EXCEPTION HANDLING - Catches exceptions instead of triggering SIGABRT
      rescue StandardError => e
        # Log the error instead of crashing
        print_message(ERROR, "Thread error processing stderr: #{e.message}")
        print_message(DEBUG, "Error class: #{e.class}")
        print_message(DEBUG, "Backtrace: #{e.backtrace.join("\n")}")
        # Thread exits gracefully instead of aborting

      rescue Exception => e
        # Catch even non-StandardError exceptions (like SystemExit, Interrupt)
        print_message(ERROR, "Fatal thread error: #{e.class}: #{e.message}")
        print_message(DEBUG, "Backtrace: #{e.backtrace.join("\n")}")
        # Re-raise certain critical exceptions
        raise if e.is_a?(SystemExit) or e.is_a?(Interrupt)
      end
      # END EXCEPTION HANDLING
      }
      # Also add exception handling for thread join
      begin
        t.join
      rescue => e
        print_message(ERROR, "Error joining stderr thread: #{e.message}")
        print_message(DEBUG, "Join error: #{e.class}")
      end
    end # Open3
    if not @opt['i']
      print "\n"
    end
  rescue Errno::EIO
      #apparently, it randomly gives this for no reason
  end # begin
  fpForSmstool.close if not fpForSmstool.nil?
# Progress changes
  # If it is not trustee sync then only pass this information to miggui. Added to provide fix for bug#823611, 824095
  if not $isTrusteeSync
    print_message(PROGRESS, "dataSetsCopied #{@totDataSetsCopied}")
  end
# Code changes for data collection for sync - Thangaraj
#  if ((not @opt['trustees-only']) and (not @opt['sync']) and (@opt['c'] != nil) and @opt['fs-data-dist']) 
#      if(@prevPair != @pairNumberProcessed)
#        if(File.exist?("#{@syncFileNameTmp}"+"prev"+".#{@prevPair}"))
#          syncFileFP = File.open("#{@syncFileName}"+".#{@prevPair}", File::WRONLY | File::APPEND, 0600)
#          tmpToOrgFileFP = File.open("#{@syncFileNameTmp}"+"prev"+".#{@prevPair}", File::RDONLY, 0600);
#          while tmpToOrgFile = tmpToOrgFileFP.gets
#            syncFileFP.puts tmpToOrgFile if tmpToOrgFile != ""
#          end
#          tmpToOrgFileFP.close if not tmpToOrgFileFP.nil?
#          syncFileFP.close if not syncFileFP.nil?
#          File.delete("#{@syncFileNameTmp}"+"prev"+".#{@prevPair}")
#        end
#        fpForDataColForSyncTot.puts "\"#{@prevPair}\" \"#{tmpFileTotCnt}\" \"#{srcPath[@prevPair]}\" \"#{destPath[@prevPair]}\""
#        @prevPair = @pairNumberProcessed
#      end
#
#      if(File.exist?("#{@syncFileNameTmp}"+"prev"+".#{@pairNumberProcessed}"))
#        tmpToOrgFileFP = File.open("#{@syncFileNameTmp}"+"prev"+".#{@pairNumberProcessed}", File::RDONLY, 0600);
#        while tmpToOrgFile = tmpToOrgFileFP.gets
#          fpForDataColForSync.puts tmpToOrgFile if tmpToOrgFile != ""
#        end
#        tmpToOrgFileFP.close if not tmpToOrgFileFP.nil?
#        File.delete("#{@syncFileNameTmp}"+"prev"+".#{@pairNumberProcessed}")
#      end
#
#      fpForDataColForSyncTmp.puts "#{fileCntInDir} #{dirName}" if dirName != ""
#      fileTotCntDir = fileTotCntDir + fileCntInDir
#      fpForDataColForSyncTmp.seek(0,IO::SEEK_SET)
#      while tmpToOrgFile = fpForDataColForSyncTmp.gets
#        fpForDataColForSync.puts tmpToOrgFile
#      end
#      fpForDataColForSyncTot.puts "\"#{@pairNumberProcessed}\" \"#{fileTotCntDir}\" \"#{srcPath[@pairNumberProcessed]}\" \"#{destPath[@pairNumberProcessed]}\""
##Compute total datasets of all source target pair
#      fpForDataColForSyncTot.seek(0,IO::SEEK_SET)
#      tmpCnt = 0
##Read the header
#      line = fpForDataColForSyncTot.gets
#      while line = fpForDataColForSyncTot.gets
#        if line =~ /\"(\d+)\"\s\"(\d+)\"/
#          tmpCnt = tmpCnt + $2.to_i
#        end
#      end
#      fpForDataColForSyncTot.puts "Total dataSet = #{tmpCnt}"
#     
#      fpForDataColForSyncTot.close if not fpForDataColForSyncTot.nil?
#      fpForDataColForSyncTmp.close if not fpForDataColForSyncTmp.nil?
#      fpForDataColForSync.close if not fpForDataColForSync.nil?
#      File.delete("#{@syncFileNameTmp}"+".#{@pairNumberProcessed}") if File.exist?("#{@syncFileNameTmp}"+".#{@pairNumberProcessed}")
#  end
# Code changes for data collection for sync - end
end

#if the checkpoint filename is > 512 then it needs to put in a file and passed to nbacku
def getCheckPointStr fileName
  return "" if fileName == ""
  if fileName.length < 512
    checkPtStr = " --checkpoint \"#{fileName}\""
    $lastChkPt = "#{fileName}"
  else
    File.open("/tmp/checkpoint", "w") { |fp|
      fp.puts(r['stopped-at'])
    }
    checkPtStr = " --checkpoint @/tmp/checkpoint"
  end
# Code changes for data collection for sync - Thangaraj
#    fileName.chomp
#    fileName.strip
#    if fileName.index("/")
#      @dirNameatLastChkPt = fileName.slice(0,fileName.rindex("/")+1)  if fileName.rindex("/") != nil
#    elsif fileName.index(":")
#      @dirNameatLastChkPt = fileName.slice(0,fileName.rindex(":")+1) if fileName.rindex(":") != nil
#    end
# Code changes for data collection for sync - end
  return checkPtStr
end

# This function validate and process session file information
def processSessionFile srcPaths, destPaths, sourcePathFile, destPathFile, localIp
  checkPtStr = ""
  $lastChkPt = srcPaths[0]
  if File.size?(@opt['c']) != nil
    print_message(DEBUG, "Session file: #{@opt['c']}")
    #file exists and is not zero size
    input = File.open(@opt['c'], "r")
    r = YAML.load(input)
    #compare src and dest info to make sure its the right session file
    if r['src-server'] != @opt['s'] or r['dest-server'] != localIp
        print_message(FATAL, "Session file values and command line input don't match. Ensure the sessions file is correct")
        exit 1
    end
    if @multiplePathsSpecified
      if r['src-path'] != sourcePathFile or r['dest-path'] != destPathFile
        print_message(FATAL, "Session file values and command line input don't match. Ensure the sessions file is correct")
        exit 1
      end
    else
      if r['src-path'] != srcPaths[0] or r['dest-path'] != destPaths[0]
        print_message(FATAL, "Session file values and command line input don't match. Ensure the sessions file is correct")
        exit 1
      end
    end
    if r['stopped-at'] != nil
      checkPtStr = getCheckPointStr(r['stopped-at'])
    else
      if r['status'] == "completed"
        print_message(INFO, "Session status is complete")
        exit 0
      end
    end

    @bytesProcessed = r['Bytes Processed'].to_i
    @pairNumberProcessed = r['Pair Processing'].to_i
    @pairChkPt = @pairNumberProcessed
# Code changes for data collection for sync - Thangaraj
#      @dirFileCntatLastCkhPt = r['File Count in Dir'].to_i
#      @dirTotFileCntatLastCkhPt = r['File Tot Count in Dir'].to_i
#      @totDataSetsCopied = r['Total DataSet Copied'].to_i
# Code changes for data collection for sync - end
    $session << "---\n"
    $session << "  src-server: #{r['src-server']}\n"
    $session << "  dest-server: #{r['dest-server']}\n"
    if @multiplePathsSpecified
      $session << "  src-path: \"#{sourcePathFile}\"\n" 
      $session << "  dest-path: \"#{destPathFile}\"\n"
    else
      $session << "  src-path: \"#{r['src-path']}\"\n"
      $session << "  dest-path: \"#{r['dest-path']}\"\n"
    end
    $session << "  started-on: \"#{r['started-on']}\"\n"
  else
    print_message(DEBUG, "Session file: #{@opt['c']} not found, creating new session file")
    #its the first time
    $session << "---\n"
    $session << "  src-server: #{@opt['s']}\n"
    $session << "  dest-server: #{localIp}\n"
    if @multiplePathsSpecified
      $session << "  src-path: \"#{sourcePathFile}\"\n"
      $session << "  dest-path: \"#{destPathFile}\"\n"
    else
      $session << "  src-path: \"#{srcPaths[0]}\"\n"
      $session << "  dest-path: \"#{destPaths[0]}\"\n"
    end
    date = DateTime.now
    dateStr = "#{date.mday}-#{date.mon}-#{date.year} #{date.hour}:#{date.min}:#{date.sec}"
    $session << "  started-on: \"#{dateStr}\"\n"
    #this is the constant info, we need to add "stopped-at" and "status" on  or completion
  end
  return checkPtStr
end

# This function gets source and destinatin flags to be used with nbackup
def setFlagsForNbackup excludePath, credentialDetails
  # Start building source and destination flags for nbackup
  destFlags = " -xvF"
  srcFlags = " -cF"

  if @opt['e']
    if @opt['e'].class == Array
      @opt['e'].each { |i| srcFlags += " --exclude-file=\"#{i}\"" }
    else
      srcFlags += " --exclude-file=\"#{@opt['e']}\""
    end
  end

  if excludePath
    if excludePath.class == Array
      excludePath.each { |i| srcFlags += " --exclude-path=\"#{i}\"" }
    else
      srcFlags += " --exclude-path \"#{excludePath}\""
    end
  end

  if not @opt['sync'] #No need of date time filters if sync as smstool takes care of it
    if @opt['u']  #modified-after
      srcFlags += " --modified-after \"#{@opt['u']}\""
      srcFlags += " --created-after \"#{@opt['u']}\"" if @passCreatedDateFilters
    end
    srcFlags += " --modified-before \"#{@opt['modified-before']}\"" if @opt['modified-before']
    srcFlags += " --accessed-after \"#{@opt['accessed-after']}\"" if @opt['accessed-after']
    srcFlags += " --accessed-before \"#{@opt['accessed-before']}\"" if  @opt['accessed-before']
  end
  
  if @opt['sync']
    srcFlags += " --disable-caching"
  end

  if @opt['delete-file-on-restore-error']
    destFlags += " --delete-file-on-restore-error"
  end

  if @opt['trustees-only']
    srcFlags += " --exclude-file-data"
    destFlags += " --exclude-file-data"
    destFlags += " --delete-existing-trustees" if @opt['delete-existing-trustees']
  end

  if @opt['trustees-dirs-only'] and not @opt['sync']
    srcFlags += " --trustees-dirs-only"
    destFlags += " --delete-existing-trustees" if @opt['delete-existing-trustees']
  end

  srcFlags += " --disable-login" if @opt['disable-login']

  if @opt['usecodeset']
    srcFlags += " --usecodeset \"#{@opt['usecodeset']}\""
    destFlags += " --usecodeset \"#{@opt['usecodeset']}\""
  end

  srcFlags += " --exclude-trustees" if @opt['no-trustees']

  if @opt['demigrate-files']
    srcFlags += " --demigrate-files"
    destFlags += " --demigrate-files"
  end

  destFlags += " --restore-ifnewer " if @opt['update-ifnewer']
  destFlags += " --keep-old-files" if @opt['never-overwrite']

  destFlags += " --exclude-dirquota " if @opt['no-dirquotas']
  destFlags += " --exclude-userquota " if @opt['no-userquotas']

  destFlags += " -U \"#{credentialDetails['dest-username']}\""

  if $isSrcLinux
    # To support NCP as source, nbackup should execute with the root credentials. So setting the user to 
    # root if either source or target is NCP. This is to provide fix for bug #824782
    if $isSrcNCP
      srcFlags += " -U \"#{ROOT_USER}\""
    else
      srcFlags += " -U \"#{credentialDetails['source-username']}\""
    end
  else
    srcFlags += " -U \".#{credentialDetails['source-username']}\""
  end
  return srcFlags, destFlags 
end

def getExistingMountPointKey mountPoints, destPath
  mountPoints.each_key do |mntpt|
    if destPath == mntpt   #path and the mount point are the same
      return mntpt
    elsif destPath.index(mntpt+"/") == 0   #path has "mntpnt/" at its begining
      return mntpt
    end
  end
  return nil
end

#Boolean variable to track whether data sync is happening of trustee sync is happening. Added to provide fix for bug#823611, 824095 
$isTrusteeSync = false
#Boolean variable to hold whether source is NCP or not
$isSrcNCP = false
# This function does following...
# 1. Returns arrays of source and destination paths
# 2. Creates two files which are required for nbackup
# 3. validate the source and destination paths
def processMultiplePaths(sourcePathFile, destPathFile, credentialDetails)
  excludePath = []
  srcPaths = []
  destPaths = []
  srcPath = ""
  srcVol = ""
  listOfSrcVols = {}

  # Process source paths file
  volMap = getVolumesAndMountPoints(credentialDetails, @opt) if $isSrcLinux and not @opt['P']

  File.readlines(sourcePathFile).each do |path|
    @opt['V'] = @opt['X'] = nil 
    if path != nil and path != ""
      path.strip!
      if @opt['P']
        # Source is posix!
        path = path.chomp('/') + "/"
      elsif $isSrcLinux
        # If path contains ":" or it does not contain any "/" then we assume its a volume path
        if (path.include?(":") or not path.include?("/"))
          # Get mount point details of this volume
          @opt['V'] = path
          srcVol, mountPoint, srcPath = verifyAndFixSrcPath(volMap, @opt)
          # To support NCP as source, nbackup should execute with the root credentials. So to 
    	  # identify the source volume type, the below method call has been added. This is to provide fix for bug #824782
	  returnValue = verifyAndReturnSrcType(volMap, @opt, false)
      	  if returnValue != false
            $isSrcNCP = returnValue
          end
          if srcPath == nil and srcVol == nil and mountPoint == nil
            print_message(FATAL, "Source path validation failed for path #{path}")
            exit 1
          end
          excludePath << mountPoint.chomp('/') + "/" +"._NETWARE/" if srcPath == mountPoint
          path = srcPath
        else
          @opt['X'] = path
          srcVol, mountPoint, srcPath = verifyAndFixSrcPath(volMap, @opt)
          # To support NCP as source, nbackup should execute with the root credentials. So to 
    	  # identify the source volume type, the below method call has been added. This is to provide fix for bug #824782
          returnValue = verifyAndReturnSrcType(volMap, @opt, false)
     	  if returnValue != false
            $isSrcNCP = returnValue
          end
          if srcPath == nil and srcVol == nil and mountPoint == nil
            print_message(FATAL, "Source path validation failed for path #{path}")
            exit 1
          end
          excludePath << srcPath.chomp('/') + "/" +"._NETWARE/" if srcPath == mountPoint
          path = srcPath
        end
      else
        # Source is NetWare. Here we have to check whether NFS is loaded for specified volume or not?
        srcVol = path
        if path.include?(":")
          srcVol, extraPath = path.split(":")
        end
        if not listOfSrcVols.has_key?(srcVol)
          listOfSrcVols[srcVol] = srcVol
          if not checkNFSLoadedOnSource(@opt['s'], srcVol, "\"#{credentialDetails['source-username']}\"")
            print_message(FATAL, "NFS namespace is not loaded for the volume #{srcVol} on source server #{@opt['s']}")
            exit 1
          end
        end
        # If path doesn't include ":" add it. Also add "/"
        if not path.include?(":")
          path = path + ":"
        end
        # Should not add "/" if source path is either SRCVOL: or SRCVOL
        path = path.chomp('/') + "/" if path.index(":") != (path.length-1)
      end
      srcPaths << path
    end
  end

  cnt = 0
  fpSourcePaths = File.open(SOURCE_PATH_FILE, File::CREAT|File::WRONLY|File::TRUNC, 0600)
  fpSourceDestPairs = File.open(SOURCE_DEST_PATH_FILE, File::CREAT|File::WRONLY|File::TRUNC, 0600)
  if @opt['sync']
    fpSourceDestPairsForSMSTOOL = File.open(SOURCE_DEST_PATH_FILE_FOR_SMSTOOL, File::CREAT|File::WRONLY|File::TRUNC, 0600)
  end
  mountPoints = {}
  @compressInfo = {}

  File.readlines(destPathFile).each do |destPath|
    if destPath != nil and destPath != ""
      # Use hash which stores mount points and compression bit associated with it.
      destPath.strip!
      mntptKey = getExistingMountPointKey(mountPoints, destPath)
      if mntptKey == nil
        # This means its a different volume path, so store it in hash
        vol, mntptKey = get_nss_vol_and_mnt_point_from_path(destPath)
        # Got mount point now....
        if vol != nil and mntptKey != nil
          # This means this path refers to NSS volume
          if is_compression_enabled?(vol)
            print_message(DEBUG, "Destination path #{destPath} is NSS and compression enabled")
            mountPoints[mntptKey] = true
          else
            mountPoints[mntptKey] = false
          end
        else
          mountPoints[destPath] = false
          mntptKey = destPath
        end
      end
      # We have to just check for whether this path is compression enabled or not?
      destPath = destPath.chomp('/') + "/"
      if mountPoints[mntptKey] == true
        @compress = true
        @compressInfo[destPath] = true
        fpSourcePaths.puts(srcPaths[cnt])
      else
        @uncompress = true
        @compressInfo[destPath] = false
        fpSourcePaths.puts("--uncompress"+"\t"+srcPaths[cnt])
      end
      fpSourceDestPairs.puts(srcPaths[cnt]+" "+destPath)
      destPaths << destPath
      if @opt['sync']
        shadowFSDestPath = getShadowFSPath(destPath)
        if shadowFSDestPath != nil 
          print_message(DEBUG, "shadowFSDestPath = #{shadowFSDestPath} for destPath = #{destPath}")
          # The ShadowFS path is passed to smstool. So that missed file comparison is done
          # against the shadowfs Path
          fpSourceDestPairsForSMSTOOL.puts(srcPaths[cnt]+" "+shadowFSDestPath)
        else
          fpSourceDestPairsForSMSTOOL.puts(srcPaths[cnt]+" "+destPath)
        end
      end
      cnt = cnt + 1
    end
  end
  fpSourcePaths.close
  fpSourceDestPairs.close
  fpSourceDestPairsForSMSTOOL.close if @opt['sync']
  print_message(DEBUG, "Source path  -----> Destinaion path")
  cnt = 0
  while cnt < srcPaths.length
    print_message(DEBUG, "#{srcPaths[cnt]} -----> #{destPaths[cnt]}") 
    cnt = cnt + 1
  end
  return srcPaths, destPaths, excludePath
end

# This function returns name of the files which contains source and destination paths
def validateAndGetFileNames
   if @opt['x'] !~ /^@/ and  @opt['v'] !~ /^@/
     print_message(FATAL, "Source paths are specified in a file so destination paths(--destination-full-path or --destination-path) also needs to be specified in a file")
     exit 1
   end
   if @opt['V'] !~ /^@/ and @opt['X'] !~ /^@/
     print_message(FATAL, "Destination paths are specified in a file so source paths(--source-path or --source-full-path) also needs to be specified in a file")
     exit 1
   end
   if @opt['V'] =~ /^@/
     sourcePathFile = @opt['V'].slice(1, @opt['V'].length)
   else
     sourcePathFile = @opt['X'].slice(1, @opt['X'].length)
   end
   if @opt['v'] =~ /^@/
     destPathFile = @opt['v'].slice(1, @opt['v'].length)
   else
     destPathFile = @opt['x'].slice(1, @opt['x'].length)
   end
   if not File.exist?(sourcePathFile) or not File.size?(sourcePathFile)
     print_message(FATAL, "Specified input paths file #{sourcePathFile} does not exist or is empty")
     exit 1
   end
   if not File.exist?(destPathFile) or not File.size?(destPathFile)
     print_message(FATAL, "Specified input paths file #{destPathFile} does not exist or is empty")
     exit 1
   end
   noOfSourcePaths = File.readlines(sourcePathFile).length
   noOfDestPaths = File.readlines(destPathFile).length
   if noOfSourcePaths != noOfDestPaths
     print_message(FATAL, "Number of source paths #{noOfSourcePaths} are different from number of destination paths #{noOfDestPaths}")
     exit 1
   end

   @multiplePathsSpecified = true
   return sourcePathFile, destPathFile
end

def main
  $argdef = [
    ['s', 'source-server', "indicates the source server's IP address or hostname", ParseArgText, true],
#    ['W', 'windows', "indicates that migration source  is a Windows server ", ParseArgBool],
    ['p', 'posix', "specifies a pure POSIX migration. Unless specified default is NSS", ParseArgBool],
    ['P', 'source-posix', "specifies that source path is POSIX. Unless specified default is NSS", ParseArgBool],
    ['i', 'verbose', "displays verbose information", ParseArgBool],
    ['V', 'source-path', "path to be migrated from the source server", ParseArgText, false],
    ['v', 'destination-path', "specifies the path where the data should be copied on destination server", ParseArgText],
    ['x', 'destination-full-path', "specifies the full destination path where the data should be copied", ParseArgText],
    ['X', 'source-full-path', "specifies the full source path to be copied", ParseArgText],
    [nil, 'continue-after-failover', "specifies that migfiles should continue after a cluster resource failover", ParseArgBool],
    [nil, 'disable-login', "New logins to source server will be disabled during data migration", ParseArgBool],
    [nil, 'never-overwrite', "do not overwrite the files if they already exist on the destination server", ParseArgBool],
    ['e', 'exclude', "exclude filter on files to be copied", ParseArgMult],
    [nil, 'exclude-path', "exclude the directory with the specified source path from migration", ParseArgMult],
    [nil, 'sync', "migrate files from the source server that are not available on the destination server or have been modified after the date given", ParseArgText],
    [nil, 'delete', "files that do not exist on the source server are deleted from the destination server. Can be used only with --sync option", ParseArgBool],
    [nil, 'trustees-only', "migrates only the trustees. New trustees added to the source server are migrated to the destination server", ParseArgBool],
    [nil, 'ignore-quota-checking', "Ignore quota checking on destination during Migration", ParseArgBool],
    [nil, 'delete-existing-trustees', "trustees that do not exist on the source server are deleted from the destination server. Can be used only with --trustees-only option", ParseArgBool],
    [nil, 'delete-file-on-restore-error', "Deletes partially restored files on restore error", ParseArgBool],
    ['c', 'session-file', "stores the migration progress, this file can be used to continue the migration ", ParseArgText],
    [nil, 'no-trustees', "exclude trustees while migrating", ParseArgBool],
    [nil, 'use-casa', "use CASA to store/retrieve username and password information", ParseArgBool],
    [nil, 'source-unsecure-ldap', "use unsecure LDAP for all LDAP commands", ParseArgBool],
    [nil, 'source-ldap-port', "port where LDAP server is listening", ParseArgText],
    [nil, 'demigrate-files', "migrate the data of HSM migrated files, by default only stubs are migrated", ParseArgBool],
    [nil, 'update-ifnewer', "update the file if the file on the source server is newer than the one on target server", ParseArgBool],
    ['u', 'modified-after', "migrate files which are modified after this date", ParseArgText],
    [nil, 'modified-before', "migrate files which are modified before this date", ParseArgText],
    [nil, 'accessed-after', "migrate files which are accessed after this date", ParseArgText],
    [nil, 'accessed-before', "migrate files which are accessed before this date", ParseArgText],
    [nil, 'usecodeset', "codepage of NetWare 5.1 source server", ParseArgText],
    [nil, 'progress', "displays migration progress", ParseArgBool],
    [nil, 'progress-interval', "time interval for displaying progress", ParseArgText],
    [nil, 'no-dirquotas', "exclude directory quotas while migrating", ParseArgBool],
    [nil, 'no-userquotas', "exclude user quotas while migrating", ParseArgBool],
    [nil, 'precheck', "checks whether system meets all pre-requisite to start migfiles", ParseArgBool],
    [nil, 'debug', "generate debug log", ParseArgBool],
    [nil, 'trustees-dirs-only', "Backup Trustee's on Directories Only", ParseArgBool],
    [nil, 'list-extensions', "List Extensions on the source and target respectively", ParseArgBool],
#    [nil, 'fs-data-dist', "collects file system data distribution at source during migration, this option should co-exist with --session-file option and generated files will be in the session file location. This option is only valid for migration", ParseArgBool],
#    [nil, 'thread-tag', "Customized thread tag name", ParseArgText],
#    [nil, 'exclude-path-file', "Specify the file which has exclude paths", ParseArgText, false],
    ParseArgHelp,
    ParseArgUsage
  ]

  # Presently when we start migfiles with -c option and stop it immediately it will create invalid 
  # session file. We need the below boolean variable to avoid this to happen
  @sessionProcessingDone = false
  @complete = false
  @disabledQuotas = false 
#  @threadTag = ""

  # Initialize Debug Logging system
  migfilesCommand = "#{$0}"
  debugOpt = false
  $*.each do |arg|
    if arg.strip == "--debug"
      debugOpt = true
    end
    migfilesCommand += " #{arg}"
  end

  initLog(debugOpt, "migfiles")
  print_message(DEBUG, "migfiles command started at #{`date +%d-%m-%y\\ %H:%M:%S`}")
  print_message(DEBUG, "migfiles command executed as: #{migfilesCommand}")

  # Parse and get command line arguments into hash @opt
  @opt = {}
  (@opt, extraArgs) = parseargs(ARGV, $argdef)

## Code changes for data collection for sync - Thangaraj
#  if @opt['thread-tag']
#     @threadTag = @opt['thread-tag'] if @opt['thread-tag'] != nil
#  end

  # We need to set this at the beginning before we ask for authentication later
  @opt['use-casa'] = true if ENV["MIG_USE_CASA"] != nil

  # Precheck starts here...
  # Validate arguments
  validateArgs()

  # if option --windows is given, we need to only call function doWindowsMigration and exit   
  if @opt['W']
    print_message(DEBUG, "Source server type: Windows")
    # Create a array of options which are ignored by this utility for Windows Migration
    optionsIgnoredForWinMig = ["exclude-path", "delete", "c", "no-trustees", "source-unsecure-ldap",
                      "source-ldap-port", "demigrate-files", "update-ifnewer", "u",
                      "modified-before", "accessed-after", "accessed-before", "progress", "progress-interval",
                      "no-dirquotas", "no-userquotas", "sync", "trustees-only", "delete-file-on-restore-error", "trustee-dirs-only"]
    optionsIgnoredForWinMig.each do |option|
      if @opt[option]
         print_message(WARN, "Option #{option} is not supported for Windows migration")
      end
    end

    doWindowsMigration()
    exit 0
  end

  # Check whether the smdr is running and tsafs is loaded
  if not checkSMDRStatus()
    print_message(FATAL, "Ensure smdrd is running and tsafs is loaded")
    exit 1
  end

  # Will be set to true if source and destination paths are specified in files
  @multiplePathsSpecified = false
  if @opt['V'] =~ /^@/ or @opt['X'] =~ /^@/ or @opt['x'] =~ /^@/ or @opt['v'] =~ /^@/
    sourcePathFile, destPathFile = validateAndGetFileNames()
  end

  # Check if user gave path like SYS:/DIR/. In this case remove "/" after ":"
  # for nbackup
  # In case of file is specified as part of source and dest path... we need not do this
  if not @multiplePathsSpecified
    if @opt['V'] and @opt['V'].include?(":")
      @opt['V'] = @opt['V'].sub(":\/", ":")
      @opt['V'].sub!(":\\", ":")
    end

    if @opt['v'] and @opt['v'].include?(":")
      @opt['v'] = @opt['v'].sub(":\/", ":")
      @opt['v'].sub!(":\\", ":")
    end
  end

  # We need localip to get/store local authentication details
  localIp = getLocalIPAddress()
  if localIp == nil
    print_message(FATAL, "Failed to get local server's IP address details")
    exit 1
  end

  credentialDetails = {}
  print_message(DEBUG, "Calling function getCredentialDetails")
  credentialDetails = getCredentialDetails(@opt['P'], @opt['s'], localIp)
  $remoteRootPwd = ENV["SOURCE_USER_PWD"]

  # Following env variables are for ncpshell
  ENV['OES_DATA'] = credentialDetails['source-password']
  ENV['OES_DATA3'] = "\"#{credentialDetails['source-username']}\""
  ENV['SMS_LOCAL_PASSWORD'] = credentialDetails['dest-password']
  ENV['SMS_REMOTE_PASSWORD'] = credentialDetails['source-password']

  # Based on this variable we pass --created-after option to nbackup
  # TODO: May be we should add --created-after option to migfiles itself?
  @passCreatedDateFilters = true

  # If source path is Posix, then it must be Linux so no need to check once again
  # Note that we are not checking the case when the source is NetWare and user gives
  # -P option. If this is the case nbackup will throw the appropiate error
  if @opt['P']
    $isSrcLinux = true
    # Source is POSIX, so do not pass created-* filters to nbackup
    @passCreatedDateFilters = false
  else
    $isSrcLinux = is_src_linux?(@opt['s'], "\"#{credentialDetails['source-username']}\"")
    @passCreatedDateFilters = false if $isSrcLinux
  end
  if not $isSrcLinux and @opt['X']
    print_message(FATAL, "Cannot specify --source-full-path for NetWare source server")
    exit 1
  end
  # If source is NetWare check whether it is 5.1 version, if so we should not pass --created-* options
  # to nbackup
  if isSource51?(@opt['s'], "\"#{credentialDetails['source-username']}\"")
    @passCreatedDateFilters = false
    # We need to check the codepage in which tsafs is loaded in destination is same as source code page
    if @opt['usecodeset']
      same = chktsafsCodeSet(@opt['usecodeset'])
      if (same[0] == false)
        print_message(FATAL, "Source server is NetWare 5.1 and tsafs on the destination is not loaded with right codeset page. For migration to work correctly, use the --usecodeset option of tsafs. Refer to man page of tsafs for more details")
        print_message(DEBUG, "chktsafsCodeSet returned #{same[1]}")
        exit 1
      else
        print_message(DEBUG, "Destination server's tsafs is loaded with code page #{@opt['usecodeset']}")
      end
    else
        print_message(FATAL, "Source server is NetWare 5.1. Need to specify --usecodeset option. Also ensure tsafs is loaded with \"--usecodeset=<source codeset>\" if code set is non-English. See man page of tsafs for more details")
	exit 1
    end
  elsif isSource60?(@opt['s'], "\"#{credentialDetails['source-username']}\"")
	print_message(INFO, "Source server is NetWare 6.0. Make sure you load latest tsafs.nlm on the source server.")
  else
    # If it not NetWare 5.1 then we should check remote TSAFS version. If its minor version is not 
    # greater then 0 we will display warning and continue
    # Form a nbackup command which should bring us the info about remote TSAFS version
    if @opt['usecodeset']
      print_message(FATAL, "--usecodeset option can be specified only for NetWare 5.1 source server")
      exit 1
    end
    nbackupCmdForVersion = "#{NBACKUP} -U \"#{credentialDetails['source-username']}\" -R #{@opt['s']} --list-tsa-version"
    print_message(DEBUG, "nbackup command executed as #{nbackupCmdForVersion}")
    majorVersion, minorVersion = checkTSAFSVersion(nbackupCmdForVersion)
    if majorVersion == -1 or minorVersion == -1
      print_message(FATAL, "Failed to get version information of source TSAFS")
    elsif $isSrcLinux and (majorVersion < 4 or minorVersion < 1)
      print_message(WARN, "tsafs on source #{@opt['s']} is not latest. Please update it to latest")
    elsif majorVersion < 3 or minorVersion < 1
      print_message(WARN, "tsafs on source #{@opt['s']} is not latest. Please update it to latest")
    end
    print_message(DEBUG, "Major version of source tsafs is #{majorVersion}")
    print_message(DEBUG, "Minor version of source tsafs is #{minorVersion}")
  end

  # This we need for subsequent LDAP commands that verifyAndFixSrcPath function executes
  createLdapConfFile()

  # Start verifying source paths and destination paths and get them into srcPaths and destPaths arrays
  srcPaths = []
  destPaths = []
  excludePath = []
  nss = false
  if @multiplePathsSpecified
    print_message(DEBUG, "Calling function processMultiplePaths")
    srcPaths, destPaths, excludePath = processMultiplePaths(sourcePathFile, destPathFile, credentialDetails)
  else
    if @opt['P']
      #Source is a POSIX path
      srcPath = @opt['X'].chomp('/') + '/'
    elsif $isSrcLinux
      volMap = getVolumesAndMountPoints(credentialDetails, @opt)
      print_message(DEBUG, "Calling function verifyAndFixSrcPath for source path")
      srcVol, mountPoint, srcPath = verifyAndFixSrcPath(volMap, @opt)
      # To support NCP as source, nbackup should execute with the root credentials. So to 
      # identify the source volume type, the below method call has been added. This is to provide fix for bug #824782
      returnValue = verifyAndReturnSrcType(volMap, @opt, false)
      if returnValue != false
      	$isSrcNCP = returnValue
      end
      if srcPath == nil and srcVol == nil and mountPoint == nil
        print_message(FATAL, "Source path validation failed for volume #{@opt['V']}") if @opt['V']
        print_message(FATAL, "Source path validation failed for path #{@opt['X']}") if @opt['X']
        exit 1
      end
      excludePath << mountPoint.chomp('/') + '/' + "._NETWARE" if not @opt['P'] and srcPath == mountPoint
    else
      # That means source is NetWare and we have @opt['V'] to deal with
      srcVol, extraPath = @opt['V'].split(":")
      srcPath = @opt['V']
      srcPath = srcPath + ":" if not srcPath.include?(":")
      if not checkNFSLoadedOnSource(@opt['s'], srcVol, "\"#{credentialDetails['source-username']}\"")
        print_message(FATAL, "NFS namespace is not loaded for the volume #{srcVol} on source server #{@opt['s']}")
        exit 1
      end
    end

    # Add "/" at the end. We should not add if srcpath is SRCVOL: or SRCVOL
    srcPath = srcPath.chomp('/')+"/" if srcPath.index(":") != (srcPath.length-1)
    if @opt['v']
      destPath, nss = verifyAndFixDestVol @opt['v']
    else
      destPath, nss = verifyAndFixDestPath @opt['x']
    end
    if destPath == nil
      print_message(FATAL, "Destination path validation failed for path #{destPath}")
      exit 1
    end
    srcPaths << srcPath
    destPaths << destPath
    print_message(DEBUG, "Source path is #{srcPath}")
    print_message(DEBUG, "Destination path is #{destPath}")
  end
  print_message(DEBUG, "Successfully returned from function verifyAndFixSrcPath")
  deleteLdapConfFile()

  if @opt['list-extensions']

  if @multiplePathsSpecified 
      print_message(FATAL, "Option list-extension cannot be specified with multi-paths")
      exit 1
  end

  isSrc = 0;
  #Listing of target extensions and checking whether meta modified tag is present is not required as Cluster resource or NCP resources will NOT have 
  #the WSM_RESOURCE_INFO_EXTN_META_MODIFIED_TIME tag. So removed the code for listing the target extensions. This change is to provide the fix for bug#823611, 824095
  print_message(INFO, "Executing list-extensions on Source for #{srcPath}")
  isSrc = 1;

  if ($isSrcLinux)
     print_message(DEBUG, "Source path is Linux")
  else 
     print_message(DEBUG, "Source path is Netware")
  end
   print_message(INFO, "Source Extension Start ")
   listExtensions(credentialDetails, srcPath,isSrc)
   print_message(INFO, "Source Extension End ")

  exit 0
  end

  # Update excludePath array(which has ._NETWARE excluded with what's given by --exclude-path
#  # Thangaraj changes for exclude path file
#  # This is introduced to accomodate exclude path for parallel sync
#  if @opt['exclude-path-file']
##    excludePathFile = @opt['exclude-path'].slice(1, @opt['exclude-path'].length)
#    if not File.exist?(@opt['exclude-path-file']) or not File.size?(@opt['exclude-path-file'])
#      print_message(INFO, "Specified input paths file #{@opt['exclude-path-file']} does not exist or is empty")
#    else
#      File.readlines(@opt['exclude-path-file']).each do |path|
#        if path != nil and path != ""
#          path.strip!
#          excludePath << path
#        end
#      end
#    end
#  end

  if @opt['exclude-path']
    if @opt['exclude-path'].class == Array
      @opt['exclude-path'].each { |i| excludePath << "#{i}" }
    else
      excludePath << "#{@opt['exclude-path']}"
    end
  end
  # Call function setFlagsForNbackup to set and get src and dest flags for nbackup command
  srcFlags, destFlags = setFlagsForNbackup(excludePath, credentialDetails)

  # This part of code checks which source volume needs to backed up as uncompressed based on
  # destination volume property. If multiple paths are specified this is already done in function
  # processMultiplePaths
  if nss and not @multiplePathsSpecified
    #If the target volume does not have compression enabled, turn off compression at src
    print_message(DEBUG, "Destination volume type: NSS")
    print_message(DEBUG, "Calling function get_nss_vol_and_mnt_point_from_path")
    vol, mntpt = get_nss_vol_and_mnt_point_from_path(destPath)
    print_message(DEBUG, "Calling function is_compression_enabled")
    if not is_compression_enabled?(vol)
      srcFlags += " --expand-compressed-data"
    end
  elsif not @multiplePathsSpecified
    #by default uncompress the data when migrating to NCP or POSIX.
    srcFlags += " --expand-compressed-data"
  end

  # Now process given session file
  @bytesProcessed = 0
  @bytesCopied = 0
  @bytesCopiedAtLastChkPt = 0
  @pairNumberProcessed = 0
  @pairChkPt = 0
# Code changes for data collection for sync - Thangaraj
#    @dirFileCntChkPt = 0
#    @dirFileCntatLastCkhPt = 0
#    @dirTotFileCntChkPt = 0
#    @dirTotFileCntatLastCkhPt = 0
     @totDataSetsCopied = 0
#    @totDataSetsCopiedChkPt = 0
#    @dirNameatLastChkPt = ""
# Code changes for data collection for sync - end

  if @opt['c']
    checkPtStr = processSessionFile(srcPaths, destPaths, sourcePathFile, destPathFile, localIp)
  end
  @sessionProcessingDone = true

  if @opt['precheck']
    print_message(INFO, "Validation for migfiles is done successfully")
    credentialDetails.clear if credentialDetails != nil
    exit 0
  end
# Prevalidation ends here...

  date = DateTime.now
  dateStr = "#{date.mday}-#{date.mon}-#{date.year} #{date.hour}:#{date.min}:#{date.sec}"
  print_message(DEBUG, "Started on: \"#{dateStr}\"")

  # --progress and --progress-interval options should be passed to nbackup only during data copy. During trustee sync, these options should not be passed. Otherwise
  # nbackup returns total number of data sets to be copied and size of data to be copied which is not required during trustee sync. Fix for bug#823611, 824095
  progressFlags = " --progress " if @opt['progress']
  progressFlags += " --progress-interval \"#{@opt['progress-interval']}\"" if @opt['progress-interval']
  if not @multiplePathsSpecified
    cmd = "#{NBACKUP} #{srcFlags} #{progressFlags} #{checkPtStr} -R #{@opt['s']} \"#{srcPaths[0]}\" |#{NBACKUP} #{destFlags} -r \"#{srcPaths[0]} #{destPaths[0]}\""
  else
    cmd = "#{NBACKUP} #{srcFlags} #{progressFlags} #{checkPtStr} -R #{@opt['s']} -T \"#{SOURCE_PATH_FILE}\" |#{NBACKUP} #{destFlags} -r \"@#{SOURCE_DEST_PATH_FILE}\""
  end
  
  if @opt['ignore-quota-checking']
    # Before calling actual nbackup command, disable quota checking on destination server
    print_message(INFO, "Disabling quota checking on target server")
    disableQuotaChecking()
    @disabledQuotas = true
  end

  if @opt['sync']
    syncfiles(credentialDetails, srcPaths, destPaths, srcFlags, destFlags, excludePath)
  else
    callNbackup(cmd, srcPaths, destPaths)
    if @isFailover and @opt['continue-after-failover']
      begin
        print_message(ERROR, "reconnecting...")
        sleep 30 * @retryCnt
        @retryCnt = @retryCnt + 1

        # Here we need to change the check point to latest...
        checkPtStr = getCheckPointStr($lastChkPt)
        if not @multiplePathsSpecified
          cmd = "#{NBACKUP} #{srcFlags}  #{progressFlags} #{checkPtStr} -R #{@opt['s']} \"#{srcPaths[0]}\" |#{NBACKUP} #{destFlags} -r \"#{srcPaths[0]} #{destPaths[0]}\""
        else
          cmd = "#{NBACKUP} #{srcFlags}  #{progressFlags} #{checkPtStr} -R #{@opt['s']} -T \"#{SOURCE_PATH_FILE}\" |#{NBACKUP} #{destFlags} -r \"@#{SOURCE_DEST_PATH_FILE}\""
        end
        print_message(DEBUG, "nbackup cmd = #{cmd}")
        @bytesProcessed = @bytesCopiedAtLastChkPt
        doCmd(cmd, srcPaths, destPaths)
      end while @isFailover and @retryCnt <= 5
    end
  end

  # Delete files used by syncfiles
  deleteTempFiles()
  if @opt['ignore-quota-checking']
    enableQuotaChecking()
  end
  if File.exist?("nbackup_restore.warn") and File.size?("nbackup_restore.warn") != nil
    print_message(ERROR, "Restore Errors:")
    File.readlines("nbackup_restore.warn").each { |line|
      print_message(ERROR, "#{line}")
      print_message(PROGRESS, "Trustee Restore Error: #{line}")
    }
    File.delete("nbackup_restore.warn")
  end
  if File.exist?("nbackup_backup.warn") and File.size?("nbackup_backup.warn") != nil
    print_message(ERROR, "Backup Errors:")
    File.readlines("nbackup_backup.warn").each { |line|
      print_message(ERROR, "#{line}")
      print_message(PROGRESS, "Trustee Backup Error: #{line}")
    }
    File.delete("nbackup_backup.warn")
  end

  if $complete
    #update the status
    if @opt['c']
      File.open(@opt['c'], File::CREAT|File::WRONLY|File::TRUNC, 0600) { |fd|
        fd.write($session + "  status: completed")
      }
    end
    print_message(DEBUG, "migfiles completed successfully at #{`date +%d-%m-%y\\ %H:%M:%S`}")
  else
    print_message(FATAL, "nbackup command failed to complete")
    exit 1
  end
end

begin
  $session = ""
  trap("INT"){
    print_message(INFO, "Interrupted by user")
    $interrupted = true
    if @opt['c'] and @sessionProcessingDone
      #session file was specified!
      writeSessionFile
# Code changes for data collection for sync - Thangaraj
#        File.delete("#{@syncFileNameTmp}"+"prev"+".#{@prevPair}") if File.exist?("#{@syncFileNameTmp}"+"prev"+".#{@prevPair}")
#        File.delete("#{@syncFileNameTmp}"+"prev"+".#{@pairNumberProcessed}") if File.exist?("#{@syncFileNameTmp}"+"prev"+".#{@pairNumberProcessed}")
#        File.delete("#{@syncFileNameTmp}"+".#{@pairNumberProcessed}") if File.exist?("#{@syncFileNameTmp}"+".#{@pairNumberProcessed}")
# Code changes for data collection for sync - end
    end
    if @opt['W']
      #cifs mount needs this sleep for umount to work
      $stderr.puts "Information: migfiles: unmounting directory #{MNT_DIR}..."
      sleep 4
    end
    deleteLdapConfFile()
    deleteTempFiles()

    killAllnbackups("sh -c #{$nbackup_command}", false)
    exit 1
  }
  $interrupted = false
  main #if __FILE__ == $0
  rescue MigrationException => e
    if @disabledQuotas
      enableQuotaChecking()
    end
    if e.to_s != nil and e.to_s != "" and e.to_s.include?("connection to server failed")
      print_message(FATAL, "Authentication to source server #{@opt['s']} failed")
    else
      print_message(FATAL, "#{e.to_s}")
    end
    if @opt['continue-after-failover']
      print_message(FATAL, "Cluster resouce might have failed over before start of data migration. Try restarting")
    end
    exit e.code
  rescue SystemCallError => failed
    if @disabledQuotas
      enableQuotaChecking()
    end
    print_message(FATAL, "SystemCallError, #{failed.message}")
    exit failed.errno
  rescue MigSyntaxError => e
    if @disabledQuotas
      enableQuotaChecking()
    end
    print_message(FATAL, "MigSyntaxError," + e.to_s)
    printUsage($argdef)
    exit e.code
  rescue LdapAuthError => e
    if @disabledQuotas
      enableQuotaChecking()
    end
    print_message(FATAL,"LdapAuthError, #{e.message}")
    exit e.code
  rescue 
    if @disabledQuotas
      enableQuotaChecking()
    end
    print_message(FATAL, "Caught exception: #{$!.message}\n Backtrace:\n #{$!.backtrace.join("\n")}") if !$interrupted
    exit 1
  ensure
    deleteLdapConfFile()
    deleteTempFiles()
    if @disabledQuotas
      enableQuotaChecking()
    end
#    Migration.ncp_umount if $ncpMounted
    $log.close if @opt['debug']
end
