Nano không lưu được: lỗi permission denied
Nano báo “Permission denied” khi lưu? Kiểm tra owner, quyền thư mục cha, filesystem read-only hoặc đầy dung lượng, rồi uid nếu bạn dùng container.
Tại sao nano không lưu được file của bạn
nano không lưu được file vì một trong 4 lý do: bạn không sở hữu file, thư mục cha không cho phép thao tác mà nano đang cố thực hiện, filesystem ở chế độ chỉ đọc hoặc đã hết dung lượng, hoặc bạn đang ở trong một container chạy với user ID khác. Hai lý do đầu là vấn đề quyền. Hai lý do sau thì không. Hãy kiểm tra theo thứ tự này vì lý do đầu tiên bao quát hầu hết trường hợp, chỉ cần một command để xác nhận, và cách sửa là sudoedit thay vì sudo nano.
Không có gì bị mất khi editor vẫn đang mở. Nội dung bạn nhập vẫn nằm trong memory, vì vậy bạn có thể để file mở, ghi buffer vào một path mà bạn sở hữu, rồi đặt file đó vào vị trí cần thiết sau. Cách thoát này nằm gần cuối guide.
Chạy các kiểm tra này trước khi thay đổi permission
Trỏ từng command vào đúng path bạn đang chỉnh sửa. Mỗi command trả lời một câu hỏi khác nhau, vì vậy hãy chạy tất cả trước khi thay đổi bất kỳ thứ gì. Thay đổi permission trước khi biết kiểm tra nào đang fail thường tạo thêm một vấn đề mới chồng lên vấn đề ban đầu.
id
ls -l /etc/nginx/nginx.conf
ls -ld /etc/nginx
namei -l /etc/nginx/nginx.conf
findmnt -no SOURCE,FSTYPE,OPTIONS -T /etc/nginx/nginx.conf
df -h /etc/nginx
df -i /etc/nginxid in user ID và các group ID hiện tại của bạn. ls -l hiển thị owner, group và các permission bit của chính file đó. ls -ld hiển thị các thông tin tương tự cho directory chứa file, đây là một câu hỏi riêng với câu trả lời riêng. namei -l duyệt qua từng phần của path và liệt kê owner cùng permission của từng phần, nên một output trả lời được cả hai câu hỏi. findmnt cho biết filesystem chứa path đó và các option dùng khi mount filesystem. df -h báo dung lượng còn trống, còn df -i báo số inode còn trống; inode có thể hết độc lập với dung lượng. Nếu các chuỗi permission này vẫn chưa quen thuộc, hãy bắt đầu với cách đọc chuỗi permission mà ls -l in ra.
Nguyên nhân 1: file thuộc về root nhưng bạn không phải root
Quyền đọc và quyền ghi là hai quyền riêng biệt. Hầu hết file trong /etc đều cho phép mọi người đọc. Vì vậy, nano vẫn mở file, hiển thị nội dung và cho phép bạn nhập tự do: những thao tác đó chưa ghi gì xuống disk. Lỗi xảy ra khi lưu, lúc kernel đối chiếu user ID và group ID của bạn với các bit quyền của owner, group và other trên file. nano chỉ hiển thị lại thông báo từ kernel, nên không có option nào của nano thay đổi được kết quả.
id và ls -l cùng xác nhận điều này. File thuộc về root, bạn không phải root, và các bit quyền dành cho other không cấp quyền ghi. Nhấn Ctrl-O lần nữa cũng không có tác dụng.
Cách đúng để chỉnh sửa file do root sở hữu là dùng sudoedit
SUDO_EDITOR=nano sudoedit /etc/nginx/nginx.confsudo tạo một bản sao tạm thời của file và đặt owner là bạn, chạy nano trên bản sao đó bằng user của bạn, rồi chép kết quả trở lại đúng vị trí với quyền root khi editor thoát. Editor không bao giờ chạy dưới quyền root. sudo -e là cùng một command với tên khác. Editor được chọn lần lượt từ SUDO_EDITOR, rồi VISUAL, rồi EDITOR, vì vậy đặt export EDITOR=nano trong shell profile sẽ đặt editor này làm mặc định ở mọi nơi. Nếu sudoers tắt flag env_editor, các biến đó sẽ bị bỏ qua và editor sẽ được lấy từ thiết lập editor trong sudoers.
sudo nano cũng có thể lưu file, và đó chính là vấn đề. Nó cấp cho editor tương tác đầy đủ quyền root trên toàn bộ filesystem trong suốt thời gian session, nên chỉ cần nhập nhầm path tại prompt lưu file là bạn có thể ghi đè nội dung lên một file hệ thống khác bằng quyền root. Làm việc với tư cách user thông thường, chỉ gọi sudo cho những bước cần quyền cao hơn là thói quen nên xây dựng, và sudoedit thể hiện thói quen đó khi bạn chỉnh sửa file cấu hình.
Hai quy tắc của sudoedit thường khiến người dùng bất ngờ. sudoedit từ chối chỉnh sửa symbolic link và từ chối chỉnh sửa file nằm trong directory mà bạn có quyền ghi, trừ khi bạn là root. Quy tắc thứ hai tồn tại vì bất kỳ ai có quyền ghi vào directory đều có thể thay file trong lúc editor đang mở. Cả hai hành vi này đều là mặc định của sudoers (sudoedit_follow tắt, sudoedit_checkdir bật). Nếu file chưa tồn tại, sudoedit sẽ tạo file đó cho bạn.
Nguyên nhân 2: thư mục cha thực sự kiểm soát điều gì
Tài liệu dành cho các editor khác thường nói rằng thao tác lưu cần quyền ghi trên thư mục, vì nhiều editor lưu bằng cách ghi một file mới rồi đổi tên file đó để thay thế file cũ. nano không hoạt động như vậy. nano mở file bạn chỉ định và ghi trực tiếp vào file đó. Vì vậy, với file đã tồn tại, bit ghi của thư mục không bao giờ được kiểm tra.
Thư mục vẫn quyết định các thao tác khác. Vì thế ls -ld có trong danh sách kiểm tra:
- Tạo một file chưa tồn tại cần quyền ghi và quyền thực thi trên thư mục, vì phải thêm một tên mới vào thư mục đó. umask của bạn quyết định các quyền ban đầu của file mới.
- Để truy cập được file, cần quyền thực thi, còn gọi là quyền tìm kiếm, trên mọi thư mục trong đường dẫn. Chỉ cần một thư mục thiếu quyền này là mọi thứ bên dưới sẽ bị chặn, và
namei -lcho biết thư mục nào bị thiếu quyền. - Lưu kèm backup hoặc bật file locking sẽ ghi thêm một file bên cạnh file gốc. Vì vậy, các tính năng này cần thư mục có quyền ghi. Backup là tùy chọn
-Bhoặcset backuptrong nanorc; locking là-Ghoặcset locking. Cả hai đều tắt, trừ khi bạn hoặc bản phân phối của bạn đã bật chúng.
Quyền trên thư mục cũng có vai trò tương tự ở những nơi khác trong hệ thống. SSH server sẽ từ chối key nếu home directory hoặc thư mục .ssh của bạn có thể bị user khác ghi vào. Đây là một nguyên nhân phổ biến khiến SSH từ chối key của bạn khi đăng nhập.
Vì nano ghi vào file đã tồn tại, file giữ nguyên inode, tức danh tính của file trên đĩa đứng sau tên file. Mọi tiến trình đang giữ file mở vẫn tiếp tục theo dõi đúng file đó, và một file được bind mount vào container vẫn hoạt động. Các editor lưu bằng cách thay thế file sẽ làm mount đó hỏng, vì mount bám theo inode chứ không bám theo tên file.
Nguyên nhân 3: filesystem ở chế độ chỉ đọc hoặc đã hết dung lượng
findmnt báo ro trong các option nghĩa là thao tác ghi chắc chắn sẽ thất bại. Filesystem có thể đã được mount ở chế độ này, thông qua /etc/fstab hoặc một bind mount chỉ đọc. Hoặc kernel đã remount filesystem ở chế độ chỉ đọc sau khi xảy ra lỗi disk. Trường hợp thứ hai nghiêm trọng hơn. sudo dmesg -T | tail -50 hiển thị các lỗi input/output và filesystem khiến hệ thống remount. Cách sửa là kiểm tra filesystem khi filesystem đã unmount. Trên VPS, việc này có nghĩa là boot vào rescue console của nhà cung cấp.
Filesystem đầy cũng khiến cùng thao tác ghi thất bại, nhưng vì một nguyên nhân khác. df -h bao quát trường hợp thông thường. df -i bao quát trường hợp dễ bị bỏ sót: inode được cấp từ một pool cố định khi filesystem được tạo. Một cây thư mục chứa nhiều file rất nhỏ có thể dùng hết pool này, trong khi df -h vẫn hiển thị còn hàng gigabyte trống. Khi đã hết dung lượng nhưng không thấy file nào chiếm chỗ rõ ràng, df và du không khớp khi disk đầy giải thích trường hợp file đã bị xóa nhưng vẫn đang mở.
Một chi tiết giải thích triệu chứng gây nhầm lẫn này. ext4 dành riêng một phần block cho root khi filesystem được tạo, vì vậy root vẫn có thể ghi sau khi từ chối thao tác ghi của user thông thường. Khi đó sudo có vẻ là cách sửa lỗi, disk tiếp tục đầy phần còn lại, rồi vấn đề quay lại ở mức nghiêm trọng hơn.
Vì nano truncate file trước khi ghi nội dung mới, thao tác ghi hết dung lượng giữa chừng có thể khiến file ngắn hơn trước. Hãy copy config quan trọng trước khi chỉnh sửa trên filesystem gần đầy. sudo cp -a /etc/nginx/nginx.conf /root/nginx.conf.bak giữ nguyên owner, group và permission trên bản copy.
Nguyên nhân 4: bạn đang ở trong container và chỉnh sửa bind mount
Quyền sở hữu file được lưu dưới dạng số. Kernel lưu user ID, còn tên hiển thị đến từ /etc/passwd thực hiện việc tra cứu. Vì vậy, cùng một file có thể hiển thị một tên trên host, nhưng hiển thị tên khác hoặc chỉ hiển thị số bên trong container. Hãy so sánh các ID thay vì tên: chạy id -u bên trong container và ls -ln trên file.
File được bind mount giữ nguyên quyền sở hữu trên host. Khi file trên host thuộc về user của bạn nhưng tiến trình trong container chạy bằng user khác, thao tác ghi sẽ bị từ chối bên trong container. Lệnh sudo bên trong container cũng không thay đổi owner của file trên host. Hãy sửa từ host bằng cách đặt owner thành ID mà container đang chạy, hoặc chạy container bằng ID hiện đang sở hữu các file. Các image từ linuxserver.io và những dự án tương tự cung cấp biến PUID và PGID để đặt user mà tiến trình chạy bằng.
Có thêm 2 trường hợp container cần biết. Mount được đặt ở chế độ chỉ đọc bằng :ro, hoặc container được khởi động bằng --read-only, sẽ từ chối mọi thao tác ghi bất kể quyền sở hữu là gì. Lệnh cat /proc/mounts bên trong container cho biết flag này. Với rootless Podman, user namespace ánh xạ user ID trong container vào một dải ID trên host. Vì vậy, file có vẻ thuộc về root bên trong container thực tế lại thuộc về account không có quyền root của bạn ở bên ngoài.
Cũng có trường hợp chỉnh sửa thành công nhưng sau đó thay đổi biến mất. File được sửa bên trong container, trên một path không phải mount, nằm trong writable layer của container. Layer này bị xóa khi container được tạo lại. Nếu muốn thay đổi được giữ lại, hãy sửa file ở phía host của mount hoặc đưa thay đổi vào bước build image.
Lối thoát: lưu vào đường dẫn bạn sở hữu
Không cố lấy thêm quyền từ bên trong editor. Nhấn Ctrl-O, xóa đường dẫn tại prompt, nhập một đường dẫn bên dưới thư mục home của bạn, chẳng hạn như /home/you/nginx.conf.new, rồi nhấn Enter. Sau đó nhấn Ctrl-X để thoát. File của bạn hiện đã được ghi vào disk, thuộc sở hữu của bạn, và phần còn lại chỉ là thao tác copy file thông thường.
sudo cp /home/you/nginx.conf.new /etc/nginx/nginx.conf
sudo nginx -tỞ đây dùng cp thay vì mv. cp ghi thông qua file đã tồn tại, nên file đó vẫn giữ nguyên owner, group và permissions. mv trên cùng filesystem sẽ thay file đó bằng file của bạn. Khi đó, config tại /etc thuộc user account của bạn, và đây trở thành vấn đề permission tiếp theo cần xử lý.
Kiểm tra kết quả bằng tool quản lý file đó trước khi reload bất kỳ thứ gì. sudo nginx -t parse cấu hình nginx, còn sudo sshd -t parse cấu hình SSH server. Có 2 loại file có editor riêng để thực hiện toàn bộ quy trình này: sudo visudo cho /etc/sudoers và crontab -e cho các cron job của riêng bạn. Mỗi tool chỉnh sửa một bản copy tạm thời, kiểm tra syntax rồi chỉ install file nếu file parse thành công.
FAQ
Tôi nên dùng sudo nano hay sudoedit để chỉnh sửa file hệ thống?
Hãy dùng sudoedit. Lệnh này sao chép file vào một bản tạm do bạn sở hữu, chạy editor dưới user của bạn rồi ghi kết quả trở lại dưới quyền root khi editor thoát. Vì vậy, bản thân editor không bao giờ có quyền root. Đặt SUDO_EDITOR, VISUAL hoặc EDITOR thành nano để chọn nano. sudo nano cũng hoạt động, nhưng cấp cho editor tương tác quyền root truy cập mọi path trên hệ thống trong suốt phiên làm việc. Chỉ cần gõ sai một filename tại prompt lưu là có thể làm hỏng file hệ thống.
Tôi có cần quyền ghi trên directory để lưu file bằng nano không?
Không cần nếu file đã tồn tại. nano ghi trực tiếp vào file, nên kernel kiểm tra write bit trên file và execute bit trên từng directory trong path. Write bit của directory có ý nghĩa khi file chưa tồn tại, vì hệ thống phải tạo một tên mới. Quyền này cũng cần khi bật backup hoặc file locking, vì cả hai đều ghi thêm một file bên cạnh file gốc.
Owner có vẻ đúng và disk không đầy. Còn điều gì có thể chặn thao tác ghi?
Có 4 nguyên nhân. Filesystem có thể đang được mount ở chế độ read-only; findmnt -no OPTIONS -T /etc/nginx/nginx.conf sẽ hiển thị trạng thái này. File có thể có immutable attribute; lsattr hiển thị attribute đó và sudo chattr -i gỡ bỏ nó. Ngay cả root cũng không thể ghi file khi attribute này đang được bật. Inode pool có thể đã hết dù vẫn còn free space; df -i sẽ hiển thị tình trạng này. SELinux hoặc AppArmor có thể từ chối thao tác ghi dù permission bits cho phép. Audit log sẽ ghi lại lần từ chối đối với path bạn đã thử ghi.
Tôi phải lưu thay đổi ở đâu khi file hoàn toàn không thể lưu?
Nhấn Ctrl-O rồi nhập một path do bạn sở hữu, bên dưới home directory hoặc bất kỳ nơi nào user của bạn có quyền ghi. Buffer vẫn còn trong memory, nên những gì bạn đã nhập không bị mất. Sau đó dùng sudo cp để chép file đã lưu vào đúng vị trí. Lệnh này giữ nguyên owner và permissions của file gốc. Tiếp theo, kiểm tra file bằng test command của service trước khi reload service.
Vì sao các thay đổi trong Docker container của tôi biến mất?
Khi path không phải là mount, thay đổi được ghi vào writable layer của container đó. Layer này bị xóa khi container được thay thế. Hãy chỉnh sửa file ở phía host của bind mount hoặc volume, hoặc build file đó vào image. Nếu path là bind mount nhưng thao tác lưu bị từ chối, hãy so sánh id -u bên trong container với numeric owner từ ls -ln. File giữ nguyên ownership của host, nên process trong container phải có owner tương ứng.