Почему Ruby в Linux возвращает true для File.writable?('/tmp/file'), но вызывает Errno::EACCES при попытке записи файла?

Я использую систему GNU/Linux.

Во-первых, у меня есть:

  • Перенесено в каталог /tmp/.

  • Создал файл с именем ruby.rb как пользователь без полномочий root.

  • Открыт irb как пользователь su.

Сейчас в ИРБ:

┌┄┄[root::archlinux]┈[/tmp]
└──╼⮚ irb
irb(main):001:0> File.writable?('ruby.rb')
=> true
irb(main):002:0> File.stat('ruby.rb')
=> #<File::Stat dev=0x2d, ino=819138, mode=0100644, nlink=1, uid=1000, gid=1000, rdev=0x0, size=0, blksize=4096, blocks=0, atime=2019-07-14 04:44:13 +0530, mtime=2019-07-14 04:44:13 +0530, ctime=2019-07-14 04:44:13 +0530>
irb(main):003:0> File.write('ruby.rb', '#!/usr/bin/ruby -w')
Traceback (most recent call last):
        3: from /root/.irb:351:in `<main>'
        2: from (irb):3
        1: from (irb):3:in `write'
Errno::EACCES (Permission denied @ rb_sysopen - ruby.rb)
irb(main):004:0> 

Помимо этого, на самом деле я пытаюсь написать файл журнала. Я проверяю, доступен ли файл для записи. Если нет, он отправляет уведомление пользователю.

Раньше я сталкивался с проблемой, когда File#writable?(str) возвращал true в таком случае (и это работает в каталоге /tmp/). И просто использовал блок begin <...> rescue Errno::EACCES, чтобы решить проблему. Но в текущем проекте я не хочу использовать спасательный блок.

Почему File#writable?(str) вообще возвращает true?

Обновлено: Прежде всего, это должно быть перемещено в unix.stackexchange.

Во-вторых, я понял

  • Вы не можете изменить файл в файловой системе tmp: я смонтировал раздел размером всего 4 МБ как tmpfs в /mnt. Создал файл как локальный пользователь. Потом сменил разрешение на всемогущее 777! Затем я изменил свою учетную запись на root. Я попытался отредактировать содержимое с помощью nano. Это невозможно.

  • Файл Руби#доступен для записи? вероятно, не определяет тип смонтированной файловой системы. Будь то XFS или EXT4, procfs или tmpfs. Если все, что он делает, это проверяет режим, в таком сценарии File#writable? возвращает true, в то время как на самом деле он не доступен для записи!

Редактировать 2: я загрузил 2 скриншота на imgur:

Почему Ruby в Linux возвращает true для File.writable?(&apos;/tmp/file&apos;), но вызывает Errno::EACCES при попытке записи файла?

Почему Ruby в Linux возвращает true для File.writable?(&apos;/tmp/file&apos;), но вызывает Errno::EACCES при попытке записи файла?

Почему бы не использовать ruby-doc.org/stdlib-2.4.0/libdoc/tempfile/rdoc/Tempfile.html?

anothermh 14.07.2019 02:04

Этот вопрос должен быть в теме Stack Overflow. Проблема в том, что отсутствует Минимальный, полный и проверяемый пример. File#writable?(str) недостаточно информации, чтобы помочь вам.

jww 14.07.2019 02:13

Во многих системах /tmp не является обычной файловой системой, поэтому вы не сможете использовать ее напрямую.

tadman 14.07.2019 02:53
/tmp доступен для записи в популярных дистрибутивах Linux. вы можете запустить strace -o $HOME/trace irb и повторить сеанс. Вывод в $HOME/trace irb сообщит вам, что irb просит ОС сделать и что ОС отвечает. Могут быть проблемы со списком контроля доступа или файловая система может быть смонтирована только для чтения. Я полагаю, что «правильный» способ реализации File#writable — через вызов ОС access(2) — strace(1) должен сообщить вам, если это так.
root 14.07.2019 03:18

@root - Не всегда верно. Большинство машин в Компиляционная ферма GCC — это Linux, но они не имеют права записи пользователем /tmp. Если у вас есть учетная запись на ферме, вам нужно export TMPDIR = "$HOME/tmp", если вы планируете использовать временный каталог.

jww 14.07.2019 06:27

Я не могу воспроизвести это. Вот что я пробовал (без полномочий root): sudo mount -t tmpfs none /mnt, echo foo > /mnt/myfile, chmod 777 /mnt/myfile, sudo bash -c 'echo more >> /mnt/myfile'. Согласно вашему сообщению, я должен получить сообщение об ошибке, но вместо этого он завершается с успехом и cat /mnt/myfile показывает «foo more», как и ожидалось.

that other guy 14.07.2019 07:47

@root, /tmp доступен для записи. Но вы написали файл с именем a от имени пользователя x, насколько я понял, вы не можете записать файл от имени пользователя y даже с правами 666... ​​Странно. Моему скрипту нужны привилегии root, чтобы выполнить какую-то задачу. Но что ж, я мог бы хотя бы проверить, существует ли уже файл и принадлежит ли он пользователю root или нет в Ruby...

S.Goswami 14.07.2019 08:12

Ну, на самом деле у меня есть 2 других компьютера. Такое поведение наблюдается на моем ноутбуке с Arch x86_64. Просто чтобы убедиться, я попробовал свой raspberry pi под управлением Arch Linux ARM. Шаги: (1) Перейдите в /tmp. (2) touch file (3) chmod 777 file. (4) su (5) nano file Такое же поведение наблюдается и на Pi. Если вы видите разрешения, это 777, и любой может читать, писать и выполнять их. Но если файл находится в /tmp/, другие пользователи (даже root) не могут писать в него. Я запускаю Kernel 5.2.0-arch2-1-ARCH на ноутбуке и 4.19.57-1-ARCH на Raspberry Pi 3 модели B.

S.Goswami 14.07.2019 08:21

@thatotherguy, да, это странно. Если вы наберете echo > file да, это сработает. Но редакторы nano, vi TUI и редакторы GUI Geany, Mousepad, atom не могут писать в него. Как только вы сделаете echo > file как root, вы не сможете написать его как обычный пользователь в этих редакторах. Да, ruby ​​тоже не может записать файл. Работает только переадресация!

S.Goswami 14.07.2019 08:28

Привет, я загрузил 2 скриншота на imgur: imgur.com/tB4T5Jlimgur.com/hzc5s27

S.Goswami 14.07.2019 11:28

(1) какой дистрибутив вы используете на своем ноутбуке? (2) каков результат test -w /tmp/ruby.rb; echo $?? (3) каков результат su -c 'test -w /tmp/ruby.rb; echo $?'? (4) каков результат mount | grep /tmp? (5) каков результат getfacl /tmp/ruby.rb? (6) каков результат ls -ld /tmp?

root 14.07.2019 12:02

Я могу воспроизвести это в ArchLinux, но не в любом другом дистрибутиве.

that other guy 14.07.2019 20:58

Такой же. У меня есть 3 системы под управлением Arch Linux. Поэтому я подумал, что это можно воспроизвести в системе. На самом деле тогда я сказал своему другу (который использует Ubuntu) сделать это. Он сказал, что все идет так, как ожидалось. Затем я запустил свою виртуальную машину Linux Mint, и мой вопрос перестал быть актуальным. Простите. Я не знал, что это может быть только для Arch Linux. Я не знаю почему. Позвольте мне опубликовать это на форуме Arch Linux! Не беспокойтесь о минусах ;) ... Как всегда узнал что-то новое ;)

S.Goswami 14.07.2019 21:00

Хорошо, вот что я нашел: bbs.archlinux.org/viewtopic.php?id=244482

S.Goswami 14.07.2019 21:16
Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Шаг 1: Создание приложения Slack Чтобы создать Slackbot, вам необходимо создать приложение Slack. Войдите в свою учетную запись Slack и перейдите на...
0
14
218
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Может быть одна из двух вещей: файл не открывается для записи или проблема с правами пользователя.

Попробуйте открыть файл для записи с помощью 'w' или 'w+'

File.open('ruby.rb', 'w') { |file| file.write("#!/usr/bin/ruby -w") }

Если это не сработает, попробуйте использовать chmod для обновления прав доступа к файлам для ruby.rb с помощью

chmod -R 777 <path_to_file>

Нет, File.open... не работает. Ну да, я могу потребовать fileutils и добавить FileUtils.chmod(0777, 'ruby.rb') и все равно иметь ту же проблему. Я тоже подумывал сначала сделать 0666. Вы знаете, что сначала было установлено разрешение 0644, что означает, что оно было доступно для записи! В любом случае не работает! Я столкнулся с этой проблемой только в каталоге /tmp/...

S.Goswami 14.07.2019 01:37
Ответ принят как подходящий

Хорошо, это не специфичная для Ruby проблема. Тем не менее, у любого языка программирования будет такая проблема, потому что все дело в защите файловой системы, реализованной в systemd.

Опция fs.protected_regular реализована в ядре Linux 4.19+ чтобы усложнить атаки спуфинга данных.

Итак, в первую очередь нужно проверить:

sysctl fs.protected_regular

В вашем случае он должен печатать 1

Итак, измените значение на 0:

sysctl fs.protected_regular=0

Надеюсь проблема решится. Или попробуйте это:

sysctl fs.protected_regular=0
sysctl fs.protected_fifos=0

Обратите внимание, что эта опция включена по умолчанию. Возможно, вам не захочется говорить каждому пользователю вашей программы, чтобы он установил вышеприведенное значение 0! Они могут даже не предпочесть это!

Итак, в Руби:

  1. Вы можете удалить файл:
File.delete('file') if File.exist?('file')
# Better use the '/tmp/file' or File.join(%w(/ tmp file)) otherwise the file will get delete from the Dir.pwd

Который будет удалять файл каждый раз, когда запускается скрипт.

  1. Вы также можете использовать блок begin ... rescue и использовать Warning.warn или Kernel.warn или STDERR.puts для печати предупреждающего сообщения.

Другие вопросы по теме