رفع خطای Permission denied هنگام ذخیره فایل در nano
اگر هنگام ذخیره فایل در nano با خطای Permission denied مواجه میشوید، این راهنما 4 دلیل اصلی شامل مالکیت فایل، مجوز دایرکتوری، وضعیت read-only و محدودیت کانتینر را بررسی میکند.
چرا nano فایل شما را ذخیره نمیکند
دلیل عدم ذخیره فایل در nano یکی از چهار مورد زیر است: شما مالک فایل نیستید، دایرکتوری والد اجازه عملیاتی که nano قصد انجام آن را دارد نمیدهد، فایلسیستم فقطخواندنی (read-only) است یا فضای خالی ندارد، یا اینکه در حال اجرای کانتینری هستید که با یک User ID متفاوت اجرا میشود. دو مورد اول مشکلات دسترسی (permission) هستند و دو مورد آخر خیر. آنها را به همین ترتیب بررسی کنید؛ زیرا دلیل اول اکثر موارد را پوشش میدهد، تأیید آن تنها با یک دستور ممکن است و راه حل آن به جای sudo nano، استفاده از sudoedit است.
تا زمانی که ویرایشگر باز است، چیزی از دست نمیرود. متن شما در حافظه قرار دارد؛ بنابراین میتوانید فایل را باز نگه دارید، بافر را در مسیری که مالک آن هستید ذخیره کنید و سپس آن را به جای اصلی منتقل کنید. این راهکار خروج در انتهای این راهنما آمده است.
پیش از تغییر هرگونه مجوز، این بررسیها را انجام دهید
هر دستور را روی مسیر واقعی که در حال ویرایش آن هستید اجرا کنید. این دستورات به پرسشهای متفاوتی پاسخ میدهند، بنابراین پیش از انجام هر تغییری، همه آنها را اجرا کنید. تغییر مجوزها پیش از آنکه بدانید کدام بررسی با شکست مواجه شده است، معمولاً مشکل دومی را به مشکل اول اضافه میکند.
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 شناسه کاربری و شناسههای گروهی که در حال حاضر دارید را چاپ میکند. ls -l مالک، گروه و بیتهای مجوز خود فایل را نشان میدهد. ls -ld همین موارد را برای دایرکتوری حاوی فایل نشان میدهد که پرسشی جداگانه با پاسخی متفاوت است. namei -l تمام بخشهای مسیر را پیمایش کرده و مالک و مجوزهای هر بخش را فهرست میکند، بنابراین به هر دو پرسش در یک خروجی پاسخ میدهد. findmnt نام سیستم فایلِ زیر آن مسیر و گزینههایی که با آن mount شده است را مشخص میکند. df -h فضای آزاد را گزارش میدهد و df -i تعداد inodeهای آزاد را گزارش میکند که تمام شدن آنها مستقل از فضای دیسک رخ میدهد. اگر این رشتههای مجوز هنوز برای شما آشنا نیستند، با نحوه خواندن رشته مجوز که ls -l چاپ میکند شروع کنید.
دلیل 1: فایل متعلق به root است و شما root نیستید
خواندن و نوشتن مجوزهای جداگانهای هستند و اکثر فایلها در /etc برای همه قابل خواندن هستند. به همین دلیل است که nano فایل را باز میکند، محتویات را به شما نشان میدهد و اجازه میدهد آزادانه تایپ کنید: هیچکدام از این کارها با دیسک تعامل ندارند. امتناع در زمان ذخیرهسازی رخ میدهد، یعنی وقتی که kernel شناسه کاربری و شناسههای گروه شما را با مالک، گروه و بیتهای دیگر فایل مقایسه میکند. nano فقط پیامی را که kernel به آن داده است منتقل میکند، بنابراین هیچ گزینهای در nano نتیجه را تغییر نمیدهد.
id و ls -l با هم این موضوع را تعیین میکنند. مالک فایل root است، شما root نیستید و بیتهای دیگر مجوز نوشتن را به شما نمیدهند. فشردن مجدد Ctrl-O کمکی نخواهد کرد.
چرا sudoedit روش صحیح ویرایش فایلهای متعلق به root است
SUDO_EDITOR=nano sudoedit /etc/nginx/nginx.confدستور sudo یک کپی موقت از فایل تهیه میکند و مالکیت آن را به شما تغییر میدهد، سپس nano را به عنوان کاربر عادی روی آن کپی اجرا میکند و پس از خروج از ویرایشگر، نتیجه را با دسترسی root در محل اصلی جایگذاری میکند. ویرایشگر هرگز با دسترسی root اجرا نمیشود. sudo -e همان دستور با نامی متفاوت است. ویرایشگر از میان SUDO_EDITOR، سپس VISUAL و در نهایت EDITOR انتخاب میشود، بنابراین تنظیم export EDITOR=nano در پروفایل shell، آن را به انتخاب پیشفرض در همه جا تبدیل میکند. اگر در فایل sudoers شما پرچم env_editor غیرفعال باشد، این متغیرها نادیده گرفته شده و ویرایشگر از تنظیم editor در sudoers فراخوانی میشود.
دستور sudo nano نیز فایل را ذخیره میکند و مشکل دقیقاً همینجاست. این دستور به یک ویرایشگر تعاملی کامل اجازه میدهد تا زمانی که نشست باز است، دسترسی root بر کل سیستم فایل داشته باشد؛ بنابراین یک اشتباه تایپی در مسیر فایل هنگام ذخیرهسازی، باعث میشود متن شما به عنوان root روی یک فایل سیستمی دیگر بازنویسی شود. کار کردن به عنوان یک کاربر عادی که فقط برای مراحل ضروری از sudo استفاده میکند عادتی است که ارزش ساختن دارد و sudoedit همان ابزاری است که در لحظه ویرایش فایلهای پیکربندی، این عادت را پیاده میکند.
دو قانون در sudoedit وجود دارد که معمولاً کاربران را غافلگیر میکند. این دستور از ویرایش symbolic link خودداری میکند و همچنین اجازه ویرایش فایلی که در یک دایرکتوری با دسترسی نوشتن برای شما قرار دارد را نمیدهد (مگر اینکه خودتان root باشید). قانون دوم به این دلیل وجود دارد که هر کسی که به دایرکتوری دسترسی نوشتن داشته باشد، میتواند در حین باز بودن ویرایشگر، فایل را جایگزین کند. هر دو رفتار، تنظیمات پیشفرض sudoers هستند (sudoedit_follow خاموش، sudoedit_checkdir روشن). فایلی که هنوز وجود ندارد، برای شما ایجاد خواهد شد.
دلیل 2: آنچه دایرکتوری والد واقعاً کنترل میکند
توصیههایی که برای سایر ویرایشگرها نوشته شده است میگوید که ذخیرهسازی به مجوز نوشتن در دایرکتوری نیاز دارد، زیرا بسیاری از ویرایشگرها با نوشتن یک فایل جدید و تغییر نام آن روی فایل قدیمی، عملیات ذخیره را انجام میدهند. nano به این صورت عمل نمیکند. این برنامه فایلی را که نام بردهاید باز کرده و مستقیماً در همان فایل مینویسد، بنابراین برای فایلی که از قبل وجود دارد، بیت نوشتن (write bit) دایرکتوری هرگز بررسی نمیشود.
دایرکتوری همچنان موارد دیگری را تعیین میکند، و به همین دلیل است که ls -ld در چکلیست قرار دارد:
- ایجاد فایلی که هنوز وجود ندارد، نیازمند مجوز نوشتن و اجرا روی دایرکتوری است، زیرا باید نام جدیدی به آن اضافه شود. umask مجوزهایی را که فایل جدید با آن شروع میشود، تعیین میکند.
- دسترسی به فایل در هر صورت نیازمند مجوز اجرا (که مجوز جستجو نیز نامیده میشود) در تمام دایرکتوریهای موجود در مسیر است. فقدان این مجوز در هر دایرکتوری، دسترسی به تمام محتویات زیرمجموعه آن را مسدود میکند و
namei -lبه شما نشان میدهد که کدام دایرکتوری دچار مشکل است. - ذخیرهسازی با قابلیت پشتیبانگیری (backup) یا قفلگذاری فایل (file locking)، یک فایل دوم در کنار فایل اصلی ایجاد میکند، بنابراین این قابلیتها به دایرکتوری با قابلیت نوشتن نیاز دارند. پشتیبانگیری همان گزینه
-Bیاset backupدر فایل nanorc است و قفلگذاری توسط-Gیاset lockingکنترل میشود. هر دوی این قابلیتها تا زمانی که شما یا توزیع سیستمعاملتان آنها را فعال نکرده باشید، غیرفعال هستند.
مجوزهای دایرکتوری در سایر بخشهای سیستم نیز اهمیت مشابهی دارند. سرور SSH زمانی که دایرکتوری home شما یا دایرکتوری .ssh شما توسط سایر کاربران قابل نوشتن باشد، کلید را رد میکند؛ این یکی از دلایل رایج رد شدن کلید شما توسط SSH در هنگام ورود است.
از آنجا که nano مستقیماً در فایلی که از قبل وجود دارد مینویسد، فایل inode خود را حفظ میکند؛ inode همان هویت فایل روی دیسک است که پشت نام آن قرار دارد. هر فرآیندی که فایل را باز نگه داشته باشد، همچنان آن را دنبال میکند و فایلی که به صورت bind mount در یک کانتینر قرار گرفته است، بدون مشکل به کار خود ادامه میدهد. ویرایشگرهایی که با جایگزینی فایل ذخیرهسازی میکنند، آن mount را میشکنند، زیرا mount از inode پیروی میکند و نه از نام فایل.
دلیل 3: سیستم فایل فقطخواندنی است یا فضای خالی ندارد
گزارش findmnt ro در گزینهها به این معناست که عملیات نوشتن از ابتدا با شکست مواجه میشده است. سیستم فایل یا از طریق /etc/fstab یا یک bind mount فقطخواندنی به این صورت mount شده است، یا هسته (kernel) پس از بروز خطای دیسک، آن را به حالت فقطخواندنی remount کرده است. حالت دوم جدیتر است. sudo dmesg -T | tail -50 خطاهای ورودی/خروجی و سیستم فایل که منجر به این remount شدهاند را نشان میدهد و راه حل آن، بررسی سیستم فایل (filesystem check) در حالت unmount است که در VPS به معنای بوت کردن کنسول نجات (rescue console) ارائهدهنده است.
پر شدن سیستم فایل نیز باعث شکست عملیات نوشتن به دلیلی متفاوت میشود. df -h موارد عادی را پوشش میدهد. df -i موردی را پوشش میدهد که معمولاً نادیده گرفته میشود: inodeها از یک مخزن ثابت که هنگام ایجاد سیستم فایل ساخته شده است تأمین میشوند و درختی از فایلهای بسیار کوچک میتواند تمام آنها را مصرف کند، در حالی که df -h همچنان گیگابایتهای خالی را نشان میدهد. وقتی فضا تمام شده و هیچ عامل واضحی آن را اشغال نکرده است، عدم تطابق df و du در دیسک پر به فایلهای حذفشدهای میپردازد که همچنان باز هستند و باعث این مشکل میشوند.
یک جزئیات، نشانهای گیجکننده در اینجا را توضیح میدهد. سیستم فایل ext4 بخشی از بلاکهای خود را هنگام ایجاد برای root رزرو میکند، بنابراین root پس از اینکه کاربران عادی با عدم دسترسی مواجه شدند، همچنان میتواند بنویسد. در این حالت sudo مانند راه حل به نظر میرسد، اما دیسک تا انتها پر میشود و مشکل با شدتی بیشتر بازمیگردد.
از آنجا که nano پیش از نوشتن محتوای جدید، فایل را truncate میکند، عملیاتی که در میانه راه با کمبود فضا مواجه شود میتواند فایل را کوتاهتر از حالت قبل باقی بگذارد. پیش از ویرایش فایلی که برایتان اهمیت دارد روی سیستم فایلی که تقریباً پر است، از آن کپی بگیرید. sudo cp -a /etc/nginx/nginx.conf /root/nginx.conf.bak مالک، گروه و مجوزهای فایل را در کپی حفظ میکند.
علت 4: شما در یک کانتینر در حال ویرایش یک bind mount هستید
مالکیت فایل بهصورت عددی است. هسته سیستمعامل یک شناسه کاربری (UID) را ذخیره میکند و نامی که شما مشاهده میکنید، از طریق هر /etc/passwd که جستجو را انجام میدهد، نمایش داده میشود. بنابراین یک فایل ممکن است روی میزبان با یک نام و داخل کانتینر با نامی متفاوت یا فقط بهصورت یک عدد نمایش داده شود. بهجای نامها، اعداد را مقایسه کنید: id -u را داخل کانتینر و ls -ln را روی فایل اجرا کنید.
یک فایل bind mount شده، مالکیتی را که روی میزبان دارد حفظ میکند. زمانی که فایل میزبان متعلق به کاربر شما باشد و پردازش کانتینر با کاربری متفاوت اجرا شود، عملیات نوشتن داخل کانتینر رد میشود و sudo داخل کانتینر، مالک را روی میزبان تغییر نمیدهد. این مشکل را از سمت میزبان با تنظیم مالکیت به شناسهای که کانتینر با آن اجرا میشود حل کنید، یا کانتینر را با همان شناسهای اجرا کنید که از قبل مالک فایلهاست. ایمیجهای linuxserver.io و پروژههای مشابه، متغیرهای PUID و PGID را ارائه میدهند که تعیین میکنند پردازش با چه کاربری اجرا شود.
دو مورد دیگر در کانتینرها وجود دارد که دانستن آنها مفید است. یک mount که با :ro فقطخواندنی شده است، یا کانتینری که با --read-only شروع شده، صرفنظر از وضعیت مالکیت، از نوشتن جلوگیری میکند و cat /proc/mounts داخل کانتینر این پرچم (flag) را نشان میدهد. در Podman بدون دسترسی root، یک user namespace شناسههای کاربری کانتینر را به محدودهای از شناسههای میزبان نگاشت میکند؛ بنابراین فایلی که داخل کانتینر متعلق به root به نظر میرسد، در خارج از آن متعلق به حساب کاربری بدون دسترسی (unprivileged) شماست.
همچنین ویرایشی وجود دارد که انجام میشود اما ناپدید میگردد. فایلی که داخل کانتینر در مسیری که mount نیست تغییر میدهید، در لایه قابلنوشتن (writable layer) همان کانتینر قرار دارد و با بازسازی کانتینر، آن لایه حذف میشود. اگر قرار است تغییرات ماندگار باشند، فایل را در سمت میزبانِ mount یا در مرحله build ایمیج تغییر دهید.
راه گریز: ذخیره در مسیری که مالک آن هستید
تلاش نکنید از داخل ویرایشگر سطح دسترسی خود را ارتقا دهید. کلیدهای Ctrl-O را فشار دهید، مسیر موجود در اعلان را پاک کنید، مسیری در دایرکتوری خانگی خود مانند /home/you/nginx.conf.new را وارد کرده و Enter بزنید. سپس با Ctrl-X خارج شوید. اکنون کار شما روی دیسک ذخیره شده و مالک آن خودتان هستید؛ بقیه مراحل صرفاً یک کپی فایل معمولی است.
sudo cp /home/you/nginx.conf.new /etc/nginx/nginx.conf
sudo nginx -tدر اینجا به جای mv از cp استفاده کنید. cp محتوا را درون فایلی که از قبل وجود دارد مینویسد، بنابراین آن فایل مالک، گروه و مجوزهای دسترسی خود را حفظ میکند. mv در همان فایلسیستم، فایل را با فایل شما جایگزین میکند که باعث میشود فایل پیکربندی در /etc تحت مالکیت حساب کاربری شما قرار بگیرد و این خود به مشکل مجوز جدیدی تبدیل میشود که باید آن را حل کنید.
پیش از بارگذاری مجدد هر چیزی، نتیجه را با ابزاری که مالک فایل است بررسی کنید. sudo nginx -t پیکربندی nginx و sudo sshd -t پیکربندی سرور SSH را تحلیل (parse) میکند. دو فایل وجود دارند که ویرایشگرهای اختصاصی دارند و تمام این مراحل را برای شما انجام میدهند: sudo visudo برای /etc/sudoers و crontab -e برای cron jobهای شخصی شما. هر کدام یک کپی موقت را ویرایش کرده، نحو (syntax) را بررسی میکنند و تنها در صورتی که فایل بدون خطا تحلیل شود، آن را جایگزین میکنند.
FAQ
آیا باید برای ویرایش فایلهای سیستمی از sudo nano استفاده کنم یا sudoedit؟
از sudoedit استفاده کنید. این دستور فایل را به یک نسخه موقت که مالک آن شما هستید کپی میکند، ویرایشگر را با دسترسی کاربر خودتان اجرا میکند و پس از خروج از ویرایشگر، نتیجه را با دسترسی root ذخیره میکند؛ بنابراین خودِ ویرایشگر هرگز دسترسی root نخواهد داشت. برای انتخاب nano، متغیر SUDO_EDITOR، VISUAL یا EDITOR را روی nano تنظیم کنید. استفاده از sudo nano نیز ممکن است، اما این دستور یک ویرایشگر تعاملی را با دسترسی root به تمام مسیرهای سیستم در طول نشست اجرا میکند؛ این یعنی یک اشتباه تایپی در نام فایل هنگام ذخیره، میتواند منجر به آسیب دیدن یک فایل سیستمی شود.
آیا برای ذخیره فایل با nano به دسترسی نوشتن در دایرکتوری نیاز دارم؟
خیر، برای فایلی که از قبل وجود دارد نیازی نیست. nano مستقیماً در خودِ فایل مینویسد، بنابراین هسته سیستمعامل بیت نوشتن روی فایل و بیت اجرای تمام دایرکتوریهای موجود در مسیر را بررسی میکند. بیت نوشتن دایرکتوری تنها زمانی اهمیت دارد که فایل هنوز وجود نداشته باشد (چون باید نام جدیدی ایجاد شود) یا زمانی که قابلیت پشتیبانگیری یا قفلگذاری فایل فعال باشد، زیرا هر دو مورد یک فایل دوم در کنار فایل اصلی ایجاد میکنند.
مالکیت فایل درست است و دیسک هم پر نیست. چه چیز دیگری میتواند مانع نوشتن شود؟
چهار مورد. ممکن است فایلسیستم به صورت read-only مونت شده باشد که با findmnt -no OPTIONS -T /etc/nginx/nginx.conf قابل مشاهده است. ممکن است فایل دارای ویژگی immutable باشد که با lsattr دیده شده و با sudo chattr -i حذف میشود؛ تا زمانی که این ویژگی فعال باشد، حتی root هم نمیتواند فایل را تغییر دهد. ممکن است با وجود فضای خالی، استخر inodeها تمام شده باشد که با df -i مشخص میشود. همچنین ممکن است SELinux یا AppArmor با وجود درست بودن مجوزها، مانع نوشتن شوند که در این صورت لاگ audit، این ممانعت را برای مسیر مورد نظر ثبت میکند.
وقتی فایل به هیچ وجه ذخیره نمیشود، تغییراتم را کجا قرار دهم؟
کلیدهای Ctrl-O را بزنید و مسیری را که مالک آن هستید (در دایرکتوری home یا هر جای دیگری که دسترسی نوشتن دارید) وارد کنید. بافر همچنان در حافظه باقی است، بنابراین چیزی از دست نمیرود. پس از آن، فایل ذخیرهشده را با sudo cp به جای اصلی منتقل کنید تا مالکیت و مجوزهای فایل اصلی حفظ شود، سپس پیش از reload کردن سرویس، آن را با دستور تست مخصوص همان سرویس بررسی کنید.
چرا ویرایشهای من داخل کانتینر Docker ناپدید میشوند؟
وقتی مسیر مورد نظر یک mount نباشد، ویرایش در لایه قابلنوشتن (writable layer) کانتینر ذخیره میشود و با جایگزینی کانتینر، آن لایه حذف میگردد. فایل را در سمت host و در یک bind mount یا volume ویرایش کنید، یا آن را در image بسازید. اگر مسیر یک bind mount است و ذخیره فایل رد میشود، خروجی id -u داخل کانتینر را با مالک عددی در ls -ln مقایسه کنید: فایل مالکیت خود را از سمت host حفظ میکند و پردازش کانتینر باید با آن مطابقت داشته باشد.