#! /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
/^@(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
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 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