从一条 PostgreSQL 锁文件报错说起:权限问题的本质
前段时间公司测试环境一台 PostgreSQL 突然连不上了,日志里反复出现一行报错:
text复制无法创建锁文件 "/var/run/postgresql/.s.pgsql.5432.lock": 权限不够
当时我第一反应是磁盘满了,结果 df -h 一看,空间还很充裕。又怀疑是不是 PostgreSQL 进程被杀、socket 目录被误删,后来一查才发现,问题根本不在磁盘,而是 /var/run/postgresql 这个目录的写权限不对。这个目录在开机时由系统自动创建,默认属主是 postgres 用户,但测试环境重启后目录权限被某个初始化脚本改成了 755,非属主用户根本没有写权限,PostgreSQL 自然没法在这个目录里创建 Unix socket 和锁文件。
其实这类问题在 Linux 系统运维里太常见了。“我没有权限”和“这个用户的权限配错了”是两种完全不同的排查路径,但绝大多数人一看到 Permission denied 就跑去改文件权限,能蒙对一次,下一次换个文件又卡住。真正的问题在于,很多人对 Linux 文件权限和用户管理这套体系只停留在“chmod 777 能解决”的层面,从来没有系统地搞明白权限位、属主属组、默认权限、特殊权限位、sudo 提权这些概念之间的逻辑关系。
这一篇我准备把 Linux 文件权限与用户管理这条线完整拆开讲一遍,从 rwx 权限位怎么编码,到用户和组的增删改查,再到 ACL、SUID/SGID/Sticky Bit、sudo 配置这些容易被忽略但实际工作里经常踩坑的点。适合刚接触 Linux 运维的新人,也适合那些已经写了好几年命令、却一直没搞懂“为什么要这样配”的开发者。
1.1 锁文件和 socket 文件到底卡在了哪一步
先回到开头那个报错。PostgreSQL 启动时,默认会在 /var/run/postgresql 目录下创建一个 Unix socket 文件和一个锁文件。锁文件的作用是防止同一个数据目录被多个 PostgreSQL 实例重复启动,socket 文件则是让本地客户端能通过 Unix domain socket 连上来。
这两个文件的创建逻辑都依赖一句话:进程对所在目录必须拥有写权限。注意,这里的关键是“目录的写权限”,不是文件本身的权限——因为锁文件此刻还不存在。Unix 文件系统的设计里,目录本质上也是一份“文件”,里面记录着目录条目的名字和 inode 对应关系。你对一个目录有写权限,才允许在里面创建、删除、重命名文件;这个规则和文件本身的权限互不影响。
PostgreSQL 是以 postgres 用户身份运行的,所以真正需要验证的是:postgres 用户对 /var/run/postgresql 这个目录,是否具备 w 权限。
看一下目录当前状态:
bash复制$ ls -ld /var/run/postgresql
drwxr-xr-x 2 postgres postgres 60 Jan 12 09:30 /var/run/postgresql
权限位是 755,属主是 postgres 用户,这对 postgres 用户本人来说有读有写有执行,看起来问题不大。但如果目录属主不是 postgres,而是被人改成了 root,或者目录权限被收成了 750,那 postgres 用户就落入“其他用户”分类,写权限直接没了。
这也就解释了一个很常见的现象:同样一套配置,有的机器上 PostgreSQL 能正常启动,有的机器上却报 socket 目录权限不够。差别往往就在于某个初始化脚本、某个监控脚本“顺手”对目录执行了 chmod,把权限位改掉了。
这类问题最简单的验证方式是:
bash复制$ sudo -u postgres touch /var/run/postgresql/.test
能创建成功,就说明目录写权限没问题;创建失败,报错信息里会直接告诉你返回的是 EACCES 还是 EROFS,两者原因完全不同。
1.2 权限模型的最小单元:属主、属组、其他人
Linux 文件的权限模型可以浓缩成一句话:每个文件都有且仅有一个属主用户、一个属组,系统再根据请求者身份,把访问者归入三类中的某一类,最终决定放行还是拒绝。
三类身份是:
u(user):文件的属主用户,通常是创建者g(group):文件的属组,表示文件归属的那个用户组里的所有用户o(other):除上面两类外的所有其他用户
这个模型的关键在于“归属判断是互斥的”。请求访问文件时,内核先看“你是不是属主本人”,如果是,就只按属主权限判断;如果不是,再看“你是不是属组成员”,是,就只按属组权限判断;两者都不满足,才落到“其他用户”权限。不存在“属主权限不够,再把属组权限加上”这种叠加逻辑。
理解了这个模型,再回头看权限配置,很多问题就豁然开朗了:
- 一个文件属主是
root,属组是www,权限是640,那么www组的成员有读权限,非组成员只能干瞪眼,就算他也在服务器上有账号也没用。 - 一个用户加入了十几个组,访问某个文件时,系统会依次检查
uid匹配和所有从属gid匹配,只要有任意一个gid匹配上,就按对应属组权限处理。
这也顺带解释了为什么“把一个用户加到组里之后,他看到的权限可能反而变了”——因为他的身份分类从“其他用户”变成了“属组成员”,生效的权限位从 others 这一档切换到了 group 这一档。
2.1 十个字符到底代表什么
很多人看 ls -l 的输出,只知道前面一串 -rw-r--r--,却说不清每个字符的含义。这串字符实际上由两部分组成:第一个字符表示文件类型,后面九个字符分成三组,每组三个字符,依次对应 u/g/o 三类身份的读、写、执行权限。
text复制-rw-r--r-- 1 root root 1024 Jan 12 10:00 test.txt
把它拆开看:
| 位置 | 字符 | 含义 |
|---|---|---|
| 第1位 | - |
普通文件(d 目录,l 符号链接,c 字符设备,b 块设备,s socket,p 管道) |
| 第2-4位 | rw- |
属主:可读、可写、不可执行 |
| 第5-7位 | r-- |
属组:只读 |
| 第8-10位 | r-- |
其他:只读 |
那 rwx 分别代表什么?日常理解起来可以这么记:
r:读取文件内容的权限;对目录而言,是列出目录条目的权限w:修改、截断、删除文件内容的权限;对目录而言,是在目录里创建、删除、重命名条目的权限x:执行文件(如果它是程序或脚本)的权限;对目录而言,是“穿过”目录进入其子目录的权限
我一直强调目录的 x 权限,因为这是新手最常忽略的点。在 Linux 里,目录的 r 和 x 是拆开的:只有 r 没有 x,你能用 ls 看到目录里有什么文件,但无法 cd 进入,也无法访问其中任何文件的 inode 信息;只有 x 没有 r,你能 cd 进去,但 ls 看不到文件名
