moriokaの日記: pdumpfs for win32/NTFS
日記 by
morioka
高林さんのpdumpfsをwin32/NTFSに移してみた。
(FATだとハードリンクが作成できないので、無駄が多いがコピーするようにした)
backslash->slashへの変換と、special folderの展開を含めている
ActiveScriptRuby 1.6.8.0 + pdumpfs 0.6(改) + Windows2000SP3(FAT/NTFS)で動作確認している。
#! /usr/local/bin/ruby
#
# pdumpfs 0.6 - a daily backup system similar to Plan9's dumpfs.
#
# DESCRIPTION:
#
# pdumpfs is a simple daily backup system similar to
# Plan9's dumpfs which preserves every daily snapshot.
# You can access the past snapshots at any time for
# retrieving a certain day's file. Let's backup your home
# directory with pdumpfs!
#
# pdumpfs constructs the snapshot YYYY/MM/DD in the
# destination directory. All source files are copied to
# the snapshot directory for the first time. On and after
# the second time, pdumpfs copies only updated or newly
# created files and stores unchanged files as hard links
# to the files of the previous day's snapshot for saving a
# disk space.
#
# USAGE:
#
# % pdumpfs <source directory> <destination directory>
# [<destination basename>]
#
# SAMPLE CRONTAB ENTRY:
#
# 00 05 * * * pdumpfs /home/USER /backup >/dev/null 2>&1
#
# BUGS:
#
# pdumpfs can handle only normal files, directories, and
# symbolic links.
#
#
# $Id: pdumpfs,v 1.37 2002/08/06 08:51:06 satoru Exp $
#
# Copyright (C) 2001 Satoru Takabayashi <satoru@namazu.org>
# All rights reserved.
# This is free software with ABSOLUTELY NO WARRANTY.
#
# You can redistribute it and/or modify it under the terms of
# the GNU General Public License version 2.
#
# win32 ported by Yasuhiro Morioka <yasuhiro.morioka@k5.dion.ne.jp>
# 2003/02/01
require 'find'
require 'date'
require 'ftools'
###########
require 'Win32API'
class Win32API
def Win32API.CreateHardLink( f, e, sa = 0)
createHardLink = Win32API.new("kernel32", "CreateHardLinkA", %w(p p l), 'i')
# ret = createHardLink.Call("/temp/aa.txt", "/temp/senpuu.asm.txt", 0)
# print ret;
return createHardLink.Call( f, e, sa)
end
end
class File
def File.link(l, t)
return Win32API.CreateHardLink(t, l)
end
end
GENERIC_WRITE = 0x40000000
GENERIC_EXECUTE = 0x20000000
GENERIC_ALL = 0x10000000
OPEN_EXISTING = 3
FILE_FLAG_BACKUP_SEMANTICS = 0x02000000
#############
def win32_dir_utime(a, m, dir)
hDir = 0
createFile = Win32API.new("kernel32", "CreateFileA", ['P','L','L','L','L','L','L'], "L")
closeHandle = Win32API.new("kernel32", "CloseHandle", ['L'], 'I')
getLocalTime = Win32API.new("kernel32", "GetLocalTime", %w(P), 'V')
# setLocalTime = Win32API.new("kernel32", "SetLocalTime", %w(P), 'V')
# getLastError = Win32API.new("kernel32", "GetLastError", [], 'L')
systemtimeToFileTime = Win32API.new("kernel32", "SystemTimeToFileTime", ['P', 'P'], 'I')
setFileTime = Win32API.new("kernel32", "SetFileTime", ['L','P','P','P'],"I")
# pdumpfsではlocaltimeで格納されているのだが、
# SetFileTime APIはNTFSの場合、UTCで読み書きするそうなので...
# utime()では、localtimeに移されているのだろう
atime = a
atime.utc
mtime = m
mtime.utc
pSYSTEMTIME_a = ' ' * 2 * 8 # 2byte x 8
pFILETIME_a = ' ' * 2 * 8 # 2byte x 8
getLocalTime.call(pSYSTEMTIME_a)
t1 = pSYSTEMTIME_a.unpack("S8")
t1[0..1] = atime.year, atime.month
t1[3..6] = atime.day, atime.hour, atime.min, atime.sec
systemtimeToFileTime.call(t1.pack("S8"), pFILETIME_a)
pSYSTEMTIME_m = ' ' * 2 * 8 # 2byte x 8
pFILETIME_m = ' ' * 2 * 8 # 2byte x 8
getLocalTime.call(pSYSTEMTIME_m)
t2 = pSYSTEMTIME_m.unpack("S8")
t2[0..1] = mtime.year, mtime.month
t2[3..6] = mtime.day, mtime.hour, mtime.min, mtime.sec
systemtimeToFileTime.call(t2.pack("S8"), pFILETIME_m)
d=dir.dup # なぜか、次のcallでfrozenだと判断されるので
hDir = createFile.Call(d, GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0)
setFileTime.call(hDir, 0, pFILETIME_a, pFILETIME_m)
closeHandle.Call(hDir)
return 0
end
###########
require "win32ole"
def expand_special_folders(dir)
/^@(AllUsersDesktop|AllUsersStartMenu|AllUsersPrograms|AllUsersStartup|Desktop|Favorites|Fonts|MyDocuments|NetHood|PrintHood|Programs|Recent|SendTo|StartMenu|Startup|Templates)/ =~ dir
if $& == nil
val = dir
else
rest = $'
/^@/ =~ $&
shell = WIN32OLE.new("WScript.Shell")
val = shell.SpecialFolders($') + rest
end
return val.tr('\\','/')
end
#########
def usage
puts "Usage: pdumpfs <source directory> <destination directory>"+
" [destination basename]"
exit 1
end
def nodir(dir)
puts "No directory: " + dir
exit 1
end
def same_file? (f1, f2)
File.symlink?(f1) == false && File.symlink?(f2) == false &&
File.file?(f1) && File.file?(f2) &&
File.size(f1) == File.size(f2) && File.mtime(f1) == File.mtime(f2)
end
def parse_options
usage if ARGV[0] == nil || ARGV[1] == nil
#
ARGV[0] = expand_special_folders(ARGV[0])
ARGV[1] = expand_special_folders(ARGV[1])
#
nodir ARGV[0] if File.directory?(ARGV[0]) == false
nodir ARGV[1] if File.directory?(ARGV[1]) == false
return ARGV
end
def datedir(date)
sprintf "%d/%02d/%02d", date.year, date.month, date.day
end
def latest_snapshot(src, dest, base)
for i in 1 .. 31 # allow at most 31 days absence
x = File.join dest, datedir(Date.today - i), base
return x if File.directory?(x)
end
nil
end
# incomplete substitute for cp -p
def copy(src, dest)
stat = File.stat(src)
File.copy src, dest
File.utime(stat.atime, stat.mtime, dest)
File.chmod(stat.mode, dest) # not necessary. just to make sure
end
def update_file(s, l, t)
type = "unsupported"
if File.symlink?(s) == false && File.directory?(s)
type = "directory"
File.mkpath t
else
if File.symlink?(l) == false && File.file?(l)
if same_file?(s, l)
type = "unchanged"
# File.link l, t
if File.link(l,t) == 0
copy l, t
end
else
type = "updated"
copy s, t
end
else
case File.ftype(s)
when "file"
type = "new file"
copy s, t
when "link"
type = "symlink"
File.symlink(File.readlink(s), t)
end
end
end
if Process.uid == 0 && type != "unsupported"
if type == "symlink"
if File.respond_to? 'lchown'
stat = File.lstat(s)
File.lchown(stat.uid, stat.gid, t)
end
else
stat = File.stat(s)
File.chown(stat.uid, stat.gid, t)
end
end
printf "%-10s %s\n", type, s
end
def restore_dir_attributes(dirs)
dirs.each {|dir, stat|
# File.utime(stat.atime, stat.mtime, dir)
win32_dir_utime(stat.atime, stat.mtime, dir)
File.chmod(stat.mode, dir)
}
end
def update_snapshot(src, latest, today)
dirs = {};
Find.find(src) do |s| # path of the source file
r = s.sub "^#{Regexp.quote src}/?", "" # relative path
l = File.join latest, r # path of the latest snapshot
t = File.join today, r # path of the today's snapshot
begin
update_file(s, l, t)
rescue Errno::ENOENT => error
STDERR.puts error.message
next
rescue => error
STDERR.puts error.message
end
if File.ftype(s) == "directory"
dirs[t] = File.stat(s)
end
end
restore_dir_attributes(dirs)
end
# incomplete substitute for cp -rp
def recursive_copy(src, dest)
dirs = {};
Find.find(src) do |s|
r = s.sub "^#{Regexp.quote src}/?", ""
t = File.join dest, r
begin
case File.ftype(s)
when "directory"
File.mkpath t
when "file"
copy s, t
when "link"
File.symlink(File.readlink(s), t)
end
if Process.uid == 0
if File.ftype(s) == "link"
if File.respond_to? 'lchown'
stat = File.lstat(s)
File.lchown(stat.uid, stat.gid, t)
end
else
stat = File.stat(s)
File.chown(stat.uid, stat.gid, t)
end
end
rescue Errno::ENOENT => error
STDERR.puts error.message
next
rescue => error
STDERR.puts error.message
end
if File.ftype(s) == "directory"
dirs[t] = File.stat(s)
end
end
restore_dir_attributes(dirs)
end
def main
src, dest, base = parse_options
base = File.basename(src) unless base
latest = latest_snapshot(src, dest, base)
today = File.join(dest, datedir(Date.today), base)
File.umask(0077)
File.mkpath(today)
if latest
update_snapshot(src, latest, today)
else
recursive_copy(src, today)
end
end
main if __FILE__ == $0
(FATだとハードリンクが作成できないので、無駄が多いがコピーするようにした)
backslash->slashへの変換と、special folderの展開を含めている
ActiveScriptRuby 1.6.8.0 + pdumpfs 0.6(改) + Windows2000SP3(FAT/NTFS)で動作確認している。
#! /usr/local/bin/ruby
#
# pdumpfs 0.6 - a daily backup system similar to Plan9's dumpfs.
#
# DESCRIPTION:
#
# pdumpfs is a simple daily backup system similar to
# Plan9's dumpfs which preserves every daily snapshot.
# You can access the past snapshots at any time for
# retrieving a certain day's file. Let's backup your home
# directory with pdumpfs!
#
# pdumpfs constructs the snapshot YYYY/MM/DD in the
# destination directory. All source files are copied to
# the snapshot directory for the first time. On and after
# the second time, pdumpfs copies only updated or newly
# created files and stores unchanged files as hard links
# to the files of the previous day's snapshot for saving a
# disk space.
#
# USAGE:
#
# % pdumpfs <source directory> <destination directory>
# [<destination basename>]
#
# SAMPLE CRONTAB ENTRY:
#
# 00 05 * * * pdumpfs /home/USER /backup >/dev/null 2>&1
#
# BUGS:
#
# pdumpfs can handle only normal files, directories, and
# symbolic links.
#
#
# $Id: pdumpfs,v 1.37 2002/08/06 08:51:06 satoru Exp $
#
# Copyright (C) 2001 Satoru Takabayashi <satoru@namazu.org>
# All rights reserved.
# This is free software with ABSOLUTELY NO WARRANTY.
#
# You can redistribute it and/or modify it under the terms of
# the GNU General Public License version 2.
#
# win32 ported by Yasuhiro Morioka <yasuhiro.morioka@k5.dion.ne.jp>
# 2003/02/01
require 'find'
require 'date'
require 'ftools'
###########
require 'Win32API'
class Win32API
def Win32API.CreateHardLink( f, e, sa = 0)
createHardLink = Win32API.new("kernel32", "CreateHardLinkA", %w(p p l), 'i')
# ret = createHardLink.Call("/temp/aa.txt", "/temp/senpuu.asm.txt", 0)
# print ret;
return createHardLink.Call( f, e, sa)
end
end
class File
def File.link(l, t)
return Win32API.CreateHardLink(t, l)
end
end
GENERIC_WRITE = 0x40000000
GENERIC_EXECUTE = 0x20000000
GENERIC_ALL = 0x10000000
OPEN_EXISTING = 3
FILE_FLAG_BACKUP_SEMANTICS = 0x02000000
#############
def win32_dir_utime(a, m, dir)
hDir = 0
createFile = Win32API.new("kernel32", "CreateFileA", ['P','L','L','L','L','L','L'], "L")
closeHandle = Win32API.new("kernel32", "CloseHandle", ['L'], 'I')
getLocalTime = Win32API.new("kernel32", "GetLocalTime", %w(P), 'V')
# setLocalTime = Win32API.new("kernel32", "SetLocalTime", %w(P), 'V')
# getLastError = Win32API.new("kernel32", "GetLastError", [], 'L')
systemtimeToFileTime = Win32API.new("kernel32", "SystemTimeToFileTime", ['P', 'P'], 'I')
setFileTime = Win32API.new("kernel32", "SetFileTime", ['L','P','P','P'],"I")
# pdumpfsではlocaltimeで格納されているのだが、
# SetFileTime APIはNTFSの場合、UTCで読み書きするそうなので...
# utime()では、localtimeに移されているのだろう
atime = a
atime.utc
mtime = m
mtime.utc
pSYSTEMTIME_a = ' ' * 2 * 8 # 2byte x 8
pFILETIME_a = ' ' * 2 * 8 # 2byte x 8
getLocalTime.call(pSYSTEMTIME_a)
t1 = pSYSTEMTIME_a.unpack("S8")
t1[0..1] = atime.year, atime.month
t1[3..6] = atime.day, atime.hour, atime.min, atime.sec
systemtimeToFileTime.call(t1.pack("S8"), pFILETIME_a)
pSYSTEMTIME_m = ' ' * 2 * 8 # 2byte x 8
pFILETIME_m = ' ' * 2 * 8 # 2byte x 8
getLocalTime.call(pSYSTEMTIME_m)
t2 = pSYSTEMTIME_m.unpack("S8")
t2[0..1] = mtime.year, mtime.month
t2[3..6] = mtime.day, mtime.hour, mtime.min, mtime.sec
systemtimeToFileTime.call(t2.pack("S8"), pFILETIME_m)
d=dir.dup # なぜか、次のcallでfrozenだと判断されるので
hDir = createFile.Call(d, GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0)
setFileTime.call(hDir, 0, pFILETIME_a, pFILETIME_m)
closeHandle.Call(hDir)
return 0
end
###########
require "win32ole"
def expand_special_folders(dir)
/^@(AllUsersDesktop|AllUsersStartMenu|AllUsersPrograms|AllUsersStartup|Desktop|Favorites|Fonts|MyDocuments|NetHood|PrintHood|Programs|Recent|SendTo|StartMenu|Startup|Templates)/ =~ dir
if $& == nil
val = dir
else
rest = $'
/^@/ =~ $&
shell = WIN32OLE.new("WScript.Shell")
val = shell.SpecialFolders($') + rest
end
return val.tr('\\','/')
end
#########
def usage
puts "Usage: pdumpfs <source directory> <destination directory>"+
" [destination basename]"
exit 1
end
def nodir(dir)
puts "No directory: " + dir
exit 1
end
def same_file? (f1, f2)
File.symlink?(f1) == false && File.symlink?(f2) == false &&
File.file?(f1) && File.file?(f2) &&
File.size(f1) == File.size(f2) && File.mtime(f1) == File.mtime(f2)
end
def parse_options
usage if ARGV[0] == nil || ARGV[1] == nil
#
ARGV[0] = expand_special_folders(ARGV[0])
ARGV[1] = expand_special_folders(ARGV[1])
#
nodir ARGV[0] if File.directory?(ARGV[0]) == false
nodir ARGV[1] if File.directory?(ARGV[1]) == false
return ARGV
end
def datedir(date)
sprintf "%d/%02d/%02d", date.year, date.month, date.day
end
def latest_snapshot(src, dest, base)
for i in 1 .. 31 # allow at most 31 days absence
x = File.join dest, datedir(Date.today - i), base
return x if File.directory?(x)
end
nil
end
# incomplete substitute for cp -p
def copy(src, dest)
stat = File.stat(src)
File.copy src, dest
File.utime(stat.atime, stat.mtime, dest)
File.chmod(stat.mode, dest) # not necessary. just to make sure
end
def update_file(s, l, t)
type = "unsupported"
if File.symlink?(s) == false && File.directory?(s)
type = "directory"
File.mkpath t
else
if File.symlink?(l) == false && File.file?(l)
if same_file?(s, l)
type = "unchanged"
# File.link l, t
if File.link(l,t) == 0
copy l, t
end
else
type = "updated"
copy s, t
end
else
case File.ftype(s)
when "file"
type = "new file"
copy s, t
when "link"
type = "symlink"
File.symlink(File.readlink(s), t)
end
end
end
if Process.uid == 0 && type != "unsupported"
if type == "symlink"
if File.respond_to? 'lchown'
stat = File.lstat(s)
File.lchown(stat.uid, stat.gid, t)
end
else
stat = File.stat(s)
File.chown(stat.uid, stat.gid, t)
end
end
printf "%-10s %s\n", type, s
end
def restore_dir_attributes(dirs)
dirs.each {|dir, stat|
# File.utime(stat.atime, stat.mtime, dir)
win32_dir_utime(stat.atime, stat.mtime, dir)
File.chmod(stat.mode, dir)
}
end
def update_snapshot(src, latest, today)
dirs = {};
Find.find(src) do |s| # path of the source file
r = s.sub "^#{Regexp.quote src}/?", "" # relative path
l = File.join latest, r # path of the latest snapshot
t = File.join today, r # path of the today's snapshot
begin
update_file(s, l, t)
rescue Errno::ENOENT => error
STDERR.puts error.message
next
rescue => error
STDERR.puts error.message
end
if File.ftype(s) == "directory"
dirs[t] = File.stat(s)
end
end
restore_dir_attributes(dirs)
end
# incomplete substitute for cp -rp
def recursive_copy(src, dest)
dirs = {};
Find.find(src) do |s|
r = s.sub "^#{Regexp.quote src}/?", ""
t = File.join dest, r
begin
case File.ftype(s)
when "directory"
File.mkpath t
when "file"
copy s, t
when "link"
File.symlink(File.readlink(s), t)
end
if Process.uid == 0
if File.ftype(s) == "link"
if File.respond_to? 'lchown'
stat = File.lstat(s)
File.lchown(stat.uid, stat.gid, t)
end
else
stat = File.stat(s)
File.chown(stat.uid, stat.gid, t)
end
end
rescue Errno::ENOENT => error
STDERR.puts error.message
next
rescue => error
STDERR.puts error.message
end
if File.ftype(s) == "directory"
dirs[t] = File.stat(s)
end
end
restore_dir_attributes(dirs)
end
def main
src, dest, base = parse_options
base = File.basename(src) unless base
latest = latest_snapshot(src, dest, base)
today = File.join(dest, datedir(Date.today), base)
File.umask(0077)
File.mkpath(today)
if latest
update_snapshot(src, latest, today)
else
recursive_copy(src, today)
end
end
main if __FILE__ == $0
pdumpfs for win32/NTFS More ログイン