無法獲取指向控制台的文件描述符 (couldn’t get a file descriptor referring to the console)
- 2021 年 10 月 19 日
- 筆記
- permission, RaspberryPi, tty, 學以致用
背景
最近收拾東西,從一堆雜物里翻出來塵封四年多的樹莓派 3B 主機來,打掃打掃灰塵,接上電源,居然還能通過之前設置好的 VNC 連上。欣慰之餘,開始 clone 我的 git 項目,為它們拓展一個新的平台。在執行 cnblogs 項目 (參考《部落格園排名預測 》) 對應的繪圖命令時,趨勢圖、預測圖是生成了,但沒有自動打開圖片,這個問題經過一番探索居然解決了,這篇文章就來分享一下解決問題的過程。
問題
第一眼看到的錯誤資訊:
$ open ./fit.png 無法獲取指向控制台的文件描述符
這裡我設置了控制台 locale 為中文,如果是英文的話,得到下面的結果:
Couldn't get a file descriptor referring to the console
果斷在網上搜索了這個錯誤,得到的結果比較少,根據解決方案主要分以下幾種:
- setfont 命令:
- loadkeys 命令:
這裡我並沒有調用 setfont 或 loadkeys 命令,直接執行它倆也會報一樣的錯誤,難道需要在登錄腳本里執行一下?抱著試試看的態度,我在 ~/.bashrc 中加了這麼一句:
loadkeys
重啟樹莓派,這回用 ssh 登錄,結果一上來就報錯:
The programs included with the Debian GNU/Linux system are free software; the exact distribution terms for each program are described in the individual files in /usr/share/doc/*/copyright. Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent permitted by applicable law. Last login: Sun Aug 8 23:07:46 2021 from 192.168.1.118 無法獲取指向控制台的文件描述符
看來網上的說法和我遇到的不一樣,只能自己探索了。好在遇事不決、量子力學,哦 no,strace,看看底層調用哪個環節出問題了:
$ strace open ./fit.png execve("/bin/open", ["open", "fit.png"], [/* 36 vars */]) = 0 brk(0) = 0x822000 uname({sys="Linux", node="raspberrypi", ...}) = 0 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x76f91000 access("/etc/ld.so.preload", R_OK) = 0 open("/etc/ld.so.preload", O_RDONLY|O_CLOEXEC) = 3 fstat64(3, {st_mode=S_IFREG|0644, st_size=42, ...}) = 0 mmap2(NULL, 42, PROT_READ|PROT_WRITE, MAP_PRIVATE, 3, 0) = 0x76f90000 close(3) = 0 open("/usr/lib/arm-linux-gnueabihf/libarmmem.so", O_RDONLY|O_CLOEXEC) = 3 read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0(\0\1\0\0\0h\5\0\0004\0\0\0"..., 512) = 512 lseek(3, 17960, SEEK_SET) = 17960 read(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 960) = 960 lseek(3, 17696, SEEK_SET) = 17696 read(3, "A.\0\0\0aeabi\0\1$\0\0\0\0056\0\6\6\10\1\t\1\n\3\f\1\22\4\24"..., 47) = 47 fstat64(3, {st_mode=S_IFREG|0644, st_size=18920, ...}) = 0 mmap2(NULL, 83236, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x76f4f000 mprotect(0x76f54000, 61440, PROT_NONE) = 0 mmap2(0x76f63000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x4000) = 0x76f63000 mprotect(0x7eb5f000, 4096, PROT_READ|PROT_WRITE|PROT_EXEC|PROT_GROWSDOWN) = 0 close(3) = 0 munmap(0x76f90000, 42) = 0 open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3 fstat64(3, {st_mode=S_IFREG|0644, st_size=76981, ...}) = 0 mmap2(NULL, 76981, PROT_READ, MAP_PRIVATE, 3, 0) = 0x76f3c000 close(3) = 0 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) open("/lib/arm-linux-gnueabihf/libc.so.6", O_RDONLY|O_CLOEXEC) = 3 read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0(\0\1\0\0\0L\204\1\0004\0\0\0"..., 512) = 512 lseek(3, 1239936, SEEK_SET) = 1239936 read(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 2840) = 2840 lseek(3, 1236500, SEEK_SET) = 1236500 read(3, "A.\0\0\0aeabi\0\1$\0\0\0\0056\0\6\6\10\1\t\1\n\2\22\4\23\1\24"..., 47) = 47 fstat64(3, {st_mode=S_IFREG|0755, st_size=1242776, ...}) = 0 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x76f90000 mmap2(NULL, 1312152, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x76dfb000 mprotect(0x76f26000, 65536, PROT_NONE) = 0 mmap2(0x76f36000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x12b000) = 0x76f36000 mmap2(0x76f39000, 9624, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x76f39000 close(3) = 0 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x76f8f000 set_tls(0x76f8f4c0, 0x76f8fba8, 0x76f94050, 0x76f8f4c0, 0x76f94050) = 0 mprotect(0x76f36000, 8192, PROT_READ) = 0 mprotect(0x76f4f000, 20480, PROT_READ|PROT_WRITE) = 0 mprotect(0x76f4f000, 20480, PROT_READ|PROT_EXEC) = 0 cacheflush(0x76f4f000, 0x76f54000, 0, 0x15, 0x7eb5f0b0) = 0 mprotect(0x22000, 4096, PROT_READ) = 0 mprotect(0x76f93000, 4096, PROT_READ) = 0 munmap(0x76f3c000, 76981) = 0 brk(0) = 0x822000 brk(0x843000) = 0x843000 open("/usr/lib/locale/locale-archive", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = 3 fstat64(3, {st_mode=S_IFREG|0644, st_size=4395328, ...}) = 0 mmap2(NULL, 2097152, PROT_READ, MAP_PRIVATE, 3, 0) = 0x76bfb000 mmap2(NULL, 1720320, PROT_READ, MAP_PRIVATE, 3, 0x28e000) = 0x76a57000 close(3) = 0 fstat64(0, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 1), ...}) = 0 fstat64(1, {st_mode=S_IFREG|0644, st_size=3753, ...}) = 0 fstat64(2, {st_mode=S_IFREG|0644, st_size=3811, ...}) = 0 open("/proc/self/fd/0", O_RDWR) = 3 ioctl(3, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 ioctl(3, KDGKBTYPE, 0x7eb5f14b) = -1 ENOTTY (Inappropriate ioctl for device) close(3) = 0 open("/dev/tty", O_RDWR) = 3 ioctl(3, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 ioctl(3, KDGKBTYPE, 0x7eb5f14b) = -1 ENOTTY (Inappropriate ioctl for device) close(3) = 0 open("/dev/tty0", O_RDWR) = -1 EACCES (Permission denied) open("/dev/tty0", O_WRONLY) = -1 EACCES (Permission denied) open("/dev/tty0", O_RDONLY) = -1 EACCES (Permission denied) open("/dev/vc/0", O_RDWR) = -1 ENOENT (No such file or directory) open("/dev/vc/0", O_WRONLY) = -1 ENOENT (No such file or directory) open("/dev/vc/0", O_RDONLY) = -1 ENOENT (No such file or directory) open("/dev/systty", O_RDWR) = -1 ENOENT (No such file or directory) open("/dev/systty", O_WRONLY) = -1 ENOENT (No such file or directory) open("/dev/systty", O_RDONLY) = -1 ENOENT (No such file or directory) open("/dev/console", O_RDWR) = -1 EACCES (Permission denied) open("/dev/console", O_WRONLY) = -1 EACCES (Permission denied) open("/dev/console", O_RDONLY) = -1 EACCES (Permission denied) ioctl(0, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 ioctl(0, KDGKBTYPE, 0x7eb5f14b) = -1 ENOTTY (Inappropriate ioctl for device) ioctl(1, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGETS, 0x7eb5f0cc) = -1 ENOTTY (Inappropriate ioctl for device) ioctl(2, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGETS, 0x7eb5f0cc) = -1 ENOTTY (Inappropriate ioctl for device) open("/usr/share/locale/locale.alias", O_RDONLY|O_CLOEXEC) = 3 fcntl64(3, F_GETFD) = 0x1 (flags FD_CLOEXEC) fstat64(3, {st_mode=S_IFREG|0644, st_size=2492, ...}) = 0 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x76f8e000 read(3, "# Locale name alias data base.\n#"..., 4096) = 2492 read(3, "", 4096) = 0 close(3) = 0 munmap(0x76f8e000, 4096) = 0 open("/usr/share/locale/zh_CN.UTF-8/LC_MESSAGES/kbd.mo", O_RDONLY) = -1 ENOENT (No such file or directory) open("/usr/share/locale/zh_CN.utf8/LC_MESSAGES/kbd.mo", O_RDONLY) = -1 ENOENT (No such file or directory) open("/usr/share/locale/zh_CN/LC_MESSAGES/kbd.mo", O_RDONLY) = 3 fstat64(3, {st_mode=S_IFREG|0644, st_size=30170, ...}) = 0 mmap2(NULL, 30170, PROT_READ, MAP_PRIVATE, 3, 0) = 0x76f87000 close(3) = 0 open("/usr/lib/arm-linux-gnueabihf/gconv/gconv-modules.cache", O_RDONLY) = 3 fstat64(3, {st_mode=S_IFREG|0644, st_size=26262, ...}) = 0 mmap2(NULL, 26262, PROT_READ, MAP_SHARED, 3, 0) = 0x76f48000 close(3) = 0 write(2, "\346\227\240\346\263\225\350\216\267\345\217\226\346\214\207\345\220\221\346\216\247\345\210\266\345\217\260\347\232\204\346\226"..., 46無法獲取指向控制台的文件描述符 ) = 46 exit_group(1) = ? +++ exited with 1 +++
brk (line 53) 之前的輸出都不用看,屬於程式初始化的邏輯;值得注意的報錯點在 line 70 與 79:
open("/dev/console", O_RDWR) = -1 EACCES (Permission denied)
這兩個地方錯誤相似,都是打開 tty 設備時沒有許可權,而且是試了三種許可權都失敗了,分別為:讀寫 (O_RDWR)、只寫 (O_WRONLY)、只讀 (O_RDONLY),看來為了打開這個設備,open 也是儘力了呀~
為什麼好端端的會沒有許可權呢?讓我們看下 ls 的輸出:
$ ls -lh /dev/console crw------- 1 root root 5, 1 Aug 8 22:37 /dev/console
這個設備是 root 創建、root 擁有、且沒有給其它用戶開放任何許可權,難怪會載入失敗。
解決方案
既然根因是許可權導致的,那就從許可權入手來解決,之前系統總結過 linux 文件許可權的規則 (參考 《[apue] linux 文件訪問許可權那些事兒 》),要將 root 創建的文件分享給當前用戶訪問,也不是什麼難事。
console group
第一個冒出來的想法,就是創建一個新的用戶組 (例如 console),把 root 和當前用戶 pi 都加入到這個組中,然後指定設備的所有組為 console,並開放適當的組許可權,這樣就可以實現共享啦。有的人可能覺得麻煩,直接把出錯的 tty 設備 chown 到當前用戶不就行了?對於普通的數據文件,我也經常這樣搞,但是對於公共的、系統相關的文件,我勸大家還是不要這麼自信,否則可能搞到開不了機 (個人慘痛遭遇就不展開了)。另外這樣做還有個額外的好處,就是當新用戶遇到了同樣問題時,只要把他加入這個組 (console) 就搞定了!下面是對應的腳本:
groups sudo groupadd console usermod -a -G console pi groups su groups usermod -a -G console root groups chgrp console /dev/console chmod g+rw /dev/console exit
夾在 usermod 前後的 groups 命令用來顯示當前用戶所屬的用戶組,用以驗證添加附加組是否成功 (實際上無效,見後面說明)。注意對 root 帳戶的操作需要使用 su 切換,切換後順便對它擁有的文件 (/dev/console) 進行操作,以避免許可權上的錯誤,最後執行完成後 exit 退回到當前用戶。這裡有兩個點需要注意:
- usermod -a 選項必需和 -G 配合使用,之前我單獨使用 -a 時 usemod 會直接列印 Usage,查了下 man 才弄明白;
- 將用戶添加到一個組後 groups 不能立即看到結果,必需重新登錄才會生效。特別對於 VNC 登錄,logout 菜單似乎不起作用,需要重啟設備才行。
覺得敲命令麻煩的同學,也可以直接進入 /etc/group 文件進行修改:
$ tail /etc/group
avahi:x:110:
ntp:x:111:
ssh:x:112:
bluetooth:x:113:
spi:x:999:pi
i2c:x:998:pi
gpio:x:997:pi
lightdm:x:114:
lpadmin:x:115:
console:x:1001:pi,root
最後一行就是了。下面是執行成功後新的 ls 的輸出:
# ls -lh /dev/console crw-rw---- 1 root console 5, 1 Aug 8 22:37 /dev/console
此時再運行一次 strace open,發現針對 console 的訪問成功了!
open("/dev/console", O_RDWR) = 3
雖然因為其它 tty 沒有修改仍然出錯了,但是至少證明方向是對的。
tty group
上面的方案是可行的,在查看 tty 設備時無意間得到了下面的輸出:
$ ls -lh /dev/tty[0-9]* crw------- 1 root root 4, 0 Aug 8 23:51 /dev/tty0 crw------- 1 pi tty 4, 1 Aug 8 23:52 /dev/tty1 crw------- 1 root tty 4, 10 Aug 8 23:51 /dev/tty10 crw------- 1 root tty 4, 11 Aug 8 23:51 /dev/tty11 crw------- 1 root tty 4, 12 Aug 8 23:51 /dev/tty12 crw------- 1 root tty 4, 13 Aug 8 23:51 /dev/tty13 crw------- 1 root tty 4, 14 Aug 8 23:51 /dev/tty14 crw------- 1 root tty 4, 15 Aug 8 23:51 /dev/tty15 crw------- 1 root tty 4, 16 Aug 8 23:51 /dev/tty16 crw------- 1 root tty 4, 17 Aug 8 23:51 /dev/tty17 crw------- 1 root tty 4, 18 Aug 8 23:51 /dev/tty18 crw------- 1 root tty 4, 19 Aug 8 23:51 /dev/tty19 crw------- 1 root root 4, 2 Aug 8 23:51 /dev/tty2 crw------- 1 root tty 4, 20 Aug 8 23:51 /dev/tty20 crw------- 1 root tty 4, 21 Aug 8 23:51 /dev/tty21 crw------- 1 root tty 4, 22 Aug 8 23:51 /dev/tty22 crw------- 1 root tty 4, 23 Aug 8 23:51 /dev/tty23 crw------- 1 root tty 4, 24 Aug 8 23:51 /dev/tty24 crw------- 1 root tty 4, 25 Aug 8 23:51 /dev/tty25 crw------- 1 root tty 4, 26 Aug 8 23:51 /dev/tty26 crw------- 1 root tty 4, 27 Aug 8 23:51 /dev/tty27 crw------- 1 root tty 4, 28 Aug 8 23:51 /dev/tty28 crw------- 1 root tty 4, 29 Aug 8 23:51 /dev/tty29 crw------- 1 root root 4, 3 Aug 8 23:51 /dev/tty3 crw------- 1 root tty 4, 30 Aug 8 23:51 /dev/tty30 crw------- 1 root tty 4, 31 Aug 8 23:51 /dev/tty31 crw------- 1 root tty 4, 32 Aug 8 23:51 /dev/tty32 crw------- 1 root tty 4, 33 Aug 8 23:51 /dev/tty33 crw------- 1 root tty 4, 34 Aug 8 23:51 /dev/tty34 crw------- 1 root tty 4, 35 Aug 8 23:51 /dev/tty35 crw------- 1 root tty 4, 36 Aug 8 23:51 /dev/tty36 crw------- 1 root tty 4, 37 Aug 8 23:51 /dev/tty37 crw------- 1 root tty 4, 38 Aug 8 23:51 /dev/tty38 crw------- 1 root tty 4, 39 Aug 8 23:51 /dev/tty39 crw------- 1 root tty 4, 4 Aug 8 23:51 /dev/tty4 crw------- 1 root tty 4, 40 Aug 8 23:51 /dev/tty40 crw------- 1 root tty 4, 41 Aug 8 23:51 /dev/tty41 crw------- 1 root tty 4, 42 Aug 8 23:51 /dev/tty42 crw------- 1 root tty 4, 43 Aug 8 23:51 /dev/tty43 crw------- 1 root tty 4, 44 Aug 8 23:51 /dev/tty44 crw------- 1 root tty 4, 45 Aug 8 23:51 /dev/tty45 crw------- 1 root tty 4, 46 Aug 8 23:51 /dev/tty46 crw------- 1 root tty 4, 47 Aug 8 23:51 /dev/tty47 crw------- 1 root tty 4, 48 Aug 8 23:51 /dev/tty48 crw------- 1 root tty 4, 49 Aug 8 23:51 /dev/tty49 crw------- 1 root tty 4, 5 Aug 8 23:51 /dev/tty5 crw------- 1 root tty 4, 50 Aug 8 23:51 /dev/tty50 crw------- 1 root tty 4, 51 Aug 8 23:51 /dev/tty51 crw------- 1 root tty 4, 52 Aug 8 23:51 /dev/tty52 crw------- 1 root tty 4, 53 Aug 8 23:51 /dev/tty53 crw------- 1 root tty 4, 54 Aug 8 23:51 /dev/tty54 crw------- 1 root tty 4, 55 Aug 8 23:51 /dev/tty55 crw------- 1 root tty 4, 56 Aug 8 23:51 /dev/tty56 crw------- 1 root tty 4, 57 Aug 8 23:51 /dev/tty57 crw------- 1 root tty 4, 58 Aug 8 23:51 /dev/tty58 crw------- 1 root tty 4, 59 Aug 8 23:51 /dev/tty59 crw------- 1 root tty 4, 6 Aug 8 23:51 /dev/tty6 crw------- 1 root tty 4, 60 Aug 8 23:51 /dev/tty60 crw------- 1 root tty 4, 61 Aug 8 23:51 /dev/tty61 crw------- 1 root tty 4, 62 Aug 8 23:51 /dev/tty62 crw------- 1 root tty 4, 63 Aug 8 23:51 /dev/tty63 crw------- 1 root tty 4, 7 Aug 8 23:51 /dev/tty7 crw------- 1 root tty 4, 8 Aug 8 23:51 /dev/tty8 crw------- 1 root tty 4, 9 Aug 8 23:51 /dev/tty9
除了 tty0 / tty2 / tty3 的用戶組屬於 root 外,其它 tty 設備都屬於 tty 用戶組,原來系統早為它們準備了現成的組,我在上面創建 console 組就顯得畫蛇添足了,而且觀察用戶 pi 所屬的附加組:
$ groups
pi adm tty dialout cdrom sudo audio video plugdev games users input netdev gpio i2c spi console
其中一個就是 tty,所以我從善如流的決定使用使用 tty 了。因此上節的腳本可以簡化為:
su chgrp tty /dev/console /dev/tty[0-9]* chmod g+rw /dev/console /dev/tty[0-9]* exit
什麼創建用戶組、為用戶添加附加組統統可以不要了,只需要修正 tty 設備文件的用戶組就可以啦。
group permission
重啟設備讓修改生效後,再運行一次 strace open,發現還是有問題:
$ strace open ./fit.png execve("/bin/open", ["open", "./fit.png"], [/* 36 vars */]) = 0 brk(0) = 0xe6b000 uname({sys="Linux", node="raspberrypi", ...}) = 0 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x76f95000 access("/etc/ld.so.preload", R_OK) = 0 open("/etc/ld.so.preload", O_RDONLY|O_CLOEXEC) = 3 fstat64(3, {st_mode=S_IFREG|0644, st_size=42, ...}) = 0 mmap2(NULL, 42, PROT_READ|PROT_WRITE, MAP_PRIVATE, 3, 0) = 0x76f94000 close(3) = 0 open("/usr/lib/arm-linux-gnueabihf/libarmmem.so", O_RDONLY|O_CLOEXEC) = 3 read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0(\0\1\0\0\0h\5\0\0004\0\0\0"..., 512) = 512 lseek(3, 17960, SEEK_SET) = 17960 read(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 960) = 960 lseek(3, 17696, SEEK_SET) = 17696 read(3, "A.\0\0\0aeabi\0\1$\0\0\0\0056\0\6\6\10\1\t\1\n\3\f\1\22\4\24"..., 47) = 47 fstat64(3, {st_mode=S_IFREG|0644, st_size=18920, ...}) = 0 mmap2(NULL, 83236, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x76f53000 mprotect(0x76f58000, 61440, PROT_NONE) = 0 mmap2(0x76f67000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x4000) = 0x76f67000 mprotect(0x7e817000, 4096, PROT_READ|PROT_WRITE|PROT_EXEC|PROT_GROWSDOWN) = 0 close(3) = 0 munmap(0x76f94000, 42) = 0 open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3 fstat64(3, {st_mode=S_IFREG|0644, st_size=76981, ...}) = 0 mmap2(NULL, 76981, PROT_READ, MAP_PRIVATE, 3, 0) = 0x76f40000 close(3) = 0 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) open("/lib/arm-linux-gnueabihf/libc.so.6", O_RDONLY|O_CLOEXEC) = 3 read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0(\0\1\0\0\0L\204\1\0004\0\0\0"..., 512) = 512 lseek(3, 1239936, SEEK_SET) = 1239936 read(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 2840) = 2840 lseek(3, 1236500, SEEK_SET) = 1236500 read(3, "A.\0\0\0aeabi\0\1$\0\0\0\0056\0\6\6\10\1\t\1\n\2\22\4\23\1\24"..., 47) = 47 fstat64(3, {st_mode=S_IFREG|0755, st_size=1242776, ...}) = 0 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x76f94000 mmap2(NULL, 1312152, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x76dff000 mprotect(0x76f2a000, 65536, PROT_NONE) = 0 mmap2(0x76f3a000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x12b000) = 0x76f3a000 mmap2(0x76f3d000, 9624, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x76f3d000 close(3) = 0 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x76f93000 set_tls(0x76f934c0, 0x76f93ba8, 0x76f98050, 0x76f934c0, 0x76f98050) = 0 mprotect(0x76f3a000, 8192, PROT_READ) = 0 mprotect(0x76f53000, 20480, PROT_READ|PROT_WRITE) = 0 mprotect(0x76f53000, 20480, PROT_READ|PROT_EXEC) = 0 cacheflush(0x76f53000, 0x76f58000, 0, 0x15, 0x7e8170b0) = 0 mprotect(0x22000, 4096, PROT_READ) = 0 mprotect(0x76f97000, 4096, PROT_READ) = 0 munmap(0x76f40000, 76981) = 0 brk(0) = 0xe6b000 brk(0xe8c000) = 0xe8c000 open("/usr/lib/locale/locale-archive", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = 3 fstat64(3, {st_mode=S_IFREG|0644, st_size=4395328, ...}) = 0 mmap2(NULL, 2097152, PROT_READ, MAP_PRIVATE, 3, 0) = 0x76bff000 mmap2(NULL, 1720320, PROT_READ, MAP_PRIVATE, 3, 0x28e000) = 0x76a5b000 close(3) = 0 fstat64(0, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 1), ...}) = 0 fstat64(1, {st_mode=S_IFREG|0644, st_size=3755, ...}) = 0 fstat64(2, {st_mode=S_IFREG|0644, st_size=3813, ...}) = 0 open("/proc/self/fd/0", O_RDWR) = 3 ioctl(3, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 ioctl(3, KDGKBTYPE, 0x7e81714b) = -1 ENOTTY (Inappropriate ioctl for device) close(3) = 0 open("/dev/tty", O_RDWR) = 3 ioctl(3, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 ioctl(3, KDGKBTYPE, 0x7e81714b) = -1 ENOTTY (Inappropriate ioctl for device) close(3) = 0 open("/dev/tty0", O_RDWR) = -1 EACCES (Permission denied) open("/dev/tty0", O_WRONLY) = 3 ioctl(3, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGETS, {B38400 -opost -isig -icanon -echo ...}) = 0 ioctl(3, KDGKBTYPE, 0x7e81714b) = 0 ioctl(3, VT_GETSTATE, 0x7e8171a8) = 0 ioctl(3, VIDIOC_QUERYCAP or VT_OPENQRY, 0x7e8171a4) = 0 clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x76f93068) = 1882 exit_group(0) = ? +++ exited with 0 +++ open: Unable to open /dev/tty2: 許可權不夠
line 70 有一次失敗,但是馬上 line 71 對同一個設備操作成功,末尾出現的許可權不夠失敗對應的 /dev/tty2 文件根本在前面沒有出現過,amazing~
用 ls 大法看一看:
$ ls -lh /dev/tty[0-9]* crw--w---- 1 root tty 4, 0 Aug 9 00:13 /dev/tty0 crw------- 1 pi tty 4, 1 Aug 9 00:14 /dev/tty1 crw--w---- 1 root tty 4, 10 Aug 9 00:13 /dev/tty10 crw--w---- 1 root tty 4, 11 Aug 9 00:13 /dev/tty11 crw--w---- 1 root tty 4, 12 Aug 9 00:13 /dev/tty12 crw--w---- 1 root tty 4, 13 Aug 9 00:13 /dev/tty13 crw--w---- 1 root tty 4, 14 Aug 9 00:13 /dev/tty14 crw--w---- 1 root tty 4, 15 Aug 9 00:13 /dev/tty15 crw--w---- 1 root tty 4, 16 Aug 9 00:13 /dev/tty16 crw--w---- 1 root tty 4, 17 Aug 9 00:13 /dev/tty17 crw--w---- 1 root tty 4, 18 Aug 9 00:13 /dev/tty18 crw--w---- 1 root tty 4, 19 Aug 9 00:13 /dev/tty19 crw--w---- 1 root tty 4, 2 Aug 9 00:13 /dev/tty2 crw--w---- 1 root tty 4, 20 Aug 9 00:13 /dev/tty20 crw--w---- 1 root tty 4, 21 Aug 9 00:13 /dev/tty21 crw--w---- 1 root tty 4, 22 Aug 9 00:13 /dev/tty22 crw--w---- 1 root tty 4, 23 Aug 9 00:13 /dev/tty23 crw--w---- 1 root tty 4, 24 Aug 9 00:13 /dev/tty24 crw--w---- 1 root tty 4, 25 Aug 9 00:13 /dev/tty25 crw--w---- 1 root tty 4, 26 Aug 9 00:13 /dev/tty26 crw--w---- 1 root tty 4, 27 Aug 9 00:13 /dev/tty27 crw--w---- 1 root tty 4, 28 Aug 9 00:13 /dev/tty28 crw--w---- 1 root tty 4, 29 Aug 9 00:13 /dev/tty29 crw--w---- 1 root tty 4, 3 Aug 9 00:13 /dev/tty3 crw--w---- 1 root tty 4, 30 Aug 9 00:13 /dev/tty30 crw--w---- 1 root tty 4, 31 Aug 9 00:13 /dev/tty31 crw--w---- 1 root tty 4, 32 Aug 9 00:13 /dev/tty32 crw--w---- 1 root tty 4, 33 Aug 9 00:13 /dev/tty33 crw--w---- 1 root tty 4, 34 Aug 9 00:13 /dev/tty34 crw--w---- 1 root tty 4, 35 Aug 9 00:13 /dev/tty35 crw--w---- 1 root tty 4, 36 Aug 9 00:13 /dev/tty36 crw--w---- 1 root tty 4, 37 Aug 9 00:13 /dev/tty37 crw--w---- 1 root tty 4, 38 Aug 9 00:13 /dev/tty38 crw--w---- 1 root tty 4, 39 Aug 9 00:13 /dev/tty39 crw--w---- 1 root tty 4, 4 Aug 9 00:13 /dev/tty4 crw--w---- 1 root tty 4, 40 Aug 9 00:13 /dev/tty40 crw--w---- 1 root tty 4, 41 Aug 9 00:13 /dev/tty41 crw--w---- 1 root tty 4, 42 Aug 9 00:13 /dev/tty42 crw--w---- 1 root tty 4, 43 Aug 9 00:13 /dev/tty43 crw--w---- 1 root tty 4, 44 Aug 9 00:13 /dev/tty44 crw--w---- 1 root tty 4, 45 Aug 9 00:13 /dev/tty45 crw--w---- 1 root tty 4, 46 Aug 9 00:13 /dev/tty46 crw--w---- 1 root tty 4, 47 Aug 9 00:13 /dev/tty47 crw--w---- 1 root tty 4, 48 Aug 9 00:13 /dev/tty48 crw--w---- 1 root tty 4, 49 Aug 9 00:13 /dev/tty49 crw--w---- 1 root tty 4, 5 Aug 9 00:13 /dev/tty5 crw--w---- 1 root tty 4, 50 Aug 9 00:13 /dev/tty50 crw--w---- 1 root tty 4, 51 Aug 9 00:13 /dev/tty51 crw--w---- 1 root tty 4, 52 Aug 9 00:13 /dev/tty52 crw--w---- 1 root tty 4, 53 Aug 9 00:13 /dev/tty53 crw--w---- 1 root tty 4, 54 Aug 9 00:13 /dev/tty54 crw--w---- 1 root tty 4, 55 Aug 9 00:13 /dev/tty55 crw--w---- 1 root tty 4, 56 Aug 9 00:13 /dev/tty56 crw--w---- 1 root tty 4, 57 Aug 9 00:13 /dev/tty57 crw--w---- 1 root tty 4, 58 Aug 9 00:13 /dev/tty58 crw--w---- 1 root tty 4, 59 Aug 9 00:13 /dev/tty59 crw--w---- 1 root tty 4, 6 Aug 9 00:13 /dev/tty6 crw--w---- 1 root tty 4, 60 Aug 9 00:13 /dev/tty60 crw--w---- 1 root tty 4, 61 Aug 9 00:13 /dev/tty61 crw--w---- 1 root tty 4, 62 Aug 9 00:13 /dev/tty62 crw--w---- 1 root tty 4, 63 Aug 9 00:13 /dev/tty63 crw--w---- 1 root tty 4, 7 Aug 9 00:13 /dev/tty7 crw--w---- 1 root tty 4, 8 Aug 9 00:13 /dev/tty8 crw--w---- 1 root tty 4, 9 Aug 9 00:13 /dev/tty9
所有 tty 設置的組許可權變成了只寫?難道是上一步中重啟設備前我忘了設置各個文件的許可權? 用 root 帳號 chmod 一下,輸出這次正常了:
$ strace open ./fit.png execve("/bin/open", ["open", "./fit.png"], [/* 36 vars */]) = 0 brk(0) = 0x288000 uname({sys="Linux", node="raspberrypi", ...}) = 0 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x76efd000 access("/etc/ld.so.preload", R_OK) = 0 open("/etc/ld.so.preload", O_RDONLY|O_CLOEXEC) = 3 fstat64(3, {st_mode=S_IFREG|0644, st_size=42, ...}) = 0 mmap2(NULL, 42, PROT_READ|PROT_WRITE, MAP_PRIVATE, 3, 0) = 0x76efc000 close(3) = 0 open("/usr/lib/arm-linux-gnueabihf/libarmmem.so", O_RDONLY|O_CLOEXEC) = 3 read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0(\0\1\0\0\0h\5\0\0004\0\0\0"..., 512) = 512 lseek(3, 17960, SEEK_SET) = 17960 read(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 960) = 960 lseek(3, 17696, SEEK_SET) = 17696 read(3, "A.\0\0\0aeabi\0\1$\0\0\0\0056\0\6\6\10\1\t\1\n\3\f\1\22\4\24"..., 47) = 47 fstat64(3, {st_mode=S_IFREG|0644, st_size=18920, ...}) = 0 mmap2(NULL, 83236, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x76ebb000 mprotect(0x76ec0000, 61440, PROT_NONE) = 0 mmap2(0x76ecf000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x4000) = 0x76ecf000 mprotect(0x7ea80000, 4096, PROT_READ|PROT_WRITE|PROT_EXEC|PROT_GROWSDOWN) = 0 close(3) = 0 munmap(0x76efc000, 42) = 0 open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3 fstat64(3, {st_mode=S_IFREG|0644, st_size=76981, ...}) = 0 mmap2(NULL, 76981, PROT_READ, MAP_PRIVATE, 3, 0) = 0x76ea8000 close(3) = 0 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) open("/lib/arm-linux-gnueabihf/libc.so.6", O_RDONLY|O_CLOEXEC) = 3 read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0(\0\1\0\0\0L\204\1\0004\0\0\0"..., 512) = 512 lseek(3, 1239936, SEEK_SET) = 1239936 read(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 2840) = 2840 lseek(3, 1236500, SEEK_SET) = 1236500 read(3, "A.\0\0\0aeabi\0\1$\0\0\0\0056\0\6\6\10\1\t\1\n\2\22\4\23\1\24"..., 47) = 47 fstat64(3, {st_mode=S_IFREG|0755, st_size=1242776, ...}) = 0 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x76efc000 mmap2(NULL, 1312152, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x76d67000 mprotect(0x76e92000, 65536, PROT_NONE) = 0 mmap2(0x76ea2000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x12b000) = 0x76ea2000 mmap2(0x76ea5000, 9624, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x76ea5000 close(3) = 0 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x76efb000 set_tls(0x76efb4c0, 0x76efbba8, 0x76f00050, 0x76efb4c0, 0x76f00050) = 0 mprotect(0x76ea2000, 8192, PROT_READ) = 0 mprotect(0x76ebb000, 20480, PROT_READ|PROT_WRITE) = 0 mprotect(0x76ebb000, 20480, PROT_READ|PROT_EXEC) = 0 cacheflush(0x76ebb000, 0x76ec0000, 0, 0x15, 0x7ea800b0) = 0 mprotect(0x22000, 4096, PROT_READ) = 0 mprotect(0x76eff000, 4096, PROT_READ) = 0 munmap(0x76ea8000, 76981) = 0 brk(0) = 0x288000 brk(0x2a9000) = 0x2a9000 open("/usr/lib/locale/locale-archive", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = 3 fstat64(3, {st_mode=S_IFREG|0644, st_size=4395328, ...}) = 0 mmap2(NULL, 2097152, PROT_READ, MAP_PRIVATE, 3, 0) = 0x76b67000 mmap2(NULL, 1720320, PROT_READ, MAP_PRIVATE, 3, 0x28e000) = 0x769c3000 close(3) = 0 fstat64(0, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 1), ...}) = 0 fstat64(1, {st_mode=S_IFREG|0644, st_size=3755, ...}) = 0 fstat64(2, {st_mode=S_IFREG|0644, st_size=3813, ...}) = 0 open("/proc/self/fd/0", O_RDWR) = 3 ioctl(3, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 ioctl(3, KDGKBTYPE, 0x7ea8014b) = -1 ENOTTY (Inappropriate ioctl for device) close(3) = 0 open("/dev/tty", O_RDWR) = 3 ioctl(3, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGETS, {B38400 opost isig icanon echo ...}) = 0 ioctl(3, KDGKBTYPE, 0x7ea8014b) = -1 ENOTTY (Inappropriate ioctl for device) close(3) = 0 open("/dev/tty0", O_RDWR) = 3 ioctl(3, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGETS, {B38400 -opost -isig -icanon -echo ...}) = 0 ioctl(3, KDGKBTYPE, 0x7ea8014b) = 0 ioctl(3, VT_GETSTATE, 0x7ea801a8) = 0 ioctl(3, VIDIOC_QUERYCAP or VT_OPENQRY, 0x7ea801a4) = 0 clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x76efb068) = 2013 exit_group(0) = ? +++ exited with 0 +++
不過再次重啟後,這個文件許可權仍然會被重置,之前使用 console group 時,也發現重啟設備後 /dev/console 文件的用戶組會自動重置為 root,組許可權重置為空。好在這裡只是將許可權重置為只寫,難道每次登錄都要設置一遍 tty 文件許可權?
xdg-open
答案是 no,倒不是因為找到了解決方案,而是即使在許可權正確的情況下、open 不再報錯了,圖片還是沒有自動打開 (汗)。於是我決定試試其它的命令,經過一番百度,找到了一個在 Ubuntu 下很常用的 xdg-open 命令,參數是要打開的圖片路徑,調用方式和 open 一樣。話說回來,open 是我在 mac 上使用的命令,不適用 raspberrypi 是情理之中的,至於這個上的 open 是幹啥的,我沒找到對應的 man 記錄 (還是個三無命令)。另外一開始如果使用 xdg-open,是不是就不用設置設備文件的用戶組和許可權了?沒有試,不得而知。
結語
本文探索了詭異的 tty 設備文件許可權問題,結合 linux 文件許可權相關的知識進行了一番大膽的嘗試,最後卻另闢蹊徑解決了問題。如果你在現實中遇到了類似的問題,不妨可以試試上面的方案,不管成功與否,都歡迎在文章下面留言回饋。
後記
和問題相關的都說的差不多了,下面來聊兩句題外話。這次收拾塵封的樹莓派,我深切的體會到了設置密碼要簡單的重要性,ssh 登錄過程中試遍了我常用的所有密碼,都不行,最後還是 1-6 拯救了我,而且 root 密碼也是這個呦~
最後看著破破爛爛的亞克力塑料外殼,實在不能忍了,果斷淘寶了一套新的外殼換上,換殼前後對比明顯:
最後上一張樹莓派 VNC 的截圖:
參考
[1]. Ubuntu下通過命令打開圖片