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

# Copyright 2025 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 'logger'

include Migration

def audit_posixrights r
  result = `l '#{r['dst-file']}' 2>&1`
  if result.include? "no such file or directory" 
    $stdout.puts "migrights:\n file: \"#{r['dst-file']}\"\n rights: #{r['rights']}\n owner: #{r['owner']}\n state: failed" 
   elsif @opt['i']
    $stdout.puts "migrights:\n file: \"#{r['dst-file']}\"\n rights:#{r['rights']}\n owner: #{r['owner']}\n state: passed"
  end
end

def test_posixrights r
  f_t = false
  o_t = false

  if File.exist? r['dst-file']
    f_t = true
  end

  if r['owner-type'] == "group"
    if check_posix_group_exist? r['owner'] 
      o_t = true
    end
    o_t = true
  else
    if check_posix_user_exist? r['owner'] 
      o_t = true
    end
  end
  if o_t == false or f_t == false or @opt['i']
    $stdout.puts "migrights:\n file: \"#{r['dst-file']}\"\n file-exists: #{f_t}\n owner: #{r['owner']}\n owner-exists: #{o_t}" 
  end
end

def mig_posixrights r
  result1 = ""
  result2 = ""
  if r['owner'] != nil
    if r['owner-type'] == "group"
      result1 = `chown -R ':#{r['owner']}' '#{r['dst-file']}' 2>&1`
    else
      result1 = `chown -R '#{r['owner']}' '#{r['dst-file']}' 2>&1`
    end
  end
  result2 = `chmod  '#{r['attrs']}' '#{r['dst-file']}' 2>&1` if r['attrs'] != nil
  if result1 == "" and result2 == ""
    $stdout.puts "migrights:\n file: #{r['dst-file']}:\n rights: #{r['rights']}\n owner: #{r['owner']}" if @opt['i'] 
  else
    print_message(ERROR, "migrights:\n file: #{r['dst-file']}:\n rights: #{r['rights']}\n owner: #{r['owner']}->failed")
  end
end

def audit_ncprights r
  if r['owner']
    # Escaping is not required for ncpcon command arguments from OES11SP2 and later releases as ncpcon itself handles special characters. Refer bugs #529215, #695115 and #820416.
    # So removed the escaping double quote code from the below command to provide the fix for the bug #847195.
    result = `ncpcon rights view \"#{r['dst-file']}\" 2>&1`
    if result.include? "FAILED" 
      $stdout.puts "migrights:\n file: \"#{r['dst-file']}\"\n rights: #{r['rights']}\n owner: #{r['owner']}\n state: failed"
    elsif @opt['i']
      $stdout.puts "migrights:\n file: \"#{r['dst-file']}\"\n rights: #{r['rights']}\n owner: #{r['owner']}\n state: passed"
    end
  end
end

def test_ncprights r
  #as the path is in NSS format ie VOL:path we are using ncpcon rights view
  #to figure out whether the path exists or not.
  # Escaping is not required for ncpcon command arguments from OES11SP2 and later releases as ncpcon itself handles special characters. Refer bugs #529215, #695115 and #820416.
  # So removed the escaping double quote code from the below command to provide the fix for the bug #847195.
  result = `ncpcon rights view \"#{r['dst-file']}\" 2>&1`
  if result.include?("FAILED")
    $stdout.puts "migrights:\n file: \"#{r['dst-file']}\"\n file-exists: false \n"
  elsif @opt['i']
    $stdout.puts "migrights:\n file: \"#{r['dst-file']}\"\n file-exists: true \n"
  end

=begin
  if r['owner']
    if not ldap_exist? r['owner']
      if not result.include?("FAILED") and not @opt['i']
        $stdout.puts "migrights:\n file: \"#{r['dst-file']}\"\n file-exists: true \n"
      end
      $stdout.puts " owner: #{r['owner']}\n owner-exists: false"
    elsif @opt['i']
      $stdout.puts " owner: #{r['owner']}\n owner-exists: true"
    end
  end
=end
end

def mig_ncprights r
   if r['src-type']
     dotuser = getDotUser r['owner'] if r['owner']
   else
     dotuser = getDotUser r['trustee'] if r['trustee']
   end
   if dotuser == nil and (r['trustee'] or r['src-type'])
     print_message(DEBUG, "getDotUser failed for trustee #{r['trustee']}")
     return 
   end

  info, errors = [], []

  if r.has_key?('rights') and not r['rights'].nil? and not dotuser.nil?
    fileName = "#{r['dst-file']}"
    # In case of file name containing any special character like space, ' we need to pass file name in 
    # following format to ncpcon to work fine.
    # ncpcon rights add "\"file_name\"" username rights
    #fileName.gsub!("\$", "\\\\\\\$") # If $ char is supported ...then uncomment this
    # Escaping is not required for ncpcon command arguments from OES11SP2 and later releases as ncpcon itself handles special characters. Refer bugs #529215, #695115 and #820416.
    # So removed the escaping code from the below command to provide the fix for the bug #847195. 
    result = `ncpcon rights add \"#{fileName}\" \"#{dotuser}\" #{r['rights']} 2>&1`
    if result.include? "OK"
      info << " trustee: #{r['trustee']}" 
      info << " rights: #{r['rights']}"
    else
      print_message(DEBUG, "ncpcon command 'ncpcon rights add \"#{fileName}\" \"#{dotuser}\" #{r['rights']}' failed. Got #{result}")
      errors << " trustee: #{r['trustee']}"
      errors << " rights: #{r['rights']} -> failed"
    end
  end

  if r['type'] == "nss" and r['dir-quota']  
    begin
      NSS.instance.set_file_quota r['dst-file'], r['dir-quota'] 
      info << " dir-quota: #{r['dir-quota']}"
    rescue 
      print_message(DEBUG, "Unable to set dir-quota for dir #{r['dst-file']}. Got #{$!.message}")
      errors << " dir-quota: #{r['dir-quota']} -> failed" 
    end
  end

  if r['type'] == "nss" and r['user-quota']
    begin
      #the volume name is always has ":" at the end and this is chopped off
      NSS.instance.set_user_quota r['dst-file'].chop, dotuser, r['user-quota']
      info << " trustee: #{r['trustee']}"
      info << " user-quota: #{r['user-quota']}"
    rescue 
      print_message(DEBUG, "Unable to set user-quota for user #{dotuser}. Got #{$!.message}")
      errors << " trustee: #{r['trustee']}"
      errors << " user-quota: #{r['user-quota']} -> failed"
    end
  end

  if r['type'] == "nss" and r['attrs']!= nil and r['attrs'] != 0
    begin
      NSS.instance.set_attrs r['dst-file'], r['attrs']
      info << " attrs: #{r['attrs']}"
    rescue 
      print_message(DEBUG, "Unable to set attrs for dataset #{r['dst-file']}. Got #{$!.message}")
      errors << " attrs: #{r['attrs']} -> failed"
    end
  end
  if r['type'] == "nss" and (r['owner'] or r['modifier'])
    begin
      NSS.instance.set_fileIds r['dst-file'], r['owner'], r['modifier']
      info << " owner: #{r['owner']}" if r['owner']
      info << " modifier: #{r['modifier']}" if r['modifier']
    rescue 
      print_message(DEBUG, "Unable to set ids #{r['owner']}, #{r['modifier']} for dataset #{r['dst-file']}. Got #{$!.message}")
      errors << " owner: #{r['owner']} -> failed" if r['owner']
      errors << " modifier: #{r['modifier']} -> failed" if r['modifier']
    end
  end

  out = errors.empty? ? $stdout : $stderr

  if @opt['i'] or not errors.empty?
    out.puts "-file: #{r['dst-file']}"
  end  
  if @opt['i'] and not info.empty?
    info.each { |x| out.puts x }
  end
  unless errors.empty?
    errors.each { |x| out.puts x }
  end
end

def main
  $argdef = [
    ['i', 'verbose', "displays verbose information", ParseArgBool],
    ['A', 'audit', "audit results of the file rights migration", ParseArgBool],
    ['t', 'test', "performs a dry run on the rights migration operation", ParseArgBool],
    ['p', 'posix', "indicates destination path is posix", ParseArgBool],
    [nil, "inputfile", "the input file (usually produced by running 'maprights').  If not given on the commandline, will read from stdin", ParseArgOrd],
    ['c', 'session-file', "stores the migration progress, this file can be used to continue the migration", ParseArgText],
    [nil, 'progress', "display the progress in terms of number of objects processed", ParseArgBool],
    [nil, 'progress-interval', "time interval for displaying progress", ParseArgText],
    [nil, "precheck", "checks whether system meets all pre-requisite to start migrights", ParseArgBool],
    [nil, 'debug', "generate debug log", ParseArgBool],
    ParseArgHelp,
    ParseArgUsage
  ]

  @init = true
  migrightsCommand = "#{$0}"
  debugOpt = false
  $*.each do |arg|
    if arg.strip == "--debug"
      debugOpt = true
    end
    migrightsCommand += " #{arg}"
  end
  initLog(debugOpt, "migrights")
  print_message(DEBUG, "migrights command started at #{`date +%d-%m-%y\\ %H:%M:%S`}")
  print_message(DEBUG, "migrights command executed as: #{migrightsCommand}")

  @opt = {}
  (@opt, extraargs) = parseargs(ARGV, $argdef)

  if @opt['inputfile'] 
    if not File.exist?(@opt['inputfile']) or not File.size?(@opt['inputfile'])
      print_message(FATAL, "Specified input file #{@opt['inputfile']} does not exist or it is empty")
      exit 1
    end
  end
  filemap = @opt['inputfile']
  if filemap == nil
    filemap = $stdin
    if filemap.isatty
      print_message(FATAL, "Must specify input file or provide input to stdin")
      printUsage($argdef)
      exit 1
    end
  end

  if @opt['t'] and @opt['A']
    print_message(FATAL, "Cannot specify --test and --audit  options together")
    printUsage($argdef)
    exit 1
  end

  if @opt['progress-interval'] and not @opt['progress']
    print_message(FATAL, "Cannot specify --progress-interval without --progress")
    exit 1
  end
  if @opt['progress-interval'] and @opt['progress-interval'].to_i <= 0
    print_message(FATAL, "value for --progress-interval should be greater than 0")
    exit 1
  end
  if @opt['p']
    posix = true
  end

  if filemap.class == String
    input = File.open filemap,"r" 
  else
    input = filemap
  end

  objNumber = 0
  @jobDone = false
  if @opt['c']
    $session = "---\n"
    $session << "  started-on: #{`date`}"
    $session << "  utility: migrights\n"
    if File.exist?(@opt['c']) and File.size?(@opt['c']) != 0
      sessionInput = File.open(@opt['c'], "r")
      sessionInfo = YAML.load(sessionInput)
      if sessionInfo['utility'] != "migrights"
        print_message(FATAL, "Invalid session file #{@opt['c']} specified. Please check the file")
        exit 1
      end
      if sessionInfo['status'] =~ /Completed/
        print_message(WARN, "Session file #{@opt['c']} shows maptrustees's status as 'Completed'")
        exit 0
      elsif sessionInfo['status'] =~ /Stopped/
        objNumber = sessionInfo['stopped-at'].to_i
      else
        print_message(FATAL, "Session file is corrupted")
        exit 1
      end
        sessionInput.close
    end
  end
  if @opt['precheck']
    $stderr.puts "Information: Validation for migrights done successfully"
    exit 0
  end
  if input != $stdin
    Psych.load_stream(input) do |doc|
      if doc != nil
        doc.each do |obj|
          if doc.class == FalseClass
            print_message(FATAL, "Input file not in correct format")
            exit 1
          end
          break
        end
        break
      end
    end
    # Set the input pointer to 0
    input.seek(0, IO::SEEK_SET) if input != $stdin
  end
  if @opt['progress'] and input != $stdin
    print_message(INFO, "precomputing for displaying progress")
     # Find out the total Number of objects has to be processed.
    @totObjs = getTotalObjs(@opt['inputfile'])
    print_message(INFO, "Total number of objects to be processed is #{@totObjs}");
    if @opt['progress-interval']
      @progressInterval = @opt['progress-interval'].to_i
    else
      @progressInterval = 30
    end
    startTimer(@progressInterval)
  end
  @init = false
  @cnt = 0
  puts "---"
  Psych.load_stream(input) do |doc|
    if doc != nil
      doc.each do |obj|
        if @cnt >= objNumber
          if posix
            if @opt['A']
              audit_posixrights obj
            elsif @opt['t']
              test_posixrights obj
            else
              mig_posixrights obj
            end
          else

            if @opt['A']
              audit_ncprights obj
            elsif @opt['t']
              test_ncprights obj
            else
              mig_ncprights obj
            end
          end
          if @cnt != 0 and @cnt % 20 == 0
            writeToSessionFile(@jobDone, @cnt) if @opt['c']
          end
          if @cnt != 0 and @cnt % NO_OF_OBJECTS_PER_RECORD == 0
            puts "---"
          end
        end
        @cnt += 1
      end
    end
  end
  input.close if input.class == File

  if @opt['c']
    @jobDone = true
    writeToSessionFile(@jobDone, @cnt)
  end
  print_message(PROGRESS, "Assigned rights for #{@totObjs} files of #{@totObjs}") if @opt['progress']
  print_message(DEBUG, "migrights completed successfully at #{`date +%d-%m-%y\\ %H:%M:%S`}")
end

begin
  trap("ALRM"){
    print_message(PROGRESS, "Assigned rights for #{@cnt} files of #{@totObjs}")
    writeToSessionFile(@jobDone, @cnt) if @opt['c']
    startTimer(@progressInterval) if @opt['progress']
  }

  main #if __FILE__ == $0
rescue SystemCallError => failed  
  print_message(FATAL, "SystemCallError, " + failed.message)
  exit failed.errno
rescue MigrationException => e 
  print_message(FATAL, "MigrationException, " + e.to_s)
  exit e.code 
rescue MigSyntaxError => e
  print_message(FATAL, "MigSyntaxError, " + e.to_s)
  printUsage($argdef)
  exit e.code
rescue Interrupt
  print_message(DEBUG, "Interrupted by user")
  writeToSessionFile(@jobDone, @cnt) if @opt['c'] and @init == false
  exit 0
rescue 
  print_message(FATAL, "Caught exception: #{$!.message}\n Backtrace:\n #{$!.backtrace.join("\n")}")
  exit 1
ensure
  $log.close if @opt['debug']
end
