Модуль FileUtils
fileutils.rb
Авторское право © 2000-2007 Minero Aoki
Данная программа является свободной программной. Вы можете распространять/изменять данную программу на тех же условиях, что и руби.
Модуль FileUtils
Пространство имен для нескольких методов работы с файлами, таких как копирование, перемещение, удаление и т.д.
Module Функции
require 'fileutils'
FileUtils.cd(dir, **options)
FileUtils.cd(dir, **options) {|dir| block }
FileUtils.pwd()
FileUtils.mkdir(dir, **options)
FileUtils.mkdir(list, **options)
FileUtils.mkdir_p(dir, **options)
FileUtils.mkdir_p(list, **options)
FileUtils.rmdir(dir, **options)
FileUtils.rmdir(list, **options)
FileUtils.ln(target, link, **options)
FileUtils.ln(targets, dir, **options)
FileUtils.ln_s(target, link, **options)
FileUtils.ln_s(targets, dir, **options)
FileUtils.ln_sf(target, link, **options)
FileUtils.cp(src, dest, **options)
FileUtils.cp(list, dir, **options)
FileUtils.cp_r(src, dest, **options)
FileUtils.cp_r(list, dir, **options)
FileUtils.mv(src, dest, **options)
FileUtils.mv(list, dir, **options)
FileUtils.rm(list, **options)
FileUtils.rm_r(list, **options)
FileUtils.rm_rf(list, **options)
FileUtils.install(src, dest, **options)
FileUtils.chmod(mode, list, **options)
FileUtils.chmod_R(mode, list, **options)
FileUtils.chown(user, group, list, **options)
FileUtils.chown_R(user, group, list, **options)
FileUtils.touch(list, **options)
Возможные options:
-
:force -
вынужденная операция (перезапись файлов, если они существуют, удаление каталогов, если они не пустые и т.д.);
-
:verbose -
вывод команды, которая будет выполнена, в формате bash, перед ее выполнением;
-
:preserve -
сохранение группы, пользователя и времени изменения объекта при копировании;
-
:noop -
без изменений (можно использовать в сочетании с
:verbose, который выведет команду для выполнения)
Каждый метод документирует параметры, которые он поддерживает. См. также методы ::commands, ::options и ::options_of для получения информации о командах и их параметрах.
Все методы, которые имеют понятие «исходного» файла или каталога, могут принимать как один файл, так и список файлов в качестве аргумента. Примеры использования см. в документации к методам.
Есть некоторые методы «нижележащего уровня», которые не принимают именованные аргументы:
FileUtils.copy_entry(src, dest, preserve = false, dereference_root = false, remove_destination = false) FileUtils.copy_file(src, dest, preserve = false, dereference = true) FileUtils.copy_stream(srcstream, deststream) FileUtils.remove_entry(path, force = false) FileUtils.remove_entry_secure(path, force = false) FileUtils.remove_file(path, force = false) FileUtils.compare_file(path_a, path_b) FileUtils.compare_stream(stream_a, stream_b) FileUtils.uptodate?(file, cmp_list)
Модуль FileUtils::Verbose
Этот модуль содержит все методы модуля FileUtils, но выводит сообщения перед выполнением действий. Это эквивалентно передаче флага :verbose методам в модуле FileUtils.
Модуль FileUtils::NoWrite
Этот модуль содержит все методы модуля FileUtils, но никогда не изменяет файлы/каталоги. Это эквивалентно передаче флага :noop методам в модуле FileUtils.
Модуль FileUtils::DryRun
Этот модуль содержит все методы модуля FileUtils, но никогда не изменяет файлы/каталоги. Это эквивалентно передаче флагов :noop и :verbose методам в модуле FileUtils.
Константы
- VERSION
Методы открытого класса
# File lib/fileutils.rb, line 137
def cd(dir, verbose: nil, &block) # :yield: dir
fu_output_message "cd #{dir}" if verbose
result = Dir.chdir(dir, &block)
fu_output_message 'cd -' if verbose and block
result
end Изменяет текущую директорию на директорию dir.
Если этот метод вызван с блоком, он возвращается к предыдущей рабочей директории после завершения выполнения блока.
FileUtils.cd('/') # change directory
FileUtils.cd('/', verbose: true) # change directory and report it
FileUtils.cd('/') do # change directory
# ... # do something
end # return to original directory
# File lib/fileutils.rb, line 1012
def chmod(mode, list, noop: nil, verbose: nil)
list = fu_list(list)
fu_output_message sprintf('chmod %s %s', mode_to_s(mode), list.join(' ')) if verbose
return if noop
list.each do |path|
Entry_.new(path).chmod(fu_mode(mode, path))
end
end Изменяет биты разрешений на указанные файлы (в list) на битовую маску, представленную значением mode.
Можно использовать символьную и абсолютную нотацию.
Абсолютная нотация:
FileUtils.chmod 0755, 'somecommand' FileUtils.chmod 0644, %w(my.rb your.rb his.rb her.rb) FileUtils.chmod 0755, '/usr/bin/ruby', verbose: true
Символьная нотация:
FileUtils.chmod "u=wrx,go=rx", 'somecommand' FileUtils.chmod "u=wr,go=rr", %w(my.rb your.rb his.rb her.rb) FileUtils.chmod "u=wrx,go=rx", '/usr/bin/ruby', verbose: true
- “a”
-
— маска для пользователя, группы и других.
- “u”
-
— маска для пользователя.
- “g”
-
— маска для группы.
- “o”
-
— маска для других.
- “w”
-
— разрешение на запись.
- “r”
-
— разрешение на чтение.
- “x”
-
— разрешение на выполнение.
- “X”
-
— разрешение на выполнение только для каталогов, должно использоваться в сочетании с “+”
- “s”
-
— идентификатор пользователя и группы.
- “t”
-
— битовая метка «всё ещё в каталоге» (sticky bit).
- “+”
-
— добавляет к классу, заданному в режиме.
- “-”
-
— удаляет из класса, заданного в режиме.
- “=”
-
— точно задаёт класс в соответствии с режимом.
# File lib/fileutils.rb, line 1029
def chmod_R(mode, list, noop: nil, verbose: nil, force: nil)
list = fu_list(list)
fu_output_message sprintf('chmod -R%s %s %s',
(force ? 'f' : ''),
mode_to_s(mode), list.join(' ')) if verbose
return if noop
list.each do |root|
Entry_.new(root).traverse do |ent|
begin
ent.chmod(fu_mode(mode, ent.path))
rescue
raise unless force
end
end
end
end Изменяет биты разрешений на указанные файлы (в list) на битовую маску, представленную значением mode рекурсивно.
FileUtils.chmod_R 0700, "/tmp/app.#{$$}"
FileUtils.chmod_R "u=wrx", "/tmp/app.#{$$}"
# File lib/fileutils.rb, line 1057
def chown(user, group, list, noop: nil, verbose: nil)
list = fu_list(list)
fu_output_message sprintf('chown %s %s',
(group ? "#{user}:#{group}" : user || ':'),
list.join(' ')) if verbose
return if noop
uid = fu_get_uid(user)
gid = fu_get_gid(group)
list.each do |path|
Entry_.new(path).chown uid, gid
end
end Изменяет владельца и группу на указанные файлы (в list) на пользователя user и группу group. user и group могут быть ID (целое число/строка) или именем (String). Если user или group имеют значение nil, этот метод не изменяет атрибут.
FileUtils.chown 'root', 'staff', '/usr/local/bin/ruby'
FileUtils.chown nil, 'bin', Dir.glob('/usr/bin/*'), verbose: true
# File lib/fileutils.rb, line 1081
def chown_R(user, group, list, noop: nil, verbose: nil, force: nil)
list = fu_list(list)
fu_output_message sprintf('chown -R%s %s %s',
(force ? 'f' : ''),
(group ? "#{user}:#{group}" : user || ':'),
list.join(' ')) if verbose
return if noop
uid = fu_get_uid(user)
gid = fu_get_gid(group)
list.each do |root|
Entry_.new(root).traverse do |ent|
begin
ent.chown uid, gid
rescue
raise unless force
end
end
end
end Изменяет владельца и группу на указанные файлы (в list) на пользователя user и группу group рекурсивно. user и group могут быть ID (целое число/строка) или именем (String). Если user или group имеют значение nil, этот метод не изменяет атрибут.
FileUtils.chown_R 'www', 'www', '/var/www/htdocs' FileUtils.chown_R 'cvs', 'cvs', '/var/cvs', verbose: true
# File lib/fileutils.rb, line 1680
def self.collect_method(opt)
OPT_TABLE.keys.select {|m| OPT_TABLE[m].include?(opt) }
end Возвращает Array имён методов, имеющих опцию opt.
p FileUtils.collect_method(:preserve) #=> ["cp", "cp_r", "copy", "install"]
# File lib/fileutils.rb, line 1641 def self.commands OPT_TABLE.keys end
Возвращает Array имён методов высокого уровня, которые принимают любые ключевые аргументы.
p FileUtils.commands #=> ["chmod", "cp", "cp_r", "install", ...]
# File lib/fileutils.rb, line 816
def compare_file(a, b)
return false unless File.size(a) == File.size(b)
File.open(a, 'rb') {|fa|
File.open(b, 'rb') {|fb|
return compare_stream(fa, fb)
}
}
end Возвращает true, если содержимое файла a и файла b идентичны.
FileUtils.compare_file('somefile', 'somefile') #=> true
FileUtils.compare_file('/dev/null', '/dev/urandom') #=> false
# File lib/fileutils.rb, line 834
def compare_stream(a, b)
bsize = fu_stream_blksize(a, b)
if RUBY_VERSION > "2.4"
sa = String.new(capacity: bsize)
sb = String.new(capacity: bsize)
else
sa = String.new
sb = String.new
end
begin
a.read(bsize, sa)
b.read(bsize, sb)
return true if sa.empty? && sb.empty?
end while sa == sb
false
end Возвращает true, если содержимое потока a и b идентичны.
# File lib/fileutils.rb, line 489
def copy_entry(src, dest, preserve = false, dereference_root = false, remove_destination = false)
if dereference_root
src = File.realpath(src)
end
Entry_.new(src, nil, false).wrap_traverse(proc do |ent|
destent = Entry_.new(dest, ent.rel, false)
File.unlink destent.path if remove_destination && (File.file?(destent.path) || File.symlink?(destent.path))
ent.copy destent.path
end, proc do |ent|
destent = Entry_.new(dest, ent.rel, false)
ent.copy_metadata destent.path if preserve
end)
end Копирует элемент файловой системы src в dest. Если src — это каталог, этот метод копирует его содержимое рекурсивно. Этот метод сохраняет типы файлов, например, символические ссылки, каталоги… (FIFO, файлы устройств и т. д. пока не поддерживаются)
И src и dest должны быть именами путей. src должен существовать, dest не должен существовать.
Если preserve имеет значение true, этот метод сохраняет владельца, группу и время изменения. Разрешения копируются независимо от preserve.
Если dereference_root имеет значение true, этот метод делает рекурсивный спуск по корню.
Если remove_destination имеет значение true, этот метод удаляет каждый файл назначения перед копированием.
# File lib/fileutils.rb, line 509 def copy_file(src, dest, preserve = false, dereference = true) ent = Entry_.new(src, nil, dereference) ent.copy_file dest ent.copy_metadata dest if preserve end
Копирует содержимое файла src в dest. И src и dest должны быть именами путей.
# File lib/fileutils.rb, line 521 def copy_stream(src, dest) IO.copy_stream(src, dest) end
Копирует поток src в dest. src должен отвечать на read(n), а dest — на write(str).
# File lib/fileutils.rb, line 426
def cp(src, dest, preserve: nil, noop: nil, verbose: nil)
fu_output_message "cp#{preserve ? ' -p' : ''} #{[src,dest].flatten.join ' '}" if verbose
return if noop
fu_each_src_dest(src, dest) do |s, d|
copy_file s, d, preserve
end
end Копирует содержимое файла src в dest. Если dest — это каталог, копирует src в dest/src.
Если src — это список файлов, то dest должен быть каталогом.
FileUtils.cp 'eval.c', 'eval.c.org' FileUtils.cp %w(cgi.rb complex.rb date.rb), '/usr/lib/ruby/1.6' FileUtils.cp %w(cgi.rb complex.rb date.rb), '/usr/lib/ruby/1.6', verbose: true FileUtils.cp 'symlink', 'dest' # copy content, "dest" is not a symlink
# File lib/fileutils.rb, line 339
def cp_lr(src, dest, noop: nil, verbose: nil,
dereference_root: true, remove_destination: false)
fu_output_message "cp -lr#{remove_destination ? ' --remove-destination' : ''} #{[src,dest].flatten.join ' '}" if verbose
return if noop
fu_each_src_dest(src, dest) do |s, d|
link_entry s, d, dereference_root, remove_destination
end
end Создаёт жёсткую ссылку src на dest. Если src — это каталог, этот метод создаёт ссылки на всё его содержимое рекурсивно. Если dest — это каталог, он создаёт ссылки на src в dest/src.
src может быть списком файлов.
Если dereference_root имеет значение true, этот метод делает рекурсивный спуск по корню.
Если remove_destination имеет значение true, этот метод удаляет каждый файл назначения перед копированием.
FileUtils.rm_r site_ruby + '/mylib', force: true
FileUtils.cp_lr 'lib/', site_ruby + '/mylib'
# Examples of linking several files to target directory.
FileUtils.cp_lr %w(mail.rb field.rb debug/), site_ruby + '/tmail'
FileUtils.cp_lr Dir.glob('*.rb'), '/home/aamine/lib/ruby', noop: true, verbose: true
# If you want to link all contents of a directory instead of the
# directory itself, c.f. src/x -> dest/x, src/y -> dest/y,
# use the following code.
FileUtils.cp_lr 'src/.', 'dest' # cp_lr('src', 'dest') makes dest/src, but this doesn't.
# File lib/fileutils.rb, line 463
def cp_r(src, dest, preserve: nil, noop: nil, verbose: nil,
dereference_root: true, remove_destination: nil)
fu_output_message "cp -r#{preserve ? 'p' : ''}#{remove_destination ? ' --remove-destination' : ''} #{[src,dest].flatten.join ' '}" if verbose
return if noop
fu_each_src_dest(src, dest) do |s, d|
copy_entry s, d, preserve, dereference_root, remove_destination
end
end Копирует src в dest. Если src является каталогом, этот метод копирует все его содержимое рекурсивно. Если dest является каталогом, копирует src в dest/src.
src может быть списком файлов.
Если dereference_root имеет значение true, этот метод обрабатывает корень дерева.
Если remove_destination имеет значение true, этот метод удаляет каждый целевой файл перед копированием.
# Installing Ruby library "mylib" under the site_ruby
FileUtils.rm_r site_ruby + '/mylib', force: true
FileUtils.cp_r 'lib/', site_ruby + '/mylib'
# Examples of copying several files to target directory.
FileUtils.cp_r %w(mail.rb field.rb debug/), site_ruby + '/tmail'
FileUtils.cp_r Dir.glob('*.rb'), '/home/foo/lib/ruby', noop: true, verbose: true
# If you want to copy all contents of a directory instead of the
# directory itself, c.f. src/x -> dest/x, src/y -> dest/y,
# use following code.
FileUtils.cp_r 'src/.', 'dest' # cp_r('src', 'dest') makes dest/src,
# but this doesn't.
# File lib/fileutils.rb, line 1661
def self.have_option?(mid, opt)
li = OPT_TABLE[mid.to_s] or raise ArgumentError, "no such method: #{mid}"
li.include?(opt)
end Возвращает true, если метод mid имеет опцию opt.
p FileUtils.have_option?(:cp, :noop) #=> true p FileUtils.have_option?(:rm, :force) #=> true p FileUtils.have_option?(:rm, :preserve) #=> false
# File lib/fileutils.rb, line 862
def install(src, dest, mode: nil, owner: nil, group: nil, preserve: nil,
noop: nil, verbose: nil)
if verbose
msg = +"install -c"
msg << ' -p' if preserve
msg << ' -m ' << mode_to_s(mode) if mode
msg << " -o #{owner}" if owner
msg << " -g #{group}" if group
msg << ' ' << [src,dest].flatten.join(' ')
fu_output_message msg
end
return if noop
uid = fu_get_uid(owner)
gid = fu_get_gid(group)
fu_each_src_dest(src, dest) do |s, d|
st = File.stat(s)
unless File.exist?(d) and compare_file(s, d)
remove_file d, true
copy_file s, d
File.utime st.atime, st.mtime, d if preserve
File.chmod fu_mode(mode, st), d if mode
File.chown uid, gid, d if uid or gid
end
end
end Если src не совпадает с dest, копирует его и изменяет режим доступа на mode. Если dest является каталогом, то результатом является dest/src. Этот метод удаляет целевой объект перед копированием.
FileUtils.install 'ruby', '/usr/local/bin/ruby', mode: 0755, verbose: true FileUtils.install 'lib.rb', '/usr/local/lib/ruby/site_ruby', verbose: true
# File lib/fileutils.rb, line 406
def link_entry(src, dest, dereference_root = false, remove_destination = false)
Entry_.new(src, nil, dereference_root).traverse do |ent|
destent = Entry_.new(dest, ent.rel, false)
File.unlink destent.path if remove_destination && File.file?(destent.path)
ent.link destent.path
end
end Создаёт жёсткую ссылку на системный элемент src в dest. Если src является каталогом, этот метод создаёт жёсткие ссылки на его содержимое рекурсивно.
Оба src и dest должны быть именами путей. src должен существовать, а dest — нет.
Если dereference_root имеет значение true, этот метод обрабатывает корень дерева.
Если remove_destination имеет значение true, этот метод удаляет каждый целевой файл перед копированием.
# File lib/fileutils.rb, line 303
def ln(src, dest, force: nil, noop: nil, verbose: nil)
fu_output_message "ln#{force ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if verbose
return if noop
fu_each_src_dest0(src, dest) do |s,d|
remove_file d, true if force
File.link s, d
end
end В первом случае создаёт жёсткую ссылку link, которая указывает на target. Если link уже существует, возникает исключение Errno::EEXIST. Но если опция force установлена, переписывает link.
FileUtils.ln 'gcc', 'cc', verbose: true FileUtils.ln '/usr/bin/emacs21', '/usr/bin/emacs'
Во втором случае создаёт ссылку dir/target указывающую на target. В третьем случае создаёт несколько жёстких ссылок в каталоге dir, указывающих на каждый элемент в targets. Если dir не является каталогом, возникает исключение Errno::ENOTDIR.
FileUtils.cd '/sbin' FileUtils.ln %w(cp mv mkdir), '/bin' # Now /sbin/cp and /bin/cp are linked.
# File lib/fileutils.rb, line 369
def ln_s(src, dest, force: nil, noop: nil, verbose: nil)
fu_output_message "ln -s#{force ? 'f' : ''} #{[src,dest].flatten.join ' '}" if verbose
return if noop
fu_each_src_dest0(src, dest) do |s,d|
remove_file d, true if force
File.symlink s, d
end
end В первом случае создаёт символическую ссылку link, которая указывает на target. Если link уже существует, возникает исключение Errno::EEXIST. Но если опция force установлена, переписывает link.
FileUtils.ln_s '/usr/bin/ruby', '/usr/local/bin/ruby' FileUtils.ln_s 'verylongsourcefilename.c', 'c', force: true
Во втором случае создаёт ссылку dir/target указывающую на target. В третьем случае создаёт несколько символических ссылок в каталоге dir, указывающих на каждый элемент в targets. Если dir не является каталогом, возникает исключение Errno::ENOTDIR.
FileUtils.ln_s Dir.glob('/bin/*.rb'), '/home/foo/bin'
# File lib/fileutils.rb, line 390 def ln_sf(src, dest, noop: nil, verbose: nil) ln_s src, dest, force: true, noop: noop, verbose: verbose end
То же, что
FileUtils.ln_s(*args, force: true)
# File lib/fileutils.rb, line 180
def mkdir(list, mode: nil, noop: nil, verbose: nil)
list = fu_list(list)
fu_output_message "mkdir #{mode ? ('-m %03o ' % mode) : ''}#{list.join ' '}" if verbose
return if noop
list.each do |dir|
fu_mkdir dir, mode
end
end Создаёт один или несколько каталогов.
FileUtils.mkdir 'test' FileUtils.mkdir %w(tmp data) FileUtils.mkdir 'notexist', noop: true # Does not really create. FileUtils.mkdir 'tmp', mode: 0700
# File lib/fileutils.rb, line 206
def mkdir_p(list, mode: nil, noop: nil, verbose: nil)
list = fu_list(list)
fu_output_message "mkdir -p #{mode ? ('-m %03o ' % mode) : ''}#{list.join ' '}" if verbose
return *list if noop
list.map {|path| remove_trailing_slash(path)}.each do |path|
# optimize for the most common case
begin
fu_mkdir path, mode
next
rescue SystemCallError
next if File.directory?(path)
end
stack = []
until path == stack.last # dirname("/")=="/", dirname("C:/")=="C:/"
stack.push path
path = File.dirname(path)
end
stack.pop # root directory should exist
stack.reverse_each do |dir|
begin
fu_mkdir dir, mode
rescue SystemCallError
raise unless File.directory?(dir)
end
end
end
return *list
end Создаёт каталог и все его родительские каталоги. Например,
FileUtils.mkdir_p '/usr/local/lib/ruby'
приводит к созданию следующих каталогов, если они не существуют.
-
/usr
-
/usr/local
-
/usr/local/lib
-
/usr/local/lib/ruby
Вы можете передать несколько каталогов одновременно в списке.
# File lib/fileutils.rb, line 536
def mv(src, dest, force: nil, noop: nil, verbose: nil, secure: nil)
fu_output_message "mv#{force ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if verbose
return if noop
fu_each_src_dest(src, dest) do |s, d|
destent = Entry_.new(d, nil, true)
begin
if destent.exist?
if destent.directory?
raise Errno::EEXIST, d
end
end
begin
File.rename s, d
rescue Errno::EXDEV,
Errno::EPERM # move from unencrypted to encrypted dir (ext4)
copy_entry s, d, true
if secure
remove_entry_secure s, force
else
remove_entry s, force
end
end
rescue SystemCallError
raise unless force
end
end
end Перемещает файл(ы) src в dest. Если file и dest находятся на разных разделах диска, файл копируется, а исходный файл удаляется.
FileUtils.mv 'badname.rb', 'goodname.rb'
FileUtils.mv 'stuff.rb', '/notexist/lib/ruby', force: true # no error
FileUtils.mv %w(junk.txt dust.txt), '/home/foo/.trash/'
FileUtils.mv Dir.glob('test*.rb'), 'test', noop: true, verbose: true
# File lib/fileutils.rb, line 1650
def self.options
OPT_TABLE.values.flatten.uniq.map {|sym| sym.to_s }
end Возвращает Array список имён опций.
p FileUtils.options #=> ["noop", "force", "verbose", "preserve", "mode"]
# File lib/fileutils.rb, line 1671
def self.options_of(mid)
OPT_TABLE[mid.to_s].map {|sym| sym.to_s }
end Возвращает Array список имён опций метода mid.
p FileUtils.options_of(:rm) #=> ["noop", "verbose", "force"]
# File lib/fileutils.rb, line 115 def pwd Dir.pwd end
Возвращает имя текущего каталога.
# File lib/fileutils.rb, line 805 def remove_dir(path, force = false) remove_entry path, force # FIXME?? check if it is a directory end
Удаляет директорию dir и её содержимое рекурсивно. Этот метод игнорирует StandardError, если force равно true.
# File lib/fileutils.rb, line 777
def remove_entry(path, force = false)
Entry_.new(path).postorder_traverse do |ent|
begin
ent.remove
rescue
raise unless force
end
end
rescue
raise unless force
end Этот метод удаляет элемент файловой системы path. path может быть обычным файлом, директорией или чем-то другим. Если path является директорией, удаляет её рекурсивно.
См. также remove_entry_secure.
# File lib/fileutils.rb, line 684
def remove_entry_secure(path, force = false)
unless fu_have_symlink?
remove_entry path, force
return
end
fullpath = File.expand_path(path)
st = File.lstat(fullpath)
unless st.directory?
File.unlink fullpath
return
end
# is a directory.
parent_st = File.stat(File.dirname(fullpath))
unless parent_st.world_writable?
remove_entry path, force
return
end
unless parent_st.sticky?
raise ArgumentError, "parent directory is world writable, FileUtils#remove_entry_secure does not work; abort: #{path.inspect} (parent directory mode #{'%o' % parent_st.mode})"
end
# freeze tree root
euid = Process.euid
dot_file = fullpath + "/."
begin
File.open(dot_file) {|f|
unless fu_stat_identical_entry?(st, f.stat)
# symlink (TOC-to-TOU attack?)
File.unlink fullpath
return
end
f.chown euid, -1
f.chmod 0700
}
rescue Errno::EISDIR # JRuby in non-native mode can't open files as dirs
File.lstat(dot_file).tap {|fstat|
unless fu_stat_identical_entry?(st, fstat)
# symlink (TOC-to-TOU attack?)
File.unlink fullpath
return
end
File.chown euid, -1, dot_file
File.chmod 0700, dot_file
}
end
unless fu_stat_identical_entry?(st, File.lstat(fullpath))
# TOC-to-TOU attack?
File.unlink fullpath
return
end
# ---- tree root is frozen ----
root = Entry_.new(path)
root.preorder_traverse do |ent|
if ent.directory?
ent.chown euid, -1
ent.chmod 0700
end
end
root.postorder_traverse do |ent|
begin
ent.remove
rescue
raise unless force
end
end
rescue
raise unless force
end Этот метод удаляет элемент файловой системы path. path должен быть обычным файлом, директорией или чем-то другим. Если path является директорией, удаляет её рекурсивно. Этот метод необходим для предотвращения локальной проблемы безопасности TOCTTOU (time-of-check-to-time-of-use) метода rm_r. rm_r создаёт уязвимость в безопасности, когда:
-
Родительская директория имеет права записи для всех пользователей (включая /tmp).
-
Удаляемое дерево директорий включает директорию с правами записи для всех пользователей.
-
В системе присутствуют символические ссылки.
Чтобы избежать этой проблемы безопасности, этот метод применяет специальную предобработку. Если path является директорией, этот метод использует chown(2) и chmod(2) для всех удаляемых директорий. Это требует, чтобы текущий процесс был владельцем удаляемого дерева директорий или имел права суперпользователя (root).
ВНИМАНИЕ: Вы должны убедиться, что ВСЕ родительские директории не могут быть изменены другими неуверенными пользователями. Например, родительские директории не должны принадлежать неуверенным пользователям и не должны иметь права записи для всех пользователей, за исключением случаев, когда установлен бит "sticky".
ВНИМАНИЕ: Только владелец удаляемого дерева директорий или суперпользователь Unix (root) должны вызывать этот метод. В противном случае метод не сработает.
Для получения подробностей об этой проблеме безопасности см. случай Perl:
Для fileutils.rb эта уязвимость была сообщена в [ruby-dev:26100].
# File lib/fileutils.rb, line 794 def remove_file(path, force = false) Entry_.new(path).remove_file rescue raise unless force end
Удаляет файл path. Этот метод игнорирует StandardError, если force равно true.
# File lib/fileutils.rb, line 576
def rm(list, force: nil, noop: nil, verbose: nil)
list = fu_list(list)
fu_output_message "rm#{force ? ' -f' : ''} #{list.join ' '}" if verbose
return if noop
list.each do |path|
remove_file path, force
end
end Удаляет указанные в list файлы. Этот метод не может удалять директории. Все StandardErrors игнорируются, когда установлен параметр :force.
FileUtils.rm %w( junk.txt dust.txt )
FileUtils.rm Dir.glob('*.so')
FileUtils.rm 'NotExistFile', force: true # never raises exception
# File lib/fileutils.rb, line 595 def rm_f(list, noop: nil, verbose: nil) rm list, force: true, noop: noop, verbose: verbose end
Эквивалентно
FileUtils.rm(list, force: true)
# File lib/fileutils.rb, line 622
def rm_r(list, force: nil, noop: nil, verbose: nil, secure: nil)
list = fu_list(list)
fu_output_message "rm -r#{force ? 'f' : ''} #{list.join ' '}" if verbose
return if noop
list.each do |path|
if secure
remove_entry_secure path, force
else
remove_entry path, force
end
end
end удаляет файлы list[0] list[1]… Если list[n] является директорией, удаляет её и всё содержимое рекурсивно. Этот метод игнорирует StandardError, когда установлен параметр :force.
FileUtils.rm_r Dir.glob('/tmp/*')
FileUtils.rm_r 'some_dir', force: true
ВНИМАНИЕ: Этот метод может привести к локальной уязвимости, если одна из родительских директорий или удаляемое дерево директорий имеют права записи для всех пользователей (включая /tmp, чьи права 1777), и текущий процесс обладает сильными привилегиями, такими как права суперпользователя (root), а в системе есть символические ссылки. Для безопасного удаления, внимательно изучите документацию remove_entry_secure и установите параметр :secure в значение true. По умолчанию secure: false.
ПРИМЕЧАНИЕ: Этот метод вызывает remove_entry_secure, если установлен параметр :secure. См. также remove_entry_secure.
# File lib/fileutils.rb, line 644 def rm_rf(list, noop: nil, verbose: nil, secure: nil) rm_r list, force: true, noop: noop, verbose: verbose, secure: secure end
Эквивалентно
FileUtils.rm_r(list, force: true)
ВНИМАНИЕ: Этот метод может привести к локальной уязвимости. Прочитайте документацию rm_r прежде чем использовать.
# File lib/fileutils.rb, line 263
def rmdir(list, parents: nil, noop: nil, verbose: nil)
list = fu_list(list)
fu_output_message "rmdir #{parents ? '-p ' : ''}#{list.join ' '}" if verbose
return if noop
list.each do |dir|
Dir.rmdir(dir = remove_trailing_slash(dir))
if parents
begin
until (parent = File.dirname(dir)) == '.' or parent == dir
dir = parent
Dir.rmdir(dir)
end
rescue Errno::ENOTEMPTY, Errno::EEXIST, Errno::ENOENT
end
end
end
end Удаляет одну или несколько директорий.
FileUtils.rmdir 'somedir' FileUtils.rmdir %w(somedir anydir otherdir) # Does not really remove directory; outputs message. FileUtils.rmdir 'somedir', verbose: true, noop: true
# File lib/fileutils.rb, line 1137
def touch(list, noop: nil, verbose: nil, mtime: nil, nocreate: nil)
list = fu_list(list)
t = mtime
if verbose
fu_output_message "touch #{nocreate ? '-c ' : ''}#{t ? t.strftime('-t %Y%m%d%H%M.%S ') : ''}#{list.join ' '}"
end
return if noop
list.each do |path|
created = nocreate
begin
File.utime(t, t, path)
rescue Errno::ENOENT
raise if created
File.open(path, 'a') {
;
}
created = true
retry if t
end
end
end Обновляет время изменения (mtime) и время доступа (atime) файлов в list. Файлы создаются, если они не существуют.
FileUtils.touch 'timestamp'
FileUtils.touch Dir.glob('*.c'); system 'make'
# File lib/fileutils.rb, line 155
def uptodate?(new, old_list)
return false unless File.exist?(new)
new_time = File.mtime(new)
old_list.each do |old|
if File.exist?(old)
return false unless new_time > File.mtime(old)
end
end
true
end Возвращает true, если new новее всех old_list. Несуществующие файлы считаются старее любого файла.
FileUtils.uptodate?('hello.o', %w(hello.c hello.h)) or \
system 'make hello.o'
Методы приватного экземпляра
# File lib/fileutils.rb, line 137
def cd(dir, verbose: nil, &block) # :yield: dir
fu_output_message "cd #{dir}" if verbose
result = Dir.chdir(dir, &block)
fu_output_message 'cd -' if verbose and block
result
end Изменяет текущую директорию на директорию dir.
Если этот метод вызывается с блоком, он возвращается к предыдущей рабочей директории после завершения выполнения блока.
FileUtils.cd('/') # change directory
FileUtils.cd('/', verbose: true) # change directory and report it
FileUtils.cd('/') do # change directory
# ... # do something
end # return to original directory
# File lib/fileutils.rb, line 1012
def chmod(mode, list, noop: nil, verbose: nil)
list = fu_list(list)
fu_output_message sprintf('chmod %s %s', mode_to_s(mode), list.join(' ')) if verbose
return if noop
list.each do |path|
Entry_.new(path).chmod(fu_mode(mode, path))
end
end Изменяет биты разрешений указанных файлов (в list) на битовую схему, представленную mode.
Можно использовать символический и абсолютный режимы.
Абсолютный режим
FileUtils.chmod 0755, 'somecommand' FileUtils.chmod 0644, %w(my.rb your.rb his.rb her.rb) FileUtils.chmod 0755, '/usr/bin/ruby', verbose: true
Символический режим
FileUtils.chmod "u=wrx,go=rx", 'somecommand' FileUtils.chmod "u=wr,go=rr", %w(my.rb your.rb his.rb her.rb) FileUtils.chmod "u=wrx,go=rx", '/usr/bin/ruby', verbose: true
- “a”
-
— маска пользователя, группы и других.
- “u”
-
— маска пользователя.
- “g”
-
— маска группы.
- “o”
-
— маска других.
- “w”
-
— разрешение на запись.
- “r”
-
— разрешение на чтение.
- “x”
-
— разрешение на выполнение.
- “X”
-
— разрешение на выполнение только для каталогов, должно использоваться совместно с “+”
- “s”
-
— uid, gid.
- “t”
-
— битовая отметка.
- “+”
-
— добавляется к классу, учитывая указанный режим.
- “-”
-
Удаляется из указанного класса, учитывая указанный режим.
- “=”
-
Определяет точный характер класса, учитывая указанный режим.
# File lib/fileutils.rb, line 1029
def chmod_R(mode, list, noop: nil, verbose: nil, force: nil)
list = fu_list(list)
fu_output_message sprintf('chmod -R%s %s %s',
(force ? 'f' : ''),
mode_to_s(mode), list.join(' ')) if verbose
return if noop
list.each do |root|
Entry_.new(root).traverse do |ent|
begin
ent.chmod(fu_mode(mode, ent.path))
rescue
raise unless force
end
end
end
end Изменяет биты разрешений указанных файлов (в list) на битовую схему, представленную mode.
FileUtils.chmod_R 0700, "/tmp/app.#{$$}"
FileUtils.chmod_R "u=wrx", "/tmp/app.#{$$}"
# File lib/fileutils.rb, line 1057
def chown(user, group, list, noop: nil, verbose: nil)
list = fu_list(list)
fu_output_message sprintf('chown %s %s',
(group ? "#{user}:#{group}" : user || ':'),
list.join(' ')) if verbose
return if noop
uid = fu_get_uid(user)
gid = fu_get_gid(group)
list.each do |path|
Entry_.new(path).chown uid, gid
end
end Изменяет владельца и группу указанных файлов (в list) на пользователя user и группу group. user и group могут быть идентификатором (целое число/строка) или именем (String). Если user или group равно null, этот метод не изменяет атрибут.
FileUtils.chown 'root', 'staff', '/usr/local/bin/ruby'
FileUtils.chown nil, 'bin', Dir.glob('/usr/bin/*'), verbose: true
# File lib/fileutils.rb, line 1081
def chown_R(user, group, list, noop: nil, verbose: nil, force: nil)
list = fu_list(list)
fu_output_message sprintf('chown -R%s %s %s',
(force ? 'f' : ''),
(group ? "#{user}:#{group}" : user || ':'),
list.join(' ')) if verbose
return if noop
uid = fu_get_uid(user)
gid = fu_get_gid(group)
list.each do |root|
Entry_.new(root).traverse do |ent|
begin
ent.chown uid, gid
rescue
raise unless force
end
end
end
end Рекурсивно изменяет владельца и группу указанных файлов (в list) на пользователя user и группу group. user и group могут быть идентификатором (целое число/строка) или именем (String). Если user или group равно null, этот метод не изменяет атрибут.
FileUtils.chown_R 'www', 'www', '/var/www/htdocs' FileUtils.chown_R 'cvs', 'cvs', '/var/cvs', verbose: true
# File lib/fileutils.rb, line 816
def compare_file(a, b)
return false unless File.size(a) == File.size(b)
File.open(a, 'rb') {|fa|
File.open(b, 'rb') {|fb|
return compare_stream(fa, fb)
}
}
end Возвращает true, если содержимое файла a и файла b идентичны.
FileUtils.compare_file('somefile', 'somefile') #=> true
FileUtils.compare_file('/dev/null', '/dev/urandom') #=> false
# File lib/fileutils.rb, line 834
def compare_stream(a, b)
bsize = fu_stream_blksize(a, b)
if RUBY_VERSION > "2.4"
sa = String.new(capacity: bsize)
sb = String.new(capacity: bsize)
else
sa = String.new
sb = String.new
end
begin
a.read(bsize, sa)
b.read(bsize, sb)
return true if sa.empty? && sb.empty?
end while sa == sb
false
end Возвращает true, если содержимое потока a и b идентичны.
# File lib/fileutils.rb, line 489
def copy_entry(src, dest, preserve = false, dereference_root = false, remove_destination = false)
if dereference_root
src = File.realpath(src)
end
Entry_.new(src, nil, false).wrap_traverse(proc do |ent|
destent = Entry_.new(dest, ent.rel, false)
File.unlink destent.path if remove_destination && (File.file?(destent.path) || File.symlink?(destent.path))
ent.copy destent.path
end, proc do |ent|
destent = Entry_.new(dest, ent.rel, false)
ent.copy_metadata destent.path if preserve
end)
end Копирует элемент файловой системы src в dest. Если src — это каталог, этот метод рекурсивно копирует его содержимое. Этот метод сохраняет типы файлов, например, симлинки, каталоги… (FIFO, файлы устройств и т. д. пока не поддерживаются)
Оба src и dest должны быть именами путей. src должен существовать, dest не должен существовать.
Если preserve true, этот метод сохраняет владельца, группу и время изменения. Разрешения копируются независимо от preserve.
Если dereference_root true, этот метод де-ссылает корень дерева.
Если remove_destination true, этот метод удаляет каждый целевой файл перед копированием.
# File lib/fileutils.rb, line 509 def copy_file(src, dest, preserve = false, dereference = true) ent = Entry_.new(src, nil, dereference) ent.copy_file dest ent.copy_metadata dest if preserve end
Копирует содержимое файла src в dest. Оба src и dest должны быть именами путей.
# File lib/fileutils.rb, line 521 def copy_stream(src, dest) IO.copy_stream(src, dest) end
Копирует поток src в dest. src должен отвечать на read(n), а dest — на write(str).
# File lib/fileutils.rb, line 426
def cp(src, dest, preserve: nil, noop: nil, verbose: nil)
fu_output_message "cp#{preserve ? ' -p' : ''} #{[src,dest].flatten.join ' '}" if verbose
return if noop
fu_each_src_dest(src, dest) do |s, d|
copy_file s, d, preserve
end
end Копирует содержимое файла src в dest. Если dest является каталогом, копирует src в dest/src.
Если src — список файлов, то dest должен быть каталогом.
FileUtils.cp 'eval.c', 'eval.c.org' FileUtils.cp %w(cgi.rb complex.rb date.rb), '/usr/lib/ruby/1.6' FileUtils.cp %w(cgi.rb complex.rb date.rb), '/usr/lib/ruby/1.6', verbose: true FileUtils.cp 'symlink', 'dest' # copy content, "dest" is not a symlink
# File lib/fileutils.rb, line 339
def cp_lr(src, dest, noop: nil, verbose: nil,
dereference_root: true, remove_destination: false)
fu_output_message "cp -lr#{remove_destination ? ' --remove-destination' : ''} #{[src,dest].flatten.join ' '}" if verbose
return if noop
fu_each_src_dest(src, dest) do |s, d|
link_entry s, d, dereference_root, remove_destination
end
end Жёсткая ссылка src на dest. Если src — каталог, этот метод создаёт ссылки на всё его содержимое рекурсивно. Если dest — каталог, создаёт ссылки src на dest/src.
src может быть списком файлов.
Если dereference_root true, этот метод де-ссылает корень дерева.
Если remove_destination true, этот метод удаляет каждый целевой файл перед копированием.
FileUtils.rm_r site_ruby + '/mylib', force: true
FileUtils.cp_lr 'lib/', site_ruby + '/mylib'
# Examples of linking several files to target directory.
FileUtils.cp_lr %w(mail.rb field.rb debug/), site_ruby + '/tmail'
FileUtils.cp_lr Dir.glob('*.rb'), '/home/aamine/lib/ruby', noop: true, verbose: true
# If you want to link all contents of a directory instead of the
# directory itself, c.f. src/x -> dest/x, src/y -> dest/y,
# use the following code.
FileUtils.cp_lr 'src/.', 'dest' # cp_lr('src', 'dest') makes dest/src, but this doesn't.
# File lib/fileutils.rb, line 463
def cp_r(src, dest, preserve: nil, noop: nil, verbose: nil,
dereference_root: true, remove_destination: nil)
fu_output_message "cp -r#{preserve ? 'p' : ''}#{remove_destination ? ' --remove-destination' : ''} #{[src,dest].flatten.join ' '}" if verbose
return if noop
fu_each_src_dest(src, dest) do |s, d|
copy_entry s, d, preserve, dereference_root, remove_destination
end
end Копирует src в dest. Если src — каталог, этот метод рекурсивно копирует всё его содержимое. Если dest — каталог, копирует src в dest/src.
src может быть списком файлов.
Если dereference_root true, этот метод де-ссылает корень дерева.
Если remove_destination true, этот метод удаляет каждый целевой файл перед копированием.
# Installing Ruby library "mylib" under the site_ruby
FileUtils.rm_r site_ruby + '/mylib', force: true
FileUtils.cp_r 'lib/', site_ruby + '/mylib'
# Examples of copying several files to target directory.
FileUtils.cp_r %w(mail.rb field.rb debug/), site_ruby + '/tmail'
FileUtils.cp_r Dir.glob('*.rb'), '/home/foo/lib/ruby', noop: true, verbose: true
# If you want to copy all contents of a directory instead of the
# directory itself, c.f. src/x -> dest/x, src/y -> dest/y,
# use following code.
FileUtils.cp_r 'src/.', 'dest' # cp_r('src', 'dest') makes dest/src,
# but this doesn't.
# File lib/fileutils.rb, line 862
def install(src, dest, mode: nil, owner: nil, group: nil, preserve: nil,
noop: nil, verbose: nil)
if verbose
msg = +"install -c"
msg << ' -p' if preserve
msg << ' -m ' << mode_to_s(mode) if mode
msg << " -o #{owner}" if owner
msg << " -g #{group}" if group
msg << ' ' << [src,dest].flatten.join(' ')
fu_output_message msg
end
return if noop
uid = fu_get_uid(owner)
gid = fu_get_gid(group)
fu_each_src_dest(src, dest) do |s, d|
st = File.stat(s)
unless File.exist?(d) and compare_file(s, d)
remove_file d, true
copy_file s, d
File.utime st.atime, st.mtime, d if preserve
File.chmod fu_mode(mode, st), d if mode
File.chown uid, gid, d if uid or gid
end
end
end Если src не совпадает с dest, копирует его и изменяет права доступа на mode. Если dest — это каталог, то целевой путь — dest/src. Этот метод удаляет целевой файл перед копированием.
FileUtils.install 'ruby', '/usr/local/bin/ruby', mode: 0755, verbose: true FileUtils.install 'lib.rb', '/usr/local/lib/ruby/site_ruby', verbose: true
# File lib/fileutils.rb, line 406
def link_entry(src, dest, dereference_root = false, remove_destination = false)
Entry_.new(src, nil, dereference_root).traverse do |ent|
destent = Entry_.new(dest, ent.rel, false)
File.unlink destent.path if remove_destination && File.file?(destent.path)
ent.link destent.path
end
end Создаёт жёсткую ссылку на системный элемент src в dest. Если src — это каталог, этот метод создаёт ссылки на его содержимое рекурсивно.
Оба значения src и dest должны быть именами путей. src должен существовать, dest — нет.
Если dereference_root равно true, этот метод дескриптирует корень дерева.
Если remove_destination равно true, этот метод удаляет каждый целевой файл перед копированием.
# File lib/fileutils.rb, line 303
def ln(src, dest, force: nil, noop: nil, verbose: nil)
fu_output_message "ln#{force ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if verbose
return if noop
fu_each_src_dest0(src, dest) do |s,d|
remove_file d, true if force
File.link s, d
end
end В первом варианте создаёт жёсткую ссылку link, которая указывает на target. Если link уже существует, генерируется исключение Errno::EEXIST. Но если опция force установлена, перезаписывает link.
FileUtils.ln 'gcc', 'cc', verbose: true FileUtils.ln '/usr/bin/emacs21', '/usr/bin/emacs'
Во втором варианте создаёт ссылку dir/target, указывающую на target. В третьем варианте создаёт несколько жёстких ссылок в каталоге dir, указывающих на каждый элемент в targets. Если dir не является каталогом, генерируется исключение Errno::ENOTDIR.
FileUtils.cd '/sbin' FileUtils.ln %w(cp mv mkdir), '/bin' # Now /sbin/cp and /bin/cp are linked.
# File lib/fileutils.rb, line 369
def ln_s(src, dest, force: nil, noop: nil, verbose: nil)
fu_output_message "ln -s#{force ? 'f' : ''} #{[src,dest].flatten.join ' '}" if verbose
return if noop
fu_each_src_dest0(src, dest) do |s,d|
remove_file d, true if force
File.symlink s, d
end
end В первом варианте создаёт символическую ссылку link, которая указывает на target. Если link уже существует, генерируется исключение Errno::EEXIST. Но если опция force установлена, перезаписывает link.
FileUtils.ln_s '/usr/bin/ruby', '/usr/local/bin/ruby' FileUtils.ln_s 'verylongsourcefilename.c', 'c', force: true
Во втором варианте создаёт ссылку dir/target, указывающую на target. В третьем варианте создаёт несколько символических ссылок в каталоге dir, указывающих на каждый элемент в targets. Если dir не является каталогом, генерируется исключение Errno::ENOTDIR.
FileUtils.ln_s Dir.glob('/bin/*.rb'), '/home/foo/bin'
# File lib/fileutils.rb, line 390 def ln_sf(src, dest, noop: nil, verbose: nil) ln_s src, dest, force: true, noop: noop, verbose: verbose end
То же, что
FileUtils.ln_s(*args, force: true)
# File lib/fileutils.rb, line 180
def mkdir(list, mode: nil, noop: nil, verbose: nil)
list = fu_list(list)
fu_output_message "mkdir #{mode ? ('-m %03o ' % mode) : ''}#{list.join ' '}" if verbose
return if noop
list.each do |dir|
fu_mkdir dir, mode
end
end Создаёт один или несколько каталогов.
FileUtils.mkdir 'test' FileUtils.mkdir %w(tmp data) FileUtils.mkdir 'notexist', noop: true # Does not really create. FileUtils.mkdir 'tmp', mode: 0700
# File lib/fileutils.rb, line 206
def mkdir_p(list, mode: nil, noop: nil, verbose: nil)
list = fu_list(list)
fu_output_message "mkdir -p #{mode ? ('-m %03o ' % mode) : ''}#{list.join ' '}" if verbose
return *list if noop
list.map {|path| remove_trailing_slash(path)}.each do |path|
# optimize for the most common case
begin
fu_mkdir path, mode
next
rescue SystemCallError
next if File.directory?(path)
end
stack = []
until path == stack.last # dirname("/")=="/", dirname("C:/")=="C:/"
stack.push path
path = File.dirname(path)
end
stack.pop # root directory should exist
stack.reverse_each do |dir|
begin
fu_mkdir dir, mode
rescue SystemCallError
raise unless File.directory?(dir)
end
end
end
return *list
end Создаёт каталог и все его родительские каталоги. Например,
FileUtils.mkdir_p '/usr/local/lib/ruby'
приводит к созданию следующих каталогов, если они не существуют.
-
/usr
-
/usr/local
-
/usr/local/lib
-
/usr/local/lib/ruby
Вы можете передавать несколько каталогов одновременно в списке.
# File lib/fileutils.rb, line 536
def mv(src, dest, force: nil, noop: nil, verbose: nil, secure: nil)
fu_output_message "mv#{force ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if verbose
return if noop
fu_each_src_dest(src, dest) do |s, d|
destent = Entry_.new(d, nil, true)
begin
if destent.exist?
if destent.directory?
raise Errno::EEXIST, d
end
end
begin
File.rename s, d
rescue Errno::EXDEV,
Errno::EPERM # move from unencrypted to encrypted dir (ext4)
copy_entry s, d, true
if secure
remove_entry_secure s, force
else
remove_entry s, force
end
end
rescue SystemCallError
raise unless force
end
end
end Перемещает файл(ы) src в dest. Если file и dest находятся на разных разделах диска, файл копируется, а затем исходный файл удаляется.
FileUtils.mv 'badname.rb', 'goodname.rb'
FileUtils.mv 'stuff.rb', '/notexist/lib/ruby', force: true # no error
FileUtils.mv %w(junk.txt dust.txt), '/home/foo/.trash/'
FileUtils.mv Dir.glob('test*.rb'), 'test', noop: true, verbose: true
# File lib/fileutils.rb, line 115 def pwd Dir.pwd end
Возвращает имя текущего каталога.
# File lib/fileutils.rb, line 805 def remove_dir(path, force = false) remove_entry path, force # FIXME?? check if it is a directory end
Удаляет каталог dir и его содержимое рекурсивно. Этот метод игнорирует StandardError, если force равно true.
# File lib/fileutils.rb, line 777
def remove_entry(path, force = false)
Entry_.new(path).postorder_traverse do |ent|
begin
ent.remove
rescue
raise unless force
end
end
rescue
raise unless force
end Этот метод удаляет системный элемент path. path может быть обычным файлом, каталогом или чем-то ещё. Если path является каталогом, удаляет его рекурсивно.
См. также remove_entry_secure.
# File lib/fileutils.rb, line 684
def remove_entry_secure(path, force = false)
unless fu_have_symlink?
remove_entry path, force
return
end
fullpath = File.expand_path(path)
st = File.lstat(fullpath)
unless st.directory?
File.unlink fullpath
return
end
# is a directory.
parent_st = File.stat(File.dirname(fullpath))
unless parent_st.world_writable?
remove_entry path, force
return
end
unless parent_st.sticky?
raise ArgumentError, "parent directory is world writable, FileUtils#remove_entry_secure does not work; abort: #{path.inspect} (parent directory mode #{'%o' % parent_st.mode})"
end
# freeze tree root
euid = Process.euid
dot_file = fullpath + "/."
begin
File.open(dot_file) {|f|
unless fu_stat_identical_entry?(st, f.stat)
# symlink (TOC-to-TOU attack?)
File.unlink fullpath
return
end
f.chown euid, -1
f.chmod 0700
}
rescue Errno::EISDIR # JRuby in non-native mode can't open files as dirs
File.lstat(dot_file).tap {|fstat|
unless fu_stat_identical_entry?(st, fstat)
# symlink (TOC-to-TOU attack?)
File.unlink fullpath
return
end
File.chown euid, -1, dot_file
File.chmod 0700, dot_file
}
end
unless fu_stat_identical_entry?(st, File.lstat(fullpath))
# TOC-to-TOU attack?
File.unlink fullpath
return
end
# ---- tree root is frozen ----
root = Entry_.new(path)
root.preorder_traverse do |ent|
if ent.directory?
ent.chown euid, -1
ent.chmod 0700
end
end
root.postorder_traverse do |ent|
begin
ent.remove
rescue
raise unless force
end
end
rescue
raise unless force
end Этот метод удаляет элемент файловой системы path. path должен быть обычным файлом, каталогом или чем-то подобным. Если path является каталогом, он удаляется рекурсивно. Этот метод необходим для предотвращения локальной проблемы безопасности TOCTTOU (time-of-check-to-time-of-use) при использовании rm_r. rm_r создаёт уязвимость, когда:
-
Родительский каталог имеет разрешения для записи всем (включая /tmp).
-
Дерево удаляемых каталогов включает каталог с разрешениями для записи всем.
-
В системе присутствуют символические ссылки.
Чтобы избежать этой уязвимости, этот метод применяет специальную предобработку. Если path является каталогом, этот метод использует chown(2) и chmod(2) для всех удаляемых каталогов. Это требует, чтобы текущий процесс был владельцем всего удаляемого дерева каталогов или являлся суперпользователем (root).
ВНИМАНИЕ: Вы должны убедиться, что ВСЕ родительские каталоги не могут быть изменены другими ненадежными пользователями. Например, родительские каталоги не должны принадлежать ненадежным пользователям и не должны иметь разрешения для записи всем, за исключением случаев, когда установлен бит "sticky".
ВНИМАНИЕ: Этот метод должен вызываться только владельцем удаляемого дерева каталогов или суперпользователем (root) системы. В противном случае метод не будет работать.
Подробная информация об этой уязвимости см. в примере с Perl:
Для fileutils.rb эта уязвимость была сообщена в [ruby-dev:26100].
# File lib/fileutils.rb, line 794 def remove_file(path, force = false) Entry_.new(path).remove_file rescue raise unless force end
Удаляет файл path. Этот метод игнорирует StandardError, если force равно true.
# File lib/fileutils.rb, line 576
def rm(list, force: nil, noop: nil, verbose: nil)
list = fu_list(list)
fu_output_message "rm#{force ? ' -f' : ''} #{list.join ' '}" if verbose
return if noop
list.each do |path|
remove_file path, force
end
end Удаляет файлы, указанные в list. Этот метод не может удалять каталоги. Все StandardErrors игнорируются, когда установлен параметр :force.
FileUtils.rm %w( junk.txt dust.txt )
FileUtils.rm Dir.glob('*.so')
FileUtils.rm 'NotExistFile', force: true # never raises exception
# File lib/fileutils.rb, line 595 def rm_f(list, noop: nil, verbose: nil) rm list, force: true, noop: noop, verbose: verbose end
Эквивалентно
FileUtils.rm(list, force: true)
# File lib/fileutils.rb, line 622
def rm_r(list, force: nil, noop: nil, verbose: nil, secure: nil)
list = fu_list(list)
fu_output_message "rm -r#{force ? 'f' : ''} #{list.join ' '}" if verbose
return if noop
list.each do |path|
if secure
remove_entry_secure path, force
else
remove_entry path, force
end
end
end Удаляет файлы list[0] list[1]… Если list[n] является каталогом, удаляет все его содержимое рекурсивно. Этот метод игнорирует StandardError при установленном параметре :force.
FileUtils.rm_r Dir.glob('/tmp/*')
FileUtils.rm_r 'some_dir', force: true
ВНИМАНИЕ: Этот метод может привести к локальной уязвимости, если один из родительских каталогов или удаляемое дерево каталогов имеют разрешения для записи всем (включая /tmp, чьи разрешения 1777), и текущий процесс имеет сильные привилегии, такие как суперпользователь (root), и в системе присутствуют символические ссылки. Для безопасного удаления внимательно прочитайте документацию remove_entry_secure, и установите параметр :secure в значение true. Значение по умолчанию - secure: false.
ПРИМЕЧАНИЕ: Этот метод вызывает remove_entry_secure, если установлен параметр :secure. См. также remove_entry_secure.
# File lib/fileutils.rb, line 644 def rm_rf(list, noop: nil, verbose: nil, secure: nil) rm_r list, force: true, noop: noop, verbose: verbose, secure: secure end
Эквивалентно
FileUtils.rm_r(list, force: true)
ВНИМАНИЕ: Этот метод может привести к локальной уязвимости. Сначала прочитайте документацию rm_r.
# File lib/fileutils.rb, line 263
def rmdir(list, parents: nil, noop: nil, verbose: nil)
list = fu_list(list)
fu_output_message "rmdir #{parents ? '-p ' : ''}#{list.join ' '}" if verbose
return if noop
list.each do |dir|
Dir.rmdir(dir = remove_trailing_slash(dir))
if parents
begin
until (parent = File.dirname(dir)) == '.' or parent == dir
dir = parent
Dir.rmdir(dir)
end
rescue Errno::ENOTEMPTY, Errno::EEXIST, Errno::ENOENT
end
end
end
end Удаляет один или несколько каталогов.
FileUtils.rmdir 'somedir' FileUtils.rmdir %w(somedir anydir otherdir) # Does not really remove directory; outputs message. FileUtils.rmdir 'somedir', verbose: true, noop: true
# File lib/fileutils.rb, line 1137
def touch(list, noop: nil, verbose: nil, mtime: nil, nocreate: nil)
list = fu_list(list)
t = mtime
if verbose
fu_output_message "touch #{nocreate ? '-c ' : ''}#{t ? t.strftime('-t %Y%m%d%H%M.%S ') : ''}#{list.join ' '}"
end
return if noop
list.each do |path|
created = nocreate
begin
File.utime(t, t, path)
rescue Errno::ENOENT
raise if created
File.open(path, 'a') {
;
}
created = true
retry if t
end
end
end Обновляет время изменения (mtime) и время доступа (atime) файла(ов) в list. Файлы создаются, если они не существуют.
FileUtils.touch 'timestamp'
FileUtils.touch Dir.glob('*.c'); system 'make'
# File lib/fileutils.rb, line 155
def uptodate?(new, old_list)
return false unless File.exist?(new)
new_time = File.mtime(new)
old_list.each do |old|
if File.exist?(old)
return false unless new_time > File.mtime(old)
end
end
true
end Возвращает true, если new новее всех old_list. Несуществующие файлы считаются старее любого файла.
FileUtils.uptodate?('hello.o', %w(hello.c hello.h)) or \
system 'make hello.o'
Ruby Core © 1993–2017 Yukihiro Matsumoto
Licensed under the Ruby License.
Ruby Standard Library © contributors
Licensed under their own licenses.