标准化 AIX 上的用户 UID 和 GID 编号

如果您有不止一台 AIX 服务器使用本地用户账户,那么这些不同系统中的用户 ID (UID) 和组 ID (GID) 编号可能会不一致。本文将介绍如何标准化您的所有 AIX 系统中的 UID 和 GID 编号,使它们保持一致。

Brian K. Smith, 系统管理员, Intermountain Healthcare

Brian Smith 是犹他州盐湖城 Intermountain Healthcare 公司的一名 UNIX 系统管理员。



2011 年 12 月 12 日

简介

在拥有多个 AIX 系统的环境中,不同系统中的 UID (User ID) 和 GID (Group ID) 编号不一致是常见现象。有几个原因会造成此问题,这是一个值得解决的问题。本文讲解了 AIX 上的 UID 和 GID 编号究竟是什么,以及前面提到的多个服务器上的编号不一致会造成的几个具体问题。本文还将介绍如何手动修改 AIX 上的 UID 和 GID 编号使其一致,并且创建一个大规模标准化 UID 和 GID 的脚本。


AIX 中的 UID 和 GID 编号概述

在例如 AIX 这样的 UNIX 系统上,操作系统用 UID 编号表示用户,用 GID 编号表示组。您可以很容易地通过运行 id 命令来查看 AIX 帐户上的 UID 和 GID 编号。

清单 1. id 输出
$ id
uid=404(brian) gid=402(testgroup) groups=1(staff)

在以上的 清单 1 中,您会看到 brian 账户的 UID 编号是 404。用户有一个 GID 为 402 的主要组(testgroup),而且是 GID 1(staff)的成员。

存储在磁盘上的每个文件都有一个所有者和一个组,尽管用户和组的实际名称不存储在磁盘上。存储的是表示用户和组的 UID 和 GID 编号。您可以通过运行文件上的 istat 命令来查看。

清单 2. istat 输出
$ istat testfile
Inode 131073 on device 10/5     File
Protection: rwxr-----
Owner:404(brian)              Group:402(testgroup)
Link count:   1         Length 291 bytes

Last updated:   Tue Apr  8 09:04:17 MDT 2008
Last modified:Wed Feb  7 13:22:36 MST 2007
Last accessed:Thu Sep 30 12:36:59 MDT 2010

在以上的 清单2 中,您会看到文件的所有者是 UID 404 (brian),组是 402 (testgroup)。

系统中的其他条目,比如包含正在运行进程的进程表,它通过追踪 UID 和 GID 编号而不是实际的用户名和组名来追踪谁拥有这些进程。


问题

如果您有多个服务器,那么这多个服务器之间的 UID 和 GID 编号可能不一致。默认情况下在 AIX 中创建用户或组时,它只是分配下一个可用的 UID 或 GID 编号。如果环境中有一个或多个服务器,服务器之间的 UID 和 GID 会很快变得不一致。这意味着 “brian” 用户可能在 Server1 上的 UID 为 404,Server2 上 的 UID 为 406,Server3 上的 UID 为 402。

有几个原因会造成此问题。将所有服务器上的 UID 和 GID 编号都一致地标准化的最大原因是您可以迁移到一个集中认证系统,如 LDAP。LDAP 等集中认证系统通常要求通过 LDAP 连接的所有服务器上启用 LDAP 的用户和组具有一致的 UID 和 GID。

即使您不想使用集中认证,如 LDAP,您还是会遇到 UID 和 GID 不一致的问题。例如,如果有一个 SAN LUN 映射到 ServerA。可能有上千个文件存储在 LUN 上。存储在 LUN 上的每个文件都有一个文件所有者和一个组,保存为 UID 和 GID 编号。因此如果使用此 LUN,断开它与 ServerA 的映射,并且让它映射到 ServerB,那么如果 ServerA 与 ServerB 之间 的 UID 和 GID 不一致,就会造成问题。在此场景中,可能会有几个问题。如果用户 brian 在 ServerA 上的 UID 是 404,用户 bob 在 ServerB 上的 UID 是 404,在转移 LUN 之后,用户 bob 就会拥有用户 brian 的所有文件。如果 ServerB 上没有 UID 404,那么 ServerB 上的文件就没有所有者,那么在运行 ls –al 命令时只能看到 “404” 作为所有者。

当在服务器之间导出 NTF 共享时,服务器之间 UID/GID 编号不一致也会造成问题。


手动更改 GID 和 UID 编号的步骤

本例中,您将更改用户(brian)的 UID 和组(testgroup)的 GID。“brian” 用户原有的 UID 是 404,新的 UID 是 3504。“testgroup” 组原有的 GID 是 402,新的 GID 是 5001。更改 GID 或 UID 编号分为多个步骤。

步骤 1. 停止应用程序并让用户注销

在更改 GID 或 UID 编号之前,停止所有正在运行的应用程序并且让所有用户从服务器注销很重要。进程表会根据 UID 和 GID 编号跟踪正在运行的进程。因此,如果进程运行时更改这些编号,会产生无法预料的结果。还有,在下一步修复文件所有权之前,用户会暂时无法访问文件。

步骤 2. 找到将组作为主要组的用户

系统中的每个用户都有一个在 /etc/passwd 文件中定义的主要组。在步骤 3 中更改 GID 编号时,AIX 会打印出它未用新的 GID 更新 /etc/passwd 文件的警告消息。因此,在更改 GID 之前,首先要获取所有将您正更改的组作为主要组的用户清单。可能没有一个用户将其作为主要组,或者可能有多个用户。要将其找出,运行以下清单 3 所示的命令:

清单 3. lsuser 输出
# lsuser -a pgrp ALL | grep pgrp=testgroup
brian pgrp=testgroup

此命令显示系统中有一个用户(brian)将 testgroup 作为主要组。记下此命令显示的用户,因为您需要在以后的步骤中运行命令来修复它们。

步骤 3. 更改 GID 编号

使用 chgroup 命令将 testgroup 的 GID 改成 5001(原先 GID 是 402)。

清单 4. chgroup 输出
# chgroup id=5001 testgroup
3004-719 Warning:/usr/bin/chgroup does not update /etc/passwd with the new gid.

chgroup 打印出警告消息让您知道它未用新的 GID 更新 /etc/passwd。此警告适用于所有将组作为主要组的用户。您在步骤 2 中收集了这些用户的列表,将在下一步中修复。

步骤 4. 修复用户主要组

对于步骤 2 中的所有表,运行以下的命令修复其主要组。注意,可能没有一个用户将其作为主要组,或者可能有多个用户。

清单 5. chuser 命令更新主要组
# chuser pgrp=testgroup brian

chuser 命令会用 testgroup 的新 GID 编号更新 /etc/passwd 文件中的 brian 用户。

步骤 5. 更改 UID 编号

使用 chuser 命令将用户 brian 的 UID 改成 3504 (原先是 404)。

清单 6. chuser 命令更改 UID
# chuser id=3504 brian

此时,系统上 brian 的 UID 和 testgroup 的 GID 都已改变。您可以运行 id brian 命令来查看新的 UID 和 GID。

清单 7. id brian 输出
# id brian
uid=3504(brian) gid=5001(testgroup) groups=1(staff)

此时还未完成。如果在用户主目录运行 ls –al 命令,您很快会看到问题。

清单 8. 用户主目录的 ls 输出
# ls -al /home/brian

drwxr-xr-x  5  404    402    4096 2009-04-08 09:12 .
drwxr-xr-x 10  bin    bin     256 2007-03-01 15:06 ..
-rw-r--r--  1  404    402      10 2007-02-07 13:22 .kshrc
-rwxr-----  1  404    402     291 2007-02-07 13:22 .profile
-rw-------  1  404    402     438 2010-11-10 11:40 .sh_history

如您所见,用户和组都正常显示,您只看到之前显示的 UID 和 GID 编号(404 和 402)。如果用户 brian 登录到系统,他将不会是这些文件的所有者。这是因为系统在每个文件中存储的是所有者和组的 UID 和 GID 编号,而不是用户或组名称。下一步将会修复此问题。

步骤 6. 修复系统中所有文件的用户和组所有权

在这一步中,将会修复系统中所有文件的用户和组所有权。这是通过运行两个 find 命令来完成的,它们会查找具有之前的 UID 和 GID 的所有文件。对于每个符合以上标准之一的文件,都会用新的用户和组所有权来更新。

清单 9. find 命令修复所有权
# find / -group 402 -exec chgrp -h testgroup {} \;
# find / -user 404 -exec chown -h brian {} \;

这些命令一旦完成,系统上所有文件的 testgroup 组和 brian 用户的用户和组所有权都会被更正过来。如果在 brain 用户的主目录运行 ls –al 命令,您可以得到确认。


将此过程自动化

之前的步骤需要花费大量时间来完成,而且只能更改一个用户和组的 UID 和 GID。如果您的环境中有多个 AIX 服务器,而且每个服务器都有多个用户和组,那么显然手动更改所有 UID 和 GID 是不切实际的。

我写了一个 Perl 脚本来自动化此过程。提供给脚本两个输入文件:包含已更新的 UID 信息的文件,以及包含已更新的 GID 信息的文件。

在 UID 文件中,每行列出两列信息。第一列是要设置的新 UID,第二列是帐户名称。GID 文件也类似。第一列是要设置的新 GID,第二列是组名称。

以下的清单 10 显示了文件内容。

清单 10. UID 和 GID 文件的内容
# cat uid.txt
3500    megan
3501    todd
3502    app_user
#
# cat gid.txt
5000    app_group

如果脚本在 UID 或 GID 文件中找到的用户或组在系统中不存在,那就跳过该行。这使得创建所有系统中的所有用户和组的列表变得非常容易,并且可以创建一个单独的 UID 和 GID 文件,用来标准化所有系统上的 UID 和 GID,即使每个系统上都没有相同的用户或组。还有,如果脚本运行时检测到系统上用户或组的当前的 UID 或 GID 与输入文件中要求的 UID/GID 一样,那么它会跳过这些行。这样就可以在即使已具有已经标准化的 UID 和 GID 的用户帐户的系统上使用输入文件的主 UID/GID 列表来运行脚本。

当脚本运行时,它不会改变系统上所有内容;它只会从系统中收集用户和组信息并在屏幕上显示标准化 UID 和 GID 编号所需的命令。

要运行此脚本,使用类似下面清单 11 中的命令。

清单 11. 运行 fix_gid_uid.pl 脚本
# ./fix_gid_uid.pl --uidfile uid.txt --gidfile gid.txt
### Commands to update Groups ###

chgroup id=5000 app_group
chuser pgrp=app_group todd
chuser pgrp=app_group megan
chuser pgrp=app_group app_user
chuser pgrp=app_group app_2
chuser pgrp=app_group app_3
chuser pgrp=app_group app_4
find / -group 14 -exec chgrp -h app_group {} \;

### Commands to update Users ###

chuser id=3500 megan
find / -user 406 -exec chown -h megan {} \;
chuser id=3501 todd 
find / -user 402 -exec chown -h todd {} \;
chuser id=3502 app_user
find / -user 409 -exec chown -h app_user {} \;

脚本在屏幕上显示出按照 uid.txt 和 gid.txt 输入文件中列出的新 UID 和 GID 标准化该系统上的 UID 和 GID 所需的所有命令。

请注意,输出结果显示,有六个用户需要在 app_group 组的 GID 改变后改变其主要组。这包含了 uid.txt 文件中未包含的用户。原因是一旦 GID 组改变,所有将该组作为主要组的用户都需要将主要组更新,即使这些用户未改变 UID。

一旦运行脚本并确认输出结果正确,运行命令就简单了。为此,再次运行脚本,并将输出结果重定向到文件中。让输出文件可执行,然后运行。

清单 12. 对系统作出实际更改
# ./fix_gid_uid.pl --uidfile uid.txt --gidfile gid.txt > commands
# chmod +x commands
# ./commands

运行时间取决于系统中有多少组和用户被修改,以及有多少文件。根据这些因素,时间范围可能从一分钟到超过一小时。如果您担心运行时间,可以将命令文件分成几部分,分别执行。如果维护时段快要结束,您可以完成当前部分。如果有了另外一个维护时段,只需再次运行 fix_gid_uid.pl 脚本。它会生成需要运行的新命令,而之前已经修复的 UID 和 GID 将不会再次出现在输出结果中。

清单 13. fix_gid_uid.pl 脚本
#!/usr/bin/perl
#  This is unsupported code.  This script is provided "as is" without warranty of
#  any kind, expressed or implied, including, but not limited to, the implied
#  warranty of merchantability or fitness for a particular purpose.
#  Use at your own risk.
#
use Getopt::Long;
use User::pwent;
use User::grent;

my ($uid_file, $gid_file);

GetOptions("uidfile=s" => \$uid_file, "gidfile=s" => \$gid_file);

if (!((defined $gid_file) || (defined $uid_file))){
  print "Specify at least one arguments:  --uidfile <filename> ";
  print "AND/OR --gidfile <filename>\n\n";
  print "Example: $0 --uidfile uid.txt --gidfile gid.txt\n\n";
  print "Format of UID and GID files should be: \n";
  print "<Desired GID/UID#>  <User/Group Name>\n\n";
  print "Example:\n";
  print "3000 user1\n";
  print "3001 user2\n\n";
  exit 1;
}

if (defined $gid_file){
  open GIDFILE, "<$gid_file" or die $!;
  while (<GIDFILE>){
    my $line = $_;
    if ($line =~ /(\S+)\s+(\S+)\s*/){
      my $gid = $1;
      my $group = $2;
      while($ent = getgrent()){
        my $ent_name = $ent->name;
        my $ent_gid = $ent->gid;
        if ($ent_gid == $gid){
          if ($ent_name ne $group){
            print "### Error, Group in $gid_file file ($group, GID: $gid)";
            print " conflicts with group on system ($ent_name, GID: $ent_gid)\n";
            print "### Exiting program, please fix and rerun\n";
            exit 2;
          }
        }
      }
      endgrent();
    }
  }
  close GIDFILE;
}

if (defined $uid_file){
  open UIDFILE, "<$uid_file" or die $!;
  while (<UIDFILE>){
    my $line = $_;
    if ($line =~ /(\S+)\s+(\S+)\s*/){
      my $uid = $1;
      my $name = $2;
      while($ent = getpwent()){
        my $ent_name = $ent->name;
        my $ent_uid = $ent->uid;
        if ($ent_uid == $uid){
          if ($ent_name ne $name){
            print "### Error, User in $uid_file file ($name, UID: $uid)";
            print "conflicts with user on system ($ent_name, UID: $ent_uid)\n";
            print "### Exiting program, please fix and rerun\n";
            exit 2;
          }
        }
      }
      endpwent();
    }
  }
  close UIDFILE;
}


if (defined $gid_file){
  print "\n### Commands to update Groups ###\n\n";

  open GIDFILE, "<$gid_file" or die $!;
  while (<GIDFILE>){
    my $line = $_;
    chomp($line);
    if ($line =~ /(\S+)\s+(\S+)\s*/){
      my $newgid = $1;
      my $group = $2;

      my $return = system("lsgroup $group >/dev/null 2>&1");
      if ($return == 0) {
        my $oldgid = `lsgroup -a id $group | cut -f 2 -d =`;
        chomp($oldgid);

        if ($oldgid != $newgid){
          print "chgroup id=$newgid $group\n";
          while($ent = getpwent()){
            my $ent_user = $ent->name;
            my $ent_gid = $ent->gid;
            if ($ent_gid == $oldgid){
              print "chuser pgrp=$group $ent_user \n";
            }
          }
          endpwent();
          print "find / -group $oldgid -exec chgrp -h $group {} \\;\n";
        }
      }
    }
  }
  close GIDFILE;
}

if (defined $uid_file){
  print "\n### Commands to update Users ###\n\n";

  open UIDFILE, "<$uid_file" or die $!;
  while (<UIDFILE>){
    my $line = $_;
    chomp($line);
    if ($line =~ /(\S+)\s+(\S+)\s*/){
      my $newuid = $1;
      my $user = $2;

      my $return = system("lsuser $user >/dev/null 2>&1");
      if ($return == 0) {
        my $olduid = `lsuser -a id $user | cut -f 2 -d =`;
        chomp($olduid);
        if ($olduid != $newuid){
          print "chuser id=$newuid $user\n";
          print "find / -user $olduid -exec chown -h $user {} \\;\n";
        }
      }
    }
  }
  close UIDFILE;
}

结束语

让多个 AIX 服务器上的 UID 和 GID 保持一致被视为是一种最佳实践,而且如本文所示,这是一个可以实现的目标。按照本文所列的流程操作,系统管理员可以避免在标准化 UID 和 GID 编号过程中可能出现的问题。

参考资料

学习

  • 参见 AIX 信息中心 中更多关于本文中用到的命令的信息:
  • AIX and UNIX 专区:developerWorks 的“AIX and UNIX 专区”提供了大量与 AIX 系统管理的所有方面相关的信息,您可以利用它们来扩展自己的 UNIX 技能。
  • AIX and UNIX 新手入门:访问“AIX and UNIX 新手入门”页面可了解更多关于 AIX 和 UNIX 的内容。
  • AIX and UNIX 专题汇总:AIX and UNIX 专区已经为您推出了很多的技术专题,为您总结了很多热门的知识点。我们在后面还会继续推出很多相关的热门专题给您,为了方便您的访问,我们在这里为您把本专区的所有专题进行汇总,让您更方便的找到您需要的内容。
  • AIX and UNIX 下载中心:在这里你可以下载到可以运行在 AIX 或者是 UNIX 系统上的 IBM 服务器软件以及工具,让您可以提前免费试用他们的强大功能。
  • IBM Systems Magazine for AIX 中文版:本杂志的内容更加关注于趋势和企业级架构应用方面的内容,同时对于新兴的技术、产品、应用方式等也有很深入的探讨。IBM Systems Magazine 的内容都是由十分资深的业内人士撰写的,包括 IBM 的合作伙伴、IBM 的主机工程师以及高级管理人员。所以,从这些内容中,您可以了解到更高层次的应用理念,让您在选择和应用 IBM 系统时有一个更好的认识。

获得产品和技术

  • 试用 IBM 软件 。下载试用版,登录在线试用,在沙箱环境中使用产品,或是通过云来访问。有超过 100 种 IBM 产品试用版选择。

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=AIX and UNIX
ArticleID=780512
ArticleTitle=标准化 AIX 上的用户 UID 和 GID 编号
publish-date=12122011