#!/usr/bin/env python3
# wanlin.wang
# 2024/12/14, version 1.0
# 2024/12/21, version 1.1, add support for Python 3.6.8 and other improvements


import os
import subprocess
import shlex

# --- POSIX => NFSv4 基本权限映射表 ---
# 针对 'r', 'w', 'x' 做粗粒度映射到 NFSv4 权限子集。
# 这里示例，借助alias来简化设置。规则是
#    - file/dir: r -> R    , w -> W       ,  x -> X
#        - file: r -> rntcy, w -> watTNcCy,  x -> xtcy
#        - dir:  r -> rntcy, w -> waDtTNcCy, x -> xtcy
PERM_MAP = {
    'r': 'R',
    'w': 'W',
    'x': 'X',
}

def convert_posix_perm_to_nfs4(posix_perm):
    """
    将类似 'r-x' 的 POSIX 权限字符串映射为简单的 NFSv4 权限字符串。
    """
    perms = []
    if 'r' in posix_perm:
        perms.append(PERM_MAP['r'])
    if 'w' in posix_perm:
        perms.append(PERM_MAP['w'])
    if 'x' in posix_perm:
        perms.append(PERM_MAP['x'])
    merged = "".join(perms)  # 例如 ['rtncy', 'x'] -> 'rtncyx'
    # 去重保持顺序
    result = "".join(sorted(set(merged), key=merged.index))
    return result

def apply_mask(posix_perm, mask_perm):
    """
    把某个条目的 nominal 权限与 mask 做交集，得到实际生效权限 (effective perms)。
    mask_perm & posix_perm 都是三字符形式，如 'rwx', 'r-x', '---'。
    逻辑：如果 mask 里无 'w'，则最终也无 'w'。
    """
    effective = ''
    for i, c in enumerate(['r','w','x']):
        if c in posix_perm and c in mask_perm:
            effective += c
        else:
            effective += '-'
    return effective

def parse_getfacl_output(acl_output):
    result = {
        'owner': None,
        'group': None,
        'mask': None,           # 普通mask
        'default_mask': None,   # 新增: default:mask
        'acl_entries': [],
        'default_acl_entries': [],
    }

    lines = acl_output.strip().split('\n')
    for raw_line in lines:
        line = raw_line.strip()
        # 去掉行内注释
        if '#' in line:
            line = line.split('#', 1)[0].strip()
        if not line:
            continue

        parts = line.split(':')
        if line.startswith('default:'):
            entry_type = parts[1]  # user / group / mask / other
            if entry_type == 'mask':
                # default:mask::rwx
                result['default_mask'] = parts[-1]  # 'rwx'
            else:
                name = ''
                perm = parts[-1]
                if len(parts) == 4:
                    name = parts[2]
                result['default_acl_entries'].append({
                    'type': entry_type,
                    'name': name,
                    'perm': perm
                })
        else:
            entry_type = parts[0]
            if entry_type == 'mask':
                result['mask'] = parts[-1]
            else:
                name = ''
                perm = parts[-1]
                if entry_type in ('user','group') and len(parts) == 3:
                    name = parts[1]
                result['acl_entries'].append({
                    'type': entry_type,
                    'name': name,
                    'perm': perm
                })

    return result

# def build_nfs4_acl_cmd(acl_info, path="/dummy", domain='localdomain'):
def build_nfs4_ace(acl_info, path="/dummy", domain='localdomain'):
    ace_list = []
    # 普通 mask
    normal_mask = acl_info['mask'] if acl_info['mask'] else 'rwx'
    # default mask
    default_mask = acl_info.get('default_mask', None)
    if not default_mask:
        default_mask = 'rwx'

    # 1) OWNER@
    owner_acl = next((e for e in acl_info['acl_entries']
                      if e['type'] == 'user' and e['name'] == ''), None)
    if owner_acl:
        # owner 不受普通mask约束
        eff = apply_mask(owner_acl['perm'], 'rwx')
    else:
        eff = 'rwx'
    ace_list.append(f"A::OWNER@:{convert_posix_perm_to_nfs4(eff)}")

    # 2) user:xxx 或 group:xxx (普通条目)
    for entry in acl_info['acl_entries']:
        if entry['type'] == 'user' and entry['name']:
            eff = apply_mask(entry['perm'], normal_mask)
            nfs4_perm = convert_posix_perm_to_nfs4(eff)
            if nfs4_perm:
                ace_list.append(f"A::{entry['name']}@{domain}:{nfs4_perm}")
        if entry['type'] == 'group' and entry['name']:
            eff = apply_mask(entry['perm'], normal_mask)
            nfs4_perm = convert_posix_perm_to_nfs4(eff)
            if nfs4_perm:
                ace_list.append(f"A:g:{entry['name']}@{domain}:{nfs4_perm}")
            else:
                ace_list.append(f"D:g:{entry['name']}@{domain}:RWXdo")

    # 3) group:: (空组)
    group_acl = next((e for e in acl_info['acl_entries']
                      if e['type'] == 'group' and e['name'] == ''), None)
    if group_acl:
        eff = apply_mask(group_acl['perm'], normal_mask)
        g_perm = convert_posix_perm_to_nfs4(eff)
        if g_perm:
            ace_list.append(f"A:g:GROUP@:{g_perm}")
        else:
            ace_list.append(f"D:g:GROUP@:RWXdo")

    # 4) other::
    other_acl = next((e for e in acl_info['acl_entries']
                      if e['type'] == 'other'), None)
    if other_acl:
        eff = apply_mask(other_acl['perm'], normal_mask)
        o_perm = convert_posix_perm_to_nfs4(eff)
        if o_perm:
            ace_list.append(f"A::EVERYONE@:{o_perm}")
        else:
            ace_list.append(f"D::EVERYONE@:RWXdo")

    # 5) default: ACL (带继承)
    for def_entry in acl_info['default_acl_entries']:
        entry_type = def_entry['type']
        nominal = def_entry['perm']
        # 应用 default_mask
        eff = apply_mask(nominal, default_mask)
        nfs4_perm = convert_posix_perm_to_nfs4(eff)
        if not nfs4_perm:
            continue
        if entry_type == 'user':
            if def_entry['name'] == '':
                ace_list.append(f"A:fd:OWNER@:{nfs4_perm}")
            else:
                ace_list.append(f"A:fd:{def_entry['name']}@{domain}:{nfs4_perm}")
        elif entry_type == 'group':
            if def_entry['name'] == '':
                ace_list.append(f"A:fdg:GROUP@:{nfs4_perm}")
            else:
                ace_list.append(f"A:fdg:{def_entry['name']}@{domain}:{nfs4_perm}")
        elif entry_type == 'other':
            pass
    return ace_list

def convert_acl_for_directory(root_dir, new_root_dir, domain='localdomain', max_depth=3, nfs4_cmd_file='nfs4_acl_cmds.txt'):
    """
    遍历 root_dir 下所有文件和目录，
    读取POSIX ACL并转换为NFSv4 ACL (包含 default: 继承处理, mask限制)
    """
    for dirpath, dirnames, filenames in os.walk(root_dir):
        # 计算当前目录的深度
        depth = dirpath[len(root_dir):].count(os.sep)
        if depth >= max_depth:
            # 如果当前目录的深度超过了最大深度，则不再递归遍历子目录。适用于大型目录结构，只在最上的几层控制。
            dirnames[:] = []

        all_paths = [dirpath] + [os.path.join(dirpath, f) for f in filenames]
        for p in all_paths:
            try:
                # 获取 POSIX ACL。这里调用subprocess时，用了universal_newlines=True，以便输出是str而不是bytes。text=True也可以用于高于3.7的Python版本。
                completed_proc = subprocess.run(["getfacl", p],
                                                check=True,
                                                stdout=subprocess.PIPE,
                                                stderr=subprocess.PIPE,
                                                universal_newlines=True)
                acl_output = completed_proc.stdout
            except subprocess.CalledProcessError as e:
                print(f"Warning: getfacl failed on {p}, error: {e}")
                continue

            acl_info = parse_getfacl_output(acl_output)
            ace_list = build_nfs4_ace(acl_info, p, domain=domain)
            if ace_list:
                new_dir = p.replace(root_dir, new_root_dir)
                nfs4_cmd = f"nfs4_setfacl -s \"{' '.join(shlex.quote(x) for x in ace_list)}\" {new_dir}"
                print(f"\n# Got POSIX ACL from {p}, setting NFSv4 ACL for {new_dir} with command:")
                print(nfs4_cmd)
                with open(nfs4_cmd_file, 'a') as f:
                    f.write(f"\n# Directory: {dirpath}\n")
                    f.write(nfs4_cmd)
                    f.write("\n")

def main():
    import sys
    if len(sys.argv) < 3:
        print(f"Usage: {sys.argv[0]} <directory> <new_directory> [domain]")
        sys.exit(1)

    root_dir = sys.argv[1]
    new_root_dir = sys.argv[2]
    domain = sys.argv[3] if len(sys.argv) > 3 else 'localdomain'
    if not os.path.isdir(root_dir):
        print(f"Error: {root_dir} is not a valid directory.")
        sys.exit(1)

    convert_acl_for_directory(root_dir, new_root_dir, domain)

if __name__ == "__main__":
    main()
