Модуль FileUtils
fileutils.rb
Авторское право © 2000-2007 Minero Aoki
Эта программа является свободным программным обеспечением. Вы можете распространять/изменять эту программу на тех же условиях, что и ruby.
Модуль 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.
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”
битовая метка «sticky».
- “+”
добавляется к классу, задавая указанный режим.
- “-”
удаляется из класса, задавая указанный режим.
- “=”
задает точное значение класса, задавая указанный режим.
# 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 равно 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 могут быть идентификаторами (целое число/строка) или именами (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 1688
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 1649 def self.commands OPT_TABLE.keys end
Возвращает Array имён высокоуровневых методов, которые принимают произвольные ключевые аргументы.
p FileUtils.commands #=> ["chmod", "cp", "cp_r", "install", ...]
# File lib/fileutils.rb, line 819
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 837
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 492
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 512 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 524 def copy_stream(src, dest) IO.copy_stream(src, dest) end
Копирует поток src в dest. src должен поддерживать чтение (read(n)), а dest — запись (write(str)).
# File lib/fileutils.rb, line 429
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 342
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 466
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 1669
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 865
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 409
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 306
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 372
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 393 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.each do |item|
path = remove_trailing_slash(item)
# 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)
break if File.directory?(path)
end
stack.pop if path == stack.last # 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 539
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 1658
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 1679
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 808 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 780
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 687
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 (время проверки — время использования) метода rm_r. rm_r создаёт уязвимость, когда:
-
Родительская директория имеет разрешение на запись для всех (включая /tmp).
-
Удаляемая древовидная структура директорий включает директорию с разрешением на запись для всех.
-
Система использует символические ссылки.
Для предотвращения этой уязвимости этот метод применяет специальную предобработку. Если path является директорией, этот метод использует chown(2) и chmod(2) для всех удаляемых директорий. Это требует, чтобы текущий процесс был владельцем удаляемой древовидной структуры директорий или являлся суперпользователем (root).
ПРЕДУПРЕЖДЕНИЕ: Вы должны убедиться, что ВСЕ родительские директории не могут быть изменены другими неуправляемыми пользователями. Например, родительские директории не должны принадлежать неуправляемым пользователям и не должны иметь разрешения на запись для всех, кроме случаев, когда установлен бит «клейкости».
ПРЕДУПРЕЖДЕНИЕ: Только владелец удаляемой древовидной структуры директорий или суперпользователь (root) должны вызывать этот метод. В противном случае этот метод не будет работать.
Подробнее об этой уязвимости см. пример из Perl:
Для fileutils.rb эта уязвимость была сообщена в [ruby-dev:26100].
# File lib/fileutils.rb, line 797 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 579
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 598 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 625
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 установлен.
# File lib/fileutils.rb, line 647 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 266
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”
-
является битом "sticky".
- “+”
-
добавляется к классу, задавая указанный режим.
- “-”
-
Удаляется из данного класса, задавая указанный режим.
- “=”
-
Указывает точный характер класса, задавая указанный режим.
# 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 819
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 837
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 492
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 512 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 524 def copy_stream(src, dest) IO.copy_stream(src, dest) end
Копирует поток src в dest. src должен отвечать на read(n), а dest — на write(str).
# File lib/fileutils.rb, line 429
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 342
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 466
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 865
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 409
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 306
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 372
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 393 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.each do |item|
path = remove_trailing_slash(item)
# 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)
break if File.directory?(path)
end
stack.pop if path == stack.last # 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 539
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 808 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 780
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 687
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 (время проверки — время использования) метода 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 797 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 579
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 598 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 625
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), текущий процесс имеет высокие привилегии, такие как суперпользователь Unix (root), а в системе присутствуют символические ссылки. Для безопасного удаления внимательно изучите документацию метода remove_entry_secure и установите параметр :secure в значение true. Значение по умолчанию — secure: false.
ПРИМЕЧАНИЕ: Этот метод вызывает remove_entry_secure, если установлен параметр :secure. См. также remove_entry_secure.
# File lib/fileutils.rb, line 647 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 266
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–2020 Yukihiro Matsumoto
Licensed under the Ruby License.
Ruby Standard Library © contributors
Licensed under their own licenses.