Понимание sudo и возможного использования

В Linux при работе в пользовательском пространстве используется стек пользовательского пространства. Если процесс выполняется в пространстве ядра, он использует стек ядра, «принадлежащий» процессу. Прерывания обрабатываются внутри ядра.

Вероятно, лучшее место, чтобы узнать такие подробности о частных частях вашего любимого ядра, - это покопаться в сайтах, обслуживающих людей, программирующих его, например, kernelnewbies или выполните поиск на «страницах ядра» LWN для Linux. Вы должны найти похожие места для BSD, Solaris и даже MacOS. Получить информацию о Windows может быть труднее ...

Такая информация не обсуждается в типичных текстах по операционной системе, вам придется искать описания для разработчиков.

15
09.11.2018, 20:22
7 ответов

Очевидным тестом будет:

if touch /path/to/file; then
    : it can be created
fi

Но на самом деле файл создается, если его еще нет. Мы могли бы убрать за собой:

if touch /path/to/file; then
    rm /path/to/file
fi

Но это приведет к удалению уже существующего файла, который вам, вероятно, не нужен.

Однако у нас есть способ обойти это:

if mkdir /path/to/file; then
    rmdir /path/to/file
fi

У вас не может быть каталога с тем же именем, что и у другого объекта в этом каталоге. Я не могу представить ситуацию, в которой вы могли бы создать каталог, но не создать файл. После этого теста ваш скрипт сможет создать обычный /path/to/fileи делать с ним все, что захочет.

12
27.01.2020, 19:49

Я думаю, что решение DopeGhoti лучше, но оно тоже должно работать:

file=$1
if [[ "${file:0:1}" == '/' ]]; then
    dir=${file%/*}
elif [[ "$file" =~.*/.* ]]; then
    dir="$(PWD)/${file%/*}"
else
    dir=$(PWD)
fi

if [[ -w "$dir" ]]; then
    echo "writable"
    #do stuff with writable file
else
    echo "not writable"
    #do stuff without writable file
fi

Первая конструкция if проверяет, является ли аргумент полным путем (, начинается с/)и устанавливает переменную dirв путь к каталогу до последнего /. В противном случае, если аргумент не начинается с /, но содержит /(, указывающий подкаталог ), он установит dirв текущий рабочий каталог + путь к подкаталогу. В противном случае предполагается текущий рабочий каталог. Затем он проверяет, доступен ли этот каталог для записи.

4
27.01.2020, 19:49

Один из вариантов, который вы, возможно, захотите рассмотреть, это создание файла на ранней стадии, но его заполнение в сценарии только позже. Вы можете использовать команду exec, чтобы открыть файл в файловом дескрипторе (, таком как 3, 4 и т. д. ), а затем использовать перенаправление на файловый дескриптор(>&3и т. д. )для записать содержимое в этот файл.

Что-то вроде:

#!/bin/bash

# Open the file for read/write, so it doesn't get
# truncated just yet (to preserve the contents in
# case the initial checks fail.)
exec 3<>dir/file.txt || {
    echo "Error creating dir/file.txt" >&2
    exit 1
}

# Long checks here...
check_ok || {
    echo "Failed checks" >&2
    # cleanup file before bailing out
    rm -f dir/file.txt
    exit 1
}

# We're ready to write, first truncate the file.
# Use "truncate(1)" from coreutils, and pass it
# /dev/fd/3 so the file doesn't need to be reopened.
truncate -s 0 /dev/fd/3

# Now populate the file, use a redirection to write
# to the previously opened file descriptor.
populate_contents >&3

Вы также можете использовать trapдля очистки файла при ошибке, это обычная практика.

Таким образом, вы получаете реальную проверку разрешений, позволяющих создать файл, и в то же время возможность выполнить его достаточно рано, чтобы в случае неудачи вы не потратил время на ожидание долгих чеков.


ОБНОВЛЕНО:Чтобы избежать затирания файла в случае неудачной проверки, используйте перенаправление bashfd<>file, которое не обрезает файл сразу. (Мы не заботимся о чтении из файла, это всего лишь обходной путь, поэтому мы не усекаем его. Добавление с помощью >>, вероятно, тоже сработает, но я склонен находить это немного более элегантным, поскольку флаг O _APPEND не используется.)

Когда мы будем готовы заменить содержимое, нам нужно сначала обрезать файл (, иначе, если мы записываем меньше байтов, чем было в файле раньше, конечные байты останутся там.)Для этой цели мы можем использовать команду truncate (1)из coreutils, и мы можем передать ей имеющийся у нас дескриптор открытого файла (, используя /dev/fd/3псевдо--файл ), поэтому повторно открывать файл не нужно. (Опять же, технически что-то более простое, например : >dir/file.txt, вероятно, сработает, но отсутствие необходимости повторного открытия файла является более элегантным решением.)

6
27.01.2020, 19:49

Из того, что я знаю, вы хотите проверить это при использовании

tee -- "$OUT_FILE"

(обратите внимание на --, иначе это не сработает для имен файлов, начинающихся с -), teeудастся открыть файл для записи.

Вот что:

  • длина пути к файлу не превышает PATH _MAX limit
  • файл существует (после разрешения символической ссылки )и не относится к типу каталога , и у вас есть разрешение на запись в него.
  • если файл не существует, имя каталога файла существует (после разрешения символической ссылки )как каталог, и у вас есть права на запись и поиск в нем, а длина имени файла не превышает ИМЯ _МАКСИМАЛЬНОЕ ограничение файловой системы, в которой находится каталог.
  • или файл представляет собой символическую ссылку, которая указывает на несуществующий файл и не является циклом символической ссылки, но соответствует критериям, указанным выше

На данный момент мы будем игнорировать файловые системы, такие как vfat, ntfs или hfsplus, которые имеют ограничения на то, какие байтовые значения могут содержать имена файлов, дисковую квоту, ограничение процесса, selinux, apparmor или другой механизм безопасности, полную файловую систему, без inode. слева, файлы устройств, которые не могут быть открыты таким образом по той или иной причине, файлы, которые являются исполняемыми файлами, в настоящее время сопоставленными в некотором адресном пространстве процесса, все из которых также могут повлиять на возможность открытия или создания файла.

Сzsh:

zmodload zsh/system
tee_would_likely_succeed() {
  local file=$1 ERRNO=0 LC_ALL=C
  if [ -d "$file" ]; then
    return 1 # directory
  elif [ -w "$file" ]; then
    return 0 # writable non-directory
  elif [ -e "$file" ]; then
    return 1 # exists, non-writable
  elif [ "$errnos[ERRNO]" != ENOENT ]; then
    return 1 # only ENOENT error can be recovered
  else
    local dir=$file:P:h base=$file:t
    [ -d "$dir" ] &&    # directory
      [ -w "$dir" ] &&  # writable
      [ -x "$dir" ] &&  # and searchable
      (($#base <= $(getconf -- NAME_MAX "$dir")))
    return
  fi
}

В bashили любой другой оболочке, подобной Bourne -, просто замените

zmodload zsh/system
tee_would_likely_succeed() {
  <zsh-code>
}

с:

tee_would_likely_succeed() {
  zsh -s -- "$@" << 'EOF'
  zmodload zsh/system
  <zsh-code>
EOF
}

Специфическими функциямиzsh-здесь являются $ERRNO(, которые предоставляют код ошибки последнего системного вызова )и ассоциативный массив $errnos[]для преобразования в соответствующие стандартные имена макросов C. А для$var:h(из csh )и$var:P(требуется zsh 5.3 или выше ).

bash еще не имеет эквивалентных функций.

$file:hможно заменить на dir=$(dirname -- "$file"; echo.); dir=${dir%??}или на GNU dirname:IFS= read -rd '' dir < <(dirname -z -- "$file").

Для $errnos[ERRNO] == ENOENT,можно запустить ls -Ldдля файла и проверить, соответствует ли сообщение об ошибке ошибке ENOENT. Однако сделать это надежно и портативно сложно.

Одним из подходов может быть:

msg_for_ENOENT=$(LC_ALL=C ls -d -- '/no such file' 2>&1)
msg_for_ENOENT=${msg_for_ENOENT##*:}

(предполагая, что сообщение об ошибке заканчивается переводом syserror()ENOENTи что этот перевод не включает :), а затем вместо [ -e "$file" ]выполните:

err=$(ls -Ld -- "$file" 2>&1)

И проверьте наличие ошибки ENOENT с помощью

case $err in
  (*:"$msg_for_ENOENT")...
esac

Часть $file:Pсложнее всего реализовать в bash, особенно на FreeBSD.

FreeBSD имеет команду realpathи команду readlink, которая принимает параметр -f, но их нельзя использовать в случаях, когда файл является символической ссылкой, которая не разрешается. То же самое с perl's Cwd::realpath().

pythonos.path.realpath(), по-видимому, работает аналогично zsh$file:P, поэтому предполагается, что установлена ​​по крайней мере одна версия pythonи что существует команда python, ссылающаяся на одну из них (, которая не является данным во FreeBSD ), вы можете сделать:

dir=$(python -c '
import os, sys
print(os.path.realpath(sys.argv[1]) + ".")' "$dir") || return
dir=${dir%.}

Но тогда вы могли бы также сделать все это в python.

Или вы можете решить не обрабатывать все эти крайние случаи.

8
27.01.2020, 19:49

Как насчет использования обычной команды test, описанной ниже?

FILE=$1

DIR=$(dirname $FILE) # $DIR now contains '.' for file names only, 'foo' for 'foo/bar'

if [ -d $DIR ] ; then
  echo "base directory $DIR for file exists"
  if [ -e $FILE ] ; then
    if [ -w $FILE ] ; then
      echo "file exists, is writeable"
    else
      echo "file exists, NOT writeable"
    fi
  elif [ -w $DIR ] ; then
    echo "directory is writeable"
  else
    echo "directory is NOT writeable"
  fi
else
  echo "can NOT create file in non-existent directory $DIR "
fi
4
27.01.2020, 19:49

Вы упомянули, что на ваш вопрос повлиял опыт пользователей. Я отвечу с точки зрения UX, так как у вас есть хорошие ответы на техническую сторону.

Вместо выполнения проверки -в начале, как насчет того, чтобы записать результаты во временный файл , а затем в самом конце поместить результаты в нужный пользователю файл? Нравится:

userfile=${1:?Where would you like the file written?}
tmpfile=$(mktemp)

#... all the complicated stuff, writing into "${tmpfile}"

# fill user's file, keeping existing permissions or creating anew
# while respecting umask
cat "${tmpfile}" > "${userfile}"
if [ 0 -eq $? ]; then
    rm "${tmpfile}"
else
    echo "Couldn't write results into ${userfile}." >&2
    echo "Results available in ${tmpfile}." >&2
    exit 1
fi

Хорошо с этим подходом :он производит желаемую операцию в нормальном сценарии счастливого пути, побочные -шаги теста -и -устанавливают проблему атомарности, сохраняют разрешения целевого файла при создании, если необходим, и очень прост в реализации.

Примечание :если бы мы использовали mv, мы бы сохранили разрешения временного файла --нам это не нужно, я думаю :мы хотим сохранить разрешения, установленные на целевой файл.

Плохая :конструкция требует в два раза больше места,(cat.. >), вынуждает пользователя выполнять некоторую ручную работу, если целевой файл не был доступен для записи в нужное время, и оставляет временный файл, лежащий вокруг (, который может иметь проблемы с безопасностью или обслуживанием ).

4
27.01.2020, 19:49

TL;DR:

: >> "${userfile}"

В отличие от <> "${userfile}"или touch "${userfile}", это не приведет к каким-либо ложным изменениям меток времени файла, а также будет работать с файлами -только для записи.


Из ОП:

I want to make a reasonable check if the file can be created/overwritten, but not actually create it.

И из вашего комментария к моему ответу с точки зрения UX:

The overwhelming majority of the time this fails is because the path provided is not valid. I can't reasonably prevent some rogue user some concurrently modifying the FS to break the job in the middle of the operation, but I certainly can check the #1 failure cause of an invalid or not writable path.

Единственным надежным тестом являетсяopen(2)файл, потому что только он разрешает все вопросы о пути :возможности записи, владельце, файловой системе, сети, контексте безопасности и т. д. Любой другой тест будет касаться некоторой части возможности записи, но не другие. Если вы хотите подмножество тестов, вам в конечном итоге придется выбрать то, что для вас важно.

Но вот еще одна мысль. Насколько я понимаю:

  1. процесс создания контента длится долго -и
  2. целевой файл следует оставить в согласованном состоянии.

Вы хотите выполнить эту предварительную -проверку из-за #1 и не хотите возиться с существующим файлом из-за #2.Так почему бы вам просто не попросить оболочку открыть файл для добавления, но на самом деле ничего не добавлять?

$ tree -ps
.
├── [dr-x------        4096]  dir_r
├── [drwx------        4096]  dir_w
├── [-r--------           0]  file_r
└── [-rw-------           0]  file_w

$ for p in file_r dir_r/foo file_w dir_w/foo; do : >> $p; done
-bash: file_r: Permission denied
-bash: dir_r/foo: Permission denied

$ tree -ps
.
├── [dr-x------        4096]  dir_r
├── [drwx------        4096]  dir_w
│   └── [-rw-rw-r--           0]  foo
├── [-r--------           0]  file_r
└── [-rw-------           0]  file_w

Под капотом это решает вопрос возможности записи именно так, как хотелось:

open("dir_w/foo", O_WRONLY|O_CREAT|O_APPEND, 0666) = 3

, но без изменения содержимого или метаданных файла. Теперь да, этот подход:

  • не говорит вам, является ли файл только добавленным, что может быть проблемой, когда вы собираетесь обновить его в конце создания контента. Вы можете обнаружить это в некоторой степени с помощью lsattrи отреагировать соответствующим образом.
  • создает файл, который ранее не существовал, в этом случае :уменьшите это с помощью выборочного rm.

Хотя я утверждаю (в своем другом ответе ), что наиболее удобный для пользователя -подход заключается в создании временного файла, который пользователь должен переместить, я думаю, что это ] наименее пользовательский -враждебный подход к полной проверке их ввода.

2
27.01.2020, 19:49

Теги

Похожие вопросы