morioka (6528) の日記

2003 年 02 月 01 日
午後 11:35

pdumpfs for win32/NTFS

高林さんの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
この議論は賞味期限が切れたので、アーカイブ化されています。 新たにコメントを付けることはできません。

未知のハックに一心不乱に取り組んだ結果、私は自然の法則を変えてしまった -- あるハッカー

処理中...