تفاوت chmod 755 و u+x در لینوکس
تفاوت دقیق استفاده از حالت عددی اکتال و نمادین در دستور chmod را بیاموزید. بفهمید چرا استفاده از 755 به صورت بازگشتی برای دایرکتوریها خطرناک است و چگونه X بزرگ مشکل را حل میکند.
تغییرات chmod
chmod دستور chmod بیتهای دسترسی یک فایل یا دایرکتوری را تغییر میدهد و این بیتها را در یکی از دو قالب عددی یا نمادین میپذیرد. قالب عددی یک عدد اکتال (هشتهشتی) است که تمام بیتهای دسترسی را بهطور همزمان جایگزین میکند. قالب نمادین، کلاس کاربر و یک عملگر را مشخص میکند و تنها بیتهای تعیینشده را ویرایش مینماید. هر دو قالب در نهایت به یک system call واحد ختم میشوند. تفاوت در این است که چه اتفاقی برای بیتهایی که به آنها اشاره نکردهاید، میافتد.
خواندن یک mode و تغییر دادن آن، دو وظیفه متفاوت هستند. اگر حروف در یک لیست طولانی (long listing) هنوز برایتان واضح نیست، ابتدا معنای drwxr-xr-x را بخش به بخش بخوانید. این راهنما فقط به تغییر آنها میپردازد.
تمام دستورات اینجا را با کاربر معمولی خود اجرا کنید. حساب کاربری root این بررسیها را دور میزند، بنابراین نمایش مجوزها اگر با دسترسی root انجام شود، هیچ چیزی را اثبات نمیکند. اگر هنوز روی یک سرور جدید با کاربر root کار میکنید، یک کاربر عادی با حداقل دسترسی بسازید و سپس بازگردید.
راهاندازی یک محیط sandbox موقت
cd "$(mktemp -d)"
touch notes.txtدستور mktemp -d یک دایرکتوری خالی در مسیر /tmp ایجاد کرده و مسیر ساختهشده را چاپ میکند، و جایگزینی دستور آن مسیر را مستقیماً به cd میفرستد. هیچکدام از دستورات زیر با فایلهای مهم شما تداخلی ندارند.
یک دستور، نتیجهٔ هر تغییر را گزارش میدهد:
stat -c '%a %A %n' notes.txtدستور %a حالت (mode) را به صورت عدد هشتهشتی (octal)، دستور %A همان حالت را به صورت حروف (مشابه خروجی long listing) و دستور %n نام فایل را چاپ میکند. در بلوکهای زیر، خطی که با $ شروع میشود دستوری است که شما تایپ میکنید و خط زیر آن، خروجی همان دستور است. آنها را شخصاً اجرا کنید. خروجی روی سیستم شما باید خطبهخط مطابقت داشته باشد، زیرا حالت عددی به هیچچیز در محیط شما وابسته نیست.
شکل عددی: یک رقم اکتال برای هر کلاس
رقم اول متعلق به مالک فایل است. رقم دوم متعلق به گروه فایل است. رقم سوم متعلق به سایر کاربران است. هر رقم مجموعی از مقادیر است که هر مقدار مربوط به یک بیت میشود. هر بار یک بیت را تنظیم کنید و خروجی stat را مشاهده کنید.
$ chmod 400 notes.txt
$ stat -c '%a %A %n' notes.txt
400 -r-------- notes.txt
$ chmod 200 notes.txt
$ stat -c '%a %A %n' notes.txt
200 --w------- notes.txt
$ chmod 100 notes.txt
$ stat -c '%a %A %n' notes.txt
100 ---x------ notes.txtحروف با ارقام مطابقت دارند. پس از کاراکتر نوع فایل در ابتدای رشته، گروه اول سه حرفی متعلق به مالک، سه حرف بعدی متعلق به گروه و سه حرف آخر متعلق به سایر کاربران است. برای ترکیب بیتها در یک کلاس، مقادیر را با هم جمع کنید.
$ chmod 600 notes.txt
$ stat -c '%a %A %n' notes.txt
600 -rw------- notes.txt
$ chmod 700 notes.txt
$ stat -c '%a %A %n' notes.txt
700 -rwx------ notes.txt
$ chmod 750 notes.txt
$ stat -c '%a %A %n' notes.txt
750 -rwxr-x--- notes.txtحالت عددی مطلق است. این حالت در هر بار اجرا، تمام 9 بیت را بازنویسی میکند، بنابراین کلاسی که به آن فکر نمیکردید نیز تغییر میکند.
$ chmod 755 notes.txt
$ stat -c '%a %A %n' notes.txt
755 -rwxr-xr-x notes.txt
$ chmod 600 notes.txt
$ stat -c '%a %A %n' notes.txt
600 -rw------- notes.txtدستور دوم هیچ اشارهای به گروه یا سایر کاربران نکرد، اما بیتهای آنها به هر حال تغییر کرد. این ویژگی شکل عددی است و به همین دلیل است که شکل عددی برای ویرایشهای جزئی ابزار مناسبی نیست. (chmod همچنین بیتهای setuid، setgid و sticky را مدیریت میکند که در یک رقم چهارم پیش از این سه رقم قرار میگیرند. این موضوع بحث جداگانهای است و این راهنما تنها به همان سه رقم میپردازد.)
فرم نمادین: یک کلاس، یک عملگر، سپس بیتها
ابتدا کلاس را بنویسید. u مالک است، g گروه است، o سایر کاربران است و a همه آنها با هم است. سپس عملگر: + بیتها را اضافه میکند، - بیتها را حذف میکند و = آن کلاس را دقیقاً روی بیتهایی که لیست میکنید تنظیم کرده و بقیه بیتهای آن را پاک میکند. سپس خود بیتها، به صورت r، w یا x.
$ chmod 640 notes.txt
$ stat -c '%a %A %n' notes.txt
640 -rw-r----- notes.txt
$ chmod u+x notes.txt
$ stat -c '%a %A %n' notes.txt
740 -rwxr----- notes.txt
$ chmod g-r notes.txt
$ stat -c '%a %A %n' notes.txt
700 -rwx------ notes.txt
$ chmod o=r notes.txt
$ stat -c '%a %A %n' notes.txt
704 -rwx---r-- notes.txtهر دستور یک کلاس را تغییر میدهد و بقیه را به همان حال رها میکند. بندها را با کاما و بدون فاصله ترکیب کنید؛ chmod آنها را از چپ به راست اعمال میکند.
$ chmod u=rw,go=r notes.txt
$ stat -c '%a %A %n' notes.txt
644 -rw-r--r-- notes.txt
$ chmod go= notes.txt
$ stat -c '%a %A %n' notes.txt
600 -rw------- notes.txtgo= بدون هیچ چیزی بعد از علامت مساوی، تمام بیتها را از گروه و سایر کاربران میگیرد. این همان حالتی است که برای فایلی میخواهید که فقط مالک آن باید بتواند آن را بخواند، مانند بخش خصوصی یک جفت کلید SSH.
دستور chmod همچنین میتواند گزارش کار خود را ارائه دهد، که در اسکریپتهایی که کسی صفحه نمایش را نمیبیند، اهمیت دارد.
chmod -v u+x notes.txt
chmod -v u+x notes.txt-v برای هر فایلی که به آن داده شده، یک خط چاپ میکند. هر دو دستور را اجرا کنید و دو خط خروجی را روی صفحه خود مقایسه کنید: اولی نام فایل را ذکر کرده و حالتی که از آن تغییر کرده و حالتی که به آن تغییر یافته را گزارش میدهد، و دومی حالت را به عنوان «حفظشده» گزارش میدهد، زیرا بیتی که از آن خواسته شده بود اضافه شود، از قبل تنظیم شده بود. -c فقط زمانی یک خط چاپ میکند که فایل واقعاً تغییر کرده باشد، بنابراین گزینه بهتری برای نگهداری در لاگ است.
مقایسه chmod 755 و u+x: از کدام نماد استفاده کنیم
کار را با یک فایل مشابه در دو حالت شروع کنید و ببینید هر نماد چه تغییری ایجاد میکند.
$ chmod 640 notes.txt
$ chmod 755 notes.txt
$ stat -c '%a %A %n' notes.txt
755 -rwxr-xr-x notes.txt
$ chmod 640 notes.txt
$ chmod u+x notes.txt
$ stat -c '%a %A %n' notes.txt
740 -rwxr----- notes.txtهر دو دستور، فایل را برای مالک آن قابلاجرا کردند. تنها یکی از آنها دو گروه دیگر (گروه و سایرین) را به همان شکلی که بودند باقی گذاشت. زمانی که وضعیت نهایی دقیق مورد نظر خود را میدانید و میخواهید آن را اعمال کنید، از حالت عددی استفاده کنید؛ این رویکرد در اسکریپتهای استقرار که باید روی ماشینی که قبلاً ندیدهاید نتیجه یکسانی تولید کنند، صادقانه و دقیق است. زمانی که در ذهن خود عبارت «اضافه کردن» یا «حذف کردن» دارید، از حالت نمادین استفاده کنید، زیرا در حالت عددی راهی برای گفتن «آن بخش را تغییر نده» وجود ندارد.
حالتی که یک فایل جدید با آن ایجاد میشود، اصلاً از chmod نمیآید. این حالت از umask فرآیندی که فایل را ایجاد کرده است ناشی میشود. دستورات umask و umask -S را در shell خود اجرا کنید تا مقدار فعال را ببینید. انتظار داشته باشید که این مقدار بین توزیعهای مختلف، بین shell ورود شما و یک سرویس systemd، و بین یک حساب کاربری و دیگری متفاوت باشد. مقدار umask حالت هر فایل جدید را تعیین میکند، بنابراین پیش از آنکه chmod اجرا شود، این مقدار تصمیم میگیرد که نقطه شروع شما چیست.
عملکرد بیت execute روی دایرکتوری
این سوءتفاهمی است که بیشترین زمان را هدر میدهد. روی یک فایل معمولی، بیت execute به هسته سیستمعامل میگوید که میتواند فایل را اجرا کند. یک دایرکتوری حاوی هیچ دستوری نیست، بنابراین چیزی در آن برای اجرا وجود ندارد. روی یک دایرکتوری، بیت execute به معنای پیمایش (traversal) است: یعنی مجوز برای حل کردن (resolve) یک نام در داخل آن. برای دسترسی به dir/file، شما به این بیت روی dir نیاز دارید. بیت read یک مجوز جداگانه است و اجازه میدهد نامهای موجود در دایرکتوری را لیست کنید. شما میتوانید یکی از این دو را بدون دیگری داشته باشید؛ موضوعی که پس از مشاهده عملی آن، پذیرشش آسانتر میشود.
$ mkdir vault
$ printf 'hello\n' > vault/secret.txt
$ chmod 600 vault/secret.txt
$ chmod 100 vault
$ echo vault/*
vault/*
$ cat vault/secret.txt
helloشل الگو را بدون تغییر چاپ کرد، زیرا گسترش vault/* به معنای خواندن دایرکتوری است و یک glob که با هیچچیز مطابقت ندارد به جای ایجاد خطا، به صورت متن تحتاللفظی عبور داده میشود. دستور cat همچنان کار کرد، زیرا شما خودتان نام را ارائه دادید و اجازه پیمایش وجود داشت. حالا این دو بیت را با هم جابهجا کنید.
$ chmod 400 vault
$ echo vault/*
vault/secret.txt
$ cat vault/secret.txt
cat: vault/secret.txt: Permission deniedنامها قابل مشاهده هستند اما محتویات در دسترس نیستند. مجوز Read به شما لیست را میدهد. مجوز Execute به شما راه ورود را میدهد. تقریباً هر دایرکتوری که استفاده میکنید به هر دو نیاز دارد.
$ chmod 500 vault
$ cat vault/secret.txt
helloچرا استفاده از chmod -R با یک mode عددی به ساختار فایلها آسیب میزند
-R کل درخت فایلها را پیمایش کرده و mode تعیینشده را به هر چیزی که با آن مواجه شود، اعمال میکند. یک دایرکتوری و یک فایل داده به بیتهای متفاوتی نیاز دارند، بنابراین یک عدد مطلق نمیتواند برای هر دو صحیح باشد. یک درخت کوچک بسازید و خراب شدن آن را مشاهده کنید.
$ mkdir -p site/css
$ touch site/index.html site/css/main.css
$ printf '#!/bin/bash\necho hi\n' > site/deploy.sh
$ chmod 755 site site/css site/deploy.sh
$ chmod 644 site/index.html site/css/main.css
$ stat -c '%a %A %n' site site/css site/index.html site/css/main.css site/deploy.sh
755 drwxr-xr-x site
755 drwxr-xr-x site/css
644 -rw-r--r-- site/index.html
644 -rw-r--r-- site/css/main.css
755 -rwxr-xr-x site/deploy.shاین همان ساختاری است که یک دایرکتوری شامل فایلهای ایستا به آن نیاز دارد. در اینجا اولین اشتباه بازگشتی (recursive) را میبینید.
$ chmod -R 755 site
$ stat -c '%a %A %n' site/index.html site/css/main.css
755 -rwxr-xr-x site/index.html
755 -rwxr-xr-x site/css/main.cssهیچچیز خراب نشد و مشکل دقیقاً همینجاست. این اشتباه بیصدا است، بنابراین سالها در دستورالعملهای کپیشده باقی میماند. اکنون دو فایل داده ادعا میکنند که برنامه هستند. Git بیت اجرایی را ذخیره میکند، بنابراین commit بعدی شما این تغییر را برای هر کسی که مخزن را clone کند به همراه میبرد و rsync و tar آن را به هر ماشینی که درخت را به آن کپی کنید، منتقل میکنند.
دومین اشتباه بازگشتی پرصدا است. یک mode بدون بیت اجرایی را به یک دایرکتوری اعمال کنید تا دایرکتوری بسته شود.
$ chmod 644 site
$ stat -c '%a %A %n' site
644 drw-r--r-- site
$ echo site/*
site/css site/deploy.sh site/index.html
$ cd site
bash: cd: site: Permission denied
$ cat site/index.html
cat: site/index.html: Permission deniedنامها همچنان لیست میشوند زیرا بیت خواندن (read) باقی مانده است، اما تمام مسیرهای داخل دایرکتوری بسته شدهاند زیرا بیت اجرایی (execute) حذف شده است. chmod -R 644 site این کار را با یک دستور برای تمام دایرکتوریهای موجود در درخت انجام میدهد. آنچه پس از آن میبینید همیشه یکسان نیست، زیرا chmod مجبور است از همان دایرکتوریهایی عبور کند که دسترسی پیمایش آنها را سلب کرده است؛ بنابراین بهجای فرض کردن، با find site -type d -exec stat -c '%a %n' {} + بررسی کنید که واقعاً چه تغییری اعمال شده است. پیش از ادامه، این مورد را به حالت قبل برگردانید.
$ chmod 755 site
$ chmod 644 site/index.html site/css/main.css
$ stat -c '%a %A %n' site site/index.html
755 drwxr-xr-x site
644 -rw-r--r-- site/index.htmlیک chmod بازگشتی قابل بازگشت (undo) نیست. chmod ثبت نمیکند که چه چیزی را جایگزین کرده است، بنابراین تعمیر آن بر عهده شماست تا modeها را بر اساس آنچه تصور میکنید قبلاً بودهاند، بازسازی کنید. این کار همانند بازسازی فایلها پس از یک rm -rf تصادفی، کند و نامطمئن است و درس آن نیز همان است: پیش از فشردن کلید Enter، هدف یک دستور بازگشتی را بررسی کنید.
حرف X بزرگ: اجرا روی دایرکتوریها، نه روی تکتک فایلها
حالتهای نمادین (Symbolic modes) علاوه بر X، x را نیز میپذیرند. X بیت اجرا (execute bit) را فقط در جایی تنظیم میکند که منطقی باشد: روی یک دایرکتوری، یا روی یک فایل معمولی که از قبل برای یکی از کلاسها دارای بیت اجرا است. این قانون دقیقاً همان قاعدهای است که میخواستید -R از آن پیروی کند.
$ chmod -R u=rwX,go=rX site
$ stat -c '%a %A %n' site site/css site/index.html site/css/main.css site/deploy.sh
755 drwxr-xr-x site
755 drwxr-xr-x site/css
644 -rw-r--r-- site/index.html
644 -rw-r--r-- site/css/main.css
755 -rwxr-xr-x site/deploy.shبا یک دستور، دایرکتوریها همچنان قابل پیمایش باقی ماندند در حالی که فایلهای داده دستنخورده رها شدند. اسکریپت بیت اجرای خود را حفظ کرد زیرا از قبل آن را داشت، که این نیمه دوم قانون X است. X وضعیت فعلی بیتهای فایل را میخواند، بنابراین فایلی که تمام بیتهای اجرای خود را از دست داده باشد، با این دستور قابل بازیابی نیست.
$ chmod 644 site/deploy.sh
$ chmod -R u=rwX,go=rX site
$ stat -c '%a %A %n' site/deploy.sh
644 -rw-r--r-- site/deploy.sh
$ site/deploy.sh
bash: site/deploy.sh: Permission denied
$ chmod u+x site/deploy.sh
$ stat -c '%a %A %n' site/deploy.sh
744 -rwxr--r-- site/deploy.sh
$ site/deploy.sh
hiهمین قانون به شکل معکوس نیز عمل میکند. X بیت اجرایی که یک فایل از قبل دارد را به کلاس دیگری تعمیم میدهد:
$ chmod go+X site/deploy.sh
$ stat -c '%a %A %n' site/deploy.sh
755 -rwxr-xr-x site/deploy.shبنابراین x کوچک برای یک فایل مشخص است که قصد دارید آن را قابلاجرا کنید، و X برای اعمال تغییرات در کل یک درخت دایرکتوری است. زمانی که به جای قانونی مبتنی بر وضعیت فعلی، یک حالت دقیق برای هر نوع فایل میخواهید، پیمایش را با find تفکیک کنید.
find site -type d -exec chmod 755 {} +
find site -type f -exec chmod 644 {} +
chmod u+x site/deploy.sh-type d دایرکتوریها را انتخاب میکند و -type f فایلهای معمولی را، بنابراین هر نوع فایل، حالتی که باید داشته باشد را دریافت میکند. -exec ... + مسیرهای زیادی را در یک فراخوانی chmod دستهبندی میکند تا به جای شروع یک پردازش برای هر فایل، همه را یکجا پردازش کند؛ این موضوع در درختی با هزاران ورودی اهمیت دارد. اسکریپت بیت خود را در یک خط جداگانه و بهصورت عمدی پس میگیرد.
بررسی نتیجه پیش از اتمام کار
دو دستور وجود دارند که خطاهای مهم پس از هر تغییر بازگشتی (recursive) را شناسایی میکنند. دستور اول دایرکتوریهایی را فهرست میکند که دیگر امکان ورود به آنها را ندارید.
find . -type d ! -perm -u+xدستور دوم فایلهایی را فهرست میکند که هر کاربری در سیستم میتواند آنها را ویرایش کند.
find . -type f -perm -o+wدر -perm -o+w، کاراکتر - در ابتدای عبارت به معنای حداقل این بیتها است؛ بنابراین با فایلی مطابقت دارد که کلاس o آن دارای بیت نوشتن (write bit) باشد، فارغ از اینکه چه بیتهای دیگری تنظیم شدهاند. نتیجه مطلوب این است که هیچکدام از این دو دستور خروجی نداشته باشند. هر دو دستور را از بالاترین سطح درختی که بهتازگی تغییر دادهاید با -R اجرا کنید و برای تعداد محدودی از مسیرهایی که قصد تغییرشان را داشتید، stat -c '%a %A %n' را به کار ببرید تا مطمئن شوید حالتی که تصور میکنید تنظیم کردهاید، همان حالتی است که در هسته (kernel) اعمال شده است.
FAQ
تفاوت بین chmod 755 و chmod u+x چیست؟
حالت عددی مطلق است. این دستور تمام بیتهای دسترسی فایل را بازنویسی میکند، بنابراین کلاسهایی که مد نظر شما نبوده نیز تغییر میکنند. حالت نمادین یک ویرایش است. u+x تنها یک بیت را برای مالک اضافه میکند و سایر بیتها را به همان شکلی که بودند باقی میگذارد. زمانی که وضعیت نهایی دقیق مورد نظر خود را برای یک فایل میدانید، از حالت عددی استفاده کنید. زمانی که میخواهید تنها یک مورد را اضافه یا حذف کنید، از حالت نمادین استفاده کنید. قبل و بعد از هر یک از این دستورات، stat -c '%a %A %n' <file> را اجرا کنید تا تفاوت در خروجی نمایش داده شود.
چرا اجرای chmod -R 755 روی دایرکتوری فایلهای وب ایده بدی است؟
زیرا -R یک حالت مطلق یکسان را به دایرکتوریها و فایلهای معمولی اعمال میکند، در حالی که این دو به بیتهای متفاوتی نیاز دارند. یک دایرکتوری برای قابلعبور بودن (traversable) به بیت execute نیاز دارد. فایلهای داده مانند HTML و CSS به آن نیازی ندارند و علامتگذاری آنها به عنوان قابلاجرا، تغییری است که git در commit بعدی شما ثبت میکند و rsync آن را به ماشین بعدی کپی میکند. از chmod -R u=rwX,go=rX <dir> استفاده کنید تا بیت execute فقط روی دایرکتوریها اعمال شود، یا این کار را با استفاده از find به همراه -type d و -type f انجام دهید تا هر نوع فایل، حالت دسترسی خاص خود را دریافت کند.
بیت execute در یک دایرکتوری چه کاری انجام میدهد؟
این بیت اجازه عبور (traversal) را میدهد، نه اجرا. در داخل یک دایرکتوری چیزی برای اجرا وجود ندارد. بیت execute مجوزی برای حل کردن (resolve) نامها در داخل آن است، بنابراین برای باز کردن فایلی در انتهای یک مسیر، به این بیت در تمام دایرکتوریهای طول آن مسیر نیاز دارید. بیت read جداگانه است و اجازه لیست کردن نامها را میدهد. شما میتوانید یکی را بدون دیگری داشته باشید: تنها با داشتن execute میتوانید فایلی را که نامش را میدانید باز کنید (در حالی که shell نمیتواند glob را در آن دایرکتوری گسترش دهد)، و تنها با داشتن read میتوانید نامها را ببینید (در حالی که هر تلاشی برای باز کردن آنها با شکست مواجه میشود).
چه زمانی باید از X بزرگ به جای x کوچک استفاده کنم؟
هر زمان که دستور به صورت بازگشتی (recursive) است، از X استفاده کنید. X بیت execute را روی دایرکتوریها تنظیم میکند و روی فایلهای معمولی فقط در صورتی که از قبل یک بیت execute تنظیم شده باشد؛ بنابراین یک بار اجرای دستور، یک درخت را بدون تبدیل فایلهای داده به برنامه، قابلعبور میکند. زمانی که به یک فایل خاص اشاره دارید و منظورتان همان فایل است، از x کوچک استفاده کنید. یک محدودیت را باید به خاطر داشت: X نمیتواند بیت execute را که قبلاً از همه جای یک فایل پاک شده است بازیابی کند، زیرا دیگر چیزی برای مطابقت وجود ندارد. آن را با chmod u+x <file> بازگردانید.
آیا chmod مالکیت فایل را تغییر میدهد؟
خیر. chmod فقط بیتهای دسترسی را تغییر میدهد. مالکیت متعلق به chown برای کاربر و chgrp برای گروه است و واگذاری یک فایل به کاربر دیگر نیازمند دسترسی root است. این موضوع اهمیت دارد زیرا بیتها بر اساس مالک و گروه ثبتشده روی فایل خوانده میشوند، نه بر اساس کسی که آخرین بار آن را نوشته است. برای مشاهده همزمان مالک، گروه و حالت دسترسی، stat -c '%U %G %a %n' <file> را اجرا کنید تا تصمیم بگیرید به کدامیک از این سه دستور واقعاً نیاز دارید.