yugui-chkbuild 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.ja.rd +191 -0
- data/Rakefile +56 -0
- data/VERSION +1 -0
- data/bin/last-build +28 -0
- data/bin/start-build +37 -0
- data/chkbuild.gemspec +107 -0
- data/core_ext/io.rb +17 -0
- data/core_ext/string.rb +10 -0
- data/lib/chkbuild.rb +45 -0
- data/lib/chkbuild/build.rb +718 -0
- data/lib/chkbuild/lock.rb +57 -0
- data/lib/chkbuild/logfile.rb +230 -0
- data/lib/chkbuild/main.rb +138 -0
- data/lib/chkbuild/options.rb +62 -0
- data/lib/chkbuild/scm/cvs.rb +132 -0
- data/lib/chkbuild/scm/git.rb +223 -0
- data/lib/chkbuild/scm/svn.rb +215 -0
- data/lib/chkbuild/scm/xforge.rb +33 -0
- data/lib/chkbuild/target.rb +180 -0
- data/lib/chkbuild/targets/gcc.rb +94 -0
- data/lib/chkbuild/targets/ruby.rb +456 -0
- data/lib/chkbuild/title.rb +107 -0
- data/lib/chkbuild/upload.rb +66 -0
- data/lib/misc/escape.rb +535 -0
- data/lib/misc/gdb.rb +74 -0
- data/lib/misc/timeoutcom.rb +174 -0
- data/lib/misc/udiff.rb +244 -0
- data/lib/misc/util.rb +232 -0
- data/sample/build-autoconf-ruby +69 -0
- data/sample/build-gcc-ruby +43 -0
- data/sample/build-ruby +37 -0
- data/sample/build-ruby2 +36 -0
- data/sample/build-svn +55 -0
- data/sample/build-yarv +35 -0
- data/sample/test-apr +12 -0
- data/sample/test-catcherr +23 -0
- data/sample/test-combfail +21 -0
- data/sample/test-core +14 -0
- data/sample/test-core2 +19 -0
- data/sample/test-date +9 -0
- data/sample/test-dep +17 -0
- data/sample/test-depver +14 -0
- data/sample/test-echo +9 -0
- data/sample/test-env +9 -0
- data/sample/test-error +9 -0
- data/sample/test-fail +18 -0
- data/sample/test-fmesg +16 -0
- data/sample/test-gcc-v +15 -0
- data/sample/test-git +11 -0
- data/sample/test-leave-proc +9 -0
- data/sample/test-limit +9 -0
- data/sample/test-make +9 -0
- data/sample/test-neterr +16 -0
- data/sample/test-savannah +14 -0
- data/sample/test-sleep +9 -0
- data/sample/test-timeout +9 -0
- data/sample/test-timeout2 +10 -0
- data/sample/test-timeout3 +9 -0
- data/sample/test-upload +13 -0
- data/sample/test-warn +13 -0
- data/setup/upload-rsync-ssh +572 -0
- data/test/misc/test-escape.rb +17 -0
- data/test/misc/test-logfile.rb +108 -0
- data/test/misc/test-timeoutcom.rb +23 -0
- data/test/test_helper.rb +9 -0
- metadata +123 -0
data/sample/build-yarv
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Copyright (C) 2006,2009 Tanaka Akira <akr@fsij.org>
|
4
|
+
#
|
5
|
+
# Redistribution and use in source and binary forms, with or without
|
6
|
+
# modification, are permitted provided that the following conditions are met:
|
7
|
+
#
|
8
|
+
# 1. Redistributions of source code must retain the above copyright notice, this
|
9
|
+
# list of conditions and the following disclaimer.
|
10
|
+
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
11
|
+
# this list of conditions and the following disclaimer in the documentation
|
12
|
+
# and/or other materials provided with the distribution.
|
13
|
+
# 3. The name of the author may not be used to endorse or promote products
|
14
|
+
# derived from this software without specific prior written permission.
|
15
|
+
#
|
16
|
+
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
17
|
+
# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
18
|
+
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
19
|
+
# EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
20
|
+
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
|
21
|
+
# OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
22
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
23
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
24
|
+
# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
|
25
|
+
# OF SUCH DAMAGE.
|
26
|
+
|
27
|
+
require 'chkbuild'
|
28
|
+
|
29
|
+
ChkBuild.limit(:data=>1024*1024*800, :as=>1024*1024*800)
|
30
|
+
|
31
|
+
ChkBuild::Ruby.def_target(
|
32
|
+
["yarv"],
|
33
|
+
:separated_srcdir => true)
|
34
|
+
|
35
|
+
ChkBuild.main
|
data/sample/test-apr
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'chkbuild'
|
4
|
+
|
5
|
+
ChkBuild.def_target("catcherr-fails",
|
6
|
+
%w[true false],
|
7
|
+
%w[true false]) {|b, *commands|
|
8
|
+
b.catch_error { b.run(commands[0]) }
|
9
|
+
b.catch_error { b.run(commands[1]) }
|
10
|
+
}
|
11
|
+
|
12
|
+
ChkBuild.def_target("catcherr-branch",
|
13
|
+
%w[true false],
|
14
|
+
%w[true false],
|
15
|
+
%w[true false],
|
16
|
+
%w[true false]) {|b, *commands|
|
17
|
+
r1 = b.catch_error { b.run(commands[0]) }
|
18
|
+
r1 && b.catch_error { b.run(commands[1]) }
|
19
|
+
r1 && b.catch_error { b.run(commands[2]) }
|
20
|
+
r1 && b.catch_error { b.run(commands[3]) }
|
21
|
+
}
|
22
|
+
|
23
|
+
ChkBuild.main
|
@@ -0,0 +1,21 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'chkbuild'
|
4
|
+
|
5
|
+
COMBFAIL1 = ChkBuild.def_target("combfail1",
|
6
|
+
["succ", "fail"]) {|b, suffix|
|
7
|
+
case suffix
|
8
|
+
when "succ" then command = "true"
|
9
|
+
when "fail" then command = "false"
|
10
|
+
end
|
11
|
+
b.run(command)
|
12
|
+
}
|
13
|
+
|
14
|
+
ChkBuild.def_target("combfail2", COMBFAIL1) {|b, cf1|
|
15
|
+
combfail2_dir = b.build_dir
|
16
|
+
/\Acombfail1=/ =~ cf1
|
17
|
+
combfail1_dir = $'
|
18
|
+
b.run(*%W(echo #{combfail1_dir} #{combfail2_dir}))
|
19
|
+
}
|
20
|
+
|
21
|
+
ChkBuild.main
|
data/sample/test-core
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'chkbuild'
|
4
|
+
|
5
|
+
ChkBuild.def_target("coretest") {|b|
|
6
|
+
FileUtils.cp(`which ruby`.chomp, "ruby")
|
7
|
+
b.run("./ruby", "-e", <<'End')
|
8
|
+
Process.setrlimit(Process::RLIMIT_CORE, Process::RLIM_INFINITY, Process::RLIM_INFINITY)
|
9
|
+
trap("SEGV", "DEFAULT")
|
10
|
+
Process.kill("SEGV", $$)
|
11
|
+
End
|
12
|
+
}
|
13
|
+
|
14
|
+
ChkBuild.main
|
data/sample/test-core2
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'chkbuild'
|
4
|
+
|
5
|
+
def coredump(b)
|
6
|
+
b.run("./ruby", "-e", <<'End')
|
7
|
+
Process.setrlimit(Process::RLIMIT_CORE, Process::RLIM_INFINITY, Process::RLIM_INFINITY)
|
8
|
+
trap("SEGV", "DEFAULT")
|
9
|
+
Process.kill("SEGV", $$)
|
10
|
+
End
|
11
|
+
end
|
12
|
+
|
13
|
+
ChkBuild.def_target("core2test") {|b|
|
14
|
+
FileUtils.cp(`which ruby`.chomp, "ruby")
|
15
|
+
b.catch_error { coredump(b) }
|
16
|
+
b.catch_error { coredump(b) }
|
17
|
+
}
|
18
|
+
|
19
|
+
ChkBuild.main
|
data/sample/test-date
ADDED
data/sample/test-dep
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'chkbuild'
|
4
|
+
|
5
|
+
Dep1 = ChkBuild.def_target("dep1",
|
6
|
+
["dev", "stable"]) {|b, suffix|
|
7
|
+
b.catch_error { raise "foo error" }
|
8
|
+
}
|
9
|
+
|
10
|
+
ChkBuild.def_target("dep2",
|
11
|
+
["dev", "stable"],
|
12
|
+
Dep1) {
|
13
|
+
|b, suffix, dep1|
|
14
|
+
p b.suffixed_name
|
15
|
+
}
|
16
|
+
|
17
|
+
ChkBuild.main
|
data/sample/test-depver
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'chkbuild'
|
4
|
+
|
5
|
+
Dep1 = ChkBuild.def_target("lib1", ["dev", "stable"]) {|b|
|
6
|
+
}
|
7
|
+
|
8
|
+
Dep2 = ChkBuild.def_target("lib2", ["dev", "stable"]) {|b|
|
9
|
+
}
|
10
|
+
|
11
|
+
ChkBuild.def_target("app", ["dev", "stable"], Dep1, Dep2) {|b, suffix, lib1, lib2|
|
12
|
+
}
|
13
|
+
|
14
|
+
ChkBuild.main
|
data/sample/test-echo
ADDED
data/sample/test-env
ADDED
data/sample/test-error
ADDED
data/sample/test-fail
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'chkbuild'
|
4
|
+
|
5
|
+
t = ChkBuild.def_target("fail") {|b|
|
6
|
+
b.run("echo", "[bug]")
|
7
|
+
b.run("false")
|
8
|
+
}
|
9
|
+
|
10
|
+
t.add_title_hook(nil) {|title, log|
|
11
|
+
mark = ''
|
12
|
+
mark << "[BUG]" if /\[BUG\]/i =~ log
|
13
|
+
mark << "[SEGV]" if /Segmentation fault/i =~ log
|
14
|
+
mark << "[FATAL]" if /\[FATAL\]/i =~ log
|
15
|
+
title.update_title(:mark, mark)
|
16
|
+
}
|
17
|
+
|
18
|
+
ChkBuild.main
|
data/sample/test-fmesg
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'chkbuild'
|
4
|
+
|
5
|
+
t = ChkBuild.def_target("fmesg") {|b|
|
6
|
+
b.run("echo", "this fails")
|
7
|
+
if /this fails/ =~ b.logfile.get_section('echo')
|
8
|
+
raise ChkBuild::Build::CommandError.new(0, "echo")
|
9
|
+
end
|
10
|
+
}
|
11
|
+
|
12
|
+
t.add_failure_hook('echo') {|log|
|
13
|
+
'FAIL'
|
14
|
+
}
|
15
|
+
|
16
|
+
ChkBuild.main
|
data/sample/test-gcc-v
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'chkbuild'
|
4
|
+
|
5
|
+
gcc_v = ChkBuild.def_target("gcc-v") {|b|
|
6
|
+
b.run("gcc", '-v', :section=>'version')
|
7
|
+
}
|
8
|
+
|
9
|
+
gcc_v.add_title_hook('version') {|title, log|
|
10
|
+
if /^gcc version (.*)$/ =~ log
|
11
|
+
title.update_title(:gcc_version, "(gcc #{$1})")
|
12
|
+
end
|
13
|
+
}
|
14
|
+
|
15
|
+
ChkBuild.main
|
data/sample/test-git
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'chkbuild'
|
4
|
+
|
5
|
+
ChkBuild.def_target("git") {|b,|
|
6
|
+
opts = {:shared_gitdir=>ChkBuild.build_top}
|
7
|
+
#b.git("git://github.com/brixen/rubyspec.git", 'tst-rubyspec', opts)
|
8
|
+
b.github("brixen", "rubyspec", "tst-rubyspec2", opts)
|
9
|
+
}
|
10
|
+
|
11
|
+
ChkBuild.main
|
data/sample/test-limit
ADDED
data/sample/test-make
ADDED
data/sample/test-neterr
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'chkbuild'
|
4
|
+
|
5
|
+
t = ChkBuild.def_target("savannah") {|b|
|
6
|
+
b.gnu_savannah_cvs('config', 'config', nil)
|
7
|
+
b.run("config/config.guess")
|
8
|
+
}
|
9
|
+
|
10
|
+
t.add_title_hook('config.guess') {|title, log|
|
11
|
+
title.update_title(:status, log.chomp[/[^\n]*\z/])
|
12
|
+
}
|
13
|
+
|
14
|
+
ChkBuild.main
|
data/sample/test-sleep
ADDED
data/sample/test-timeout
ADDED
data/sample/test-upload
ADDED
data/sample/test-warn
ADDED
@@ -0,0 +1,572 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Copyright (C) 2005 Tanaka Akira <akr@fsij.org>
|
4
|
+
#
|
5
|
+
# Redistribution and use in source and binary forms, with or without
|
6
|
+
# modification, are permitted provided that the following conditions are met:
|
7
|
+
#
|
8
|
+
# 1. Redistributions of source code must retain the above copyright notice, this
|
9
|
+
# list of conditions and the following disclaimer.
|
10
|
+
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
11
|
+
# this list of conditions and the following disclaimer in the documentation
|
12
|
+
# and/or other materials provided with the distribution.
|
13
|
+
# 3. The name of the author may not be used to endorse or promote products
|
14
|
+
# derived from this software without specific prior written permission.
|
15
|
+
#
|
16
|
+
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
17
|
+
# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
18
|
+
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
19
|
+
# EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
20
|
+
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
|
21
|
+
# OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
22
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
23
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
24
|
+
# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
|
25
|
+
# OF SUCH DAMAGE.
|
26
|
+
|
27
|
+
# usage: setup/upload-rsync-ssh [remoteuser@]remotehost [remote-dir [chkbuild-user]]
|
28
|
+
#
|
29
|
+
# setup/upload-rsync-ssh setups upload configuration.
|
30
|
+
# 1. generate ssh key dedicated for the upload.
|
31
|
+
# localhost:~/.ssh/chkbuild-localhost-remotehost
|
32
|
+
# localhost:~/.ssh/chkbuild-localhost-remotehost.pub
|
33
|
+
# 2. ssh client setup
|
34
|
+
# localhost:~chkbuild-user/.ssh/chkbuild-localhost-remotehost
|
35
|
+
# localhost:~chkbuild-user/.ssh/known_hosts
|
36
|
+
# 2. setup rsync server via ssh on remotehost.
|
37
|
+
# remotehost:~remoteuser/.ssh/chkbuild-rsyncd-localhost-remotehost.conf
|
38
|
+
# remotehost:~remoteuser/.ssh/authorized_keys
|
39
|
+
# remotehost:~remoteuser/public_html/chkbuild/localhost/
|
40
|
+
|
41
|
+
# You need an user dedicated to chkbuild before using setup/upload-rsync-ssh.
|
42
|
+
# Assume the login name of the dedicated user is chkbuild and
|
43
|
+
# your login name is luser.
|
44
|
+
#
|
45
|
+
# /home/chkbuild user=luser group=chkbuild mode=2750
|
46
|
+
# /home/chkbuild/build user=luser group=chkbuild mode=2775
|
47
|
+
# /home/chkbuild/public_html user=luser group=chkbuild mode=2775
|
48
|
+
#
|
49
|
+
# # adduser --disabled-login --no-create-home --shell /home/luser/chkbuild/start-build chkbuild
|
50
|
+
# # usermod -G ...,chkbuild luser
|
51
|
+
# # cd /home
|
52
|
+
# # mkdir chkbuild chkbuild/build chkbuild/public_html
|
53
|
+
# # chown luser chkbuild
|
54
|
+
# # chown chkbuild:luser chkbuild/build chkbuild/public_html
|
55
|
+
# # chmod 2775 chkbuild/build chkbuild/public_html
|
56
|
+
# # setup/upload-rsync-ssh chkbuild remote-user@remotehost
|
57
|
+
# ...
|
58
|
+
# rsync target: remote-user@remotehost::upload
|
59
|
+
#
|
60
|
+
# Note that setup/upload-rsync-ssh invokes ssh twice.
|
61
|
+
|
62
|
+
require 'etc'
|
63
|
+
require 'socket'
|
64
|
+
require 'fileutils'
|
65
|
+
|
66
|
+
local_host = Socket.gethostname
|
67
|
+
DEFAULT_CHKBUILD_UESR = 'chkbuild'
|
68
|
+
DEFAULT_REMOTE_DIR = "public_html/chkbuild/#{local_host}"
|
69
|
+
|
70
|
+
def usage(status)
|
71
|
+
puts <<"End"
|
72
|
+
usage: setup/upload-rsync-ssh [remoteuser@]remotehost [remote-dir [chkbuild-user]]
|
73
|
+
default:
|
74
|
+
remote-dir = #{DEFAULT_REMOTE_DIR.inspect}
|
75
|
+
chkbuild-user = #{DEFAULT_CHKBUILD_UESR.inspect}
|
76
|
+
End
|
77
|
+
exit status
|
78
|
+
end
|
79
|
+
|
80
|
+
if ARGV.length < 1 || 3 < ARGV.length
|
81
|
+
puts "setup/upload-rsync-ssh needs exactly 1 to 3 arguments"
|
82
|
+
usage(1)
|
83
|
+
end
|
84
|
+
|
85
|
+
remote_info, remote_dir, chkbuild_user = ARGV
|
86
|
+
remote_dir ||= DEFAULT_REMOTE_DIR
|
87
|
+
chkbuild_user ||= DEFAULT_CHKBUILD_UESR
|
88
|
+
|
89
|
+
local_user = Etc.getpwuid
|
90
|
+
|
91
|
+
if /@/ =~ remote_info
|
92
|
+
remote_user = $`
|
93
|
+
remote_host = $'
|
94
|
+
else
|
95
|
+
remote_user = local_user.name
|
96
|
+
remote_host = remote_info
|
97
|
+
end
|
98
|
+
|
99
|
+
begin
|
100
|
+
chkbuild_user = Etc.getpwnam(chkbuild_user)
|
101
|
+
rescue ArgumentError
|
102
|
+
puts "no such user: #{chkbuild_user.inspect}"
|
103
|
+
exit 1
|
104
|
+
end
|
105
|
+
|
106
|
+
begin
|
107
|
+
chkbuild_group = Etc.getgrgid(chkbuild_user.gid)
|
108
|
+
rescue ArgumentError
|
109
|
+
puts "no group which gid is #{chkbuild_user.gid}"
|
110
|
+
exit 1
|
111
|
+
end
|
112
|
+
|
113
|
+
if !chkbuild_group.mem.include?(local_user.name)
|
114
|
+
puts "#{local_user.name.inspect} is not a member of group #{chkbuild_group.name.inspect}"
|
115
|
+
exit 1
|
116
|
+
end
|
117
|
+
|
118
|
+
if !Process.groups.include?(chkbuild_user.gid)
|
119
|
+
puts "process supplementary groups doesn't contain #{chkbuild_group.name.inspect}"
|
120
|
+
exit 1
|
121
|
+
end
|
122
|
+
|
123
|
+
if local_user.uid == chkbuild_user.uid
|
124
|
+
puts "same uid #{local_user.name.inspect} and #{chkbuild_user.name}: #{local_user.uid}"
|
125
|
+
exit 1
|
126
|
+
end
|
127
|
+
|
128
|
+
# xxx: gid check?
|
129
|
+
|
130
|
+
class FileMap
|
131
|
+
def initialize(filemap)
|
132
|
+
@paths = []
|
133
|
+
@filemap = {}
|
134
|
+
filemap.each_line {|line|
|
135
|
+
next if /\A\s*\z/ =~ line
|
136
|
+
path, *spec_list = line.split(/\s+/)
|
137
|
+
if %r{/\z} =~ path
|
138
|
+
spec_list << "dir"
|
139
|
+
path.sub!(%r{/\z}, '')
|
140
|
+
end
|
141
|
+
specs = {}
|
142
|
+
spec_list.each {|spec|
|
143
|
+
if /=/ =~ spec
|
144
|
+
key = $`
|
145
|
+
val = $'
|
146
|
+
case key
|
147
|
+
when /\Auser\z/
|
148
|
+
val = val.to_i
|
149
|
+
when /\Agroup\z/
|
150
|
+
val = val.to_i
|
151
|
+
when /\Amode\z/
|
152
|
+
mode, mask = val.split(%r{/})
|
153
|
+
val = [mode.oct, mask.oct]
|
154
|
+
end
|
155
|
+
specs[key] = val
|
156
|
+
else
|
157
|
+
specs[spec] = true
|
158
|
+
end
|
159
|
+
}
|
160
|
+
if @filemap[path]
|
161
|
+
raise ArgumentError, "duplicate path: #{path.inspect}"
|
162
|
+
end
|
163
|
+
@paths << path
|
164
|
+
@filemap[path] = specs
|
165
|
+
}
|
166
|
+
@filemap.each {|path, specs|
|
167
|
+
dir = File.dirname(path)
|
168
|
+
while @filemap.include? dir
|
169
|
+
@filemap[dir]['dir'] = true
|
170
|
+
dir = File.dirname(dir)
|
171
|
+
end
|
172
|
+
}
|
173
|
+
end
|
174
|
+
|
175
|
+
def creatable_target?(specs, parent_specs, parent)
|
176
|
+
parent_uid = nil
|
177
|
+
parent_gid = nil
|
178
|
+
parent_mode = nil
|
179
|
+
parent_mask = nil
|
180
|
+
if parent_specs
|
181
|
+
parent_specs.each {|key, val|
|
182
|
+
case key
|
183
|
+
when /\Auser\z/ then parent_uid = val
|
184
|
+
when /\Agroup\z/ then parent_gid = val
|
185
|
+
when /\Amode\z/ then parent_mode, parent_mask = val
|
186
|
+
when /\Afile\z/ then return false
|
187
|
+
end
|
188
|
+
}
|
189
|
+
else
|
190
|
+
begin
|
191
|
+
parent_stat = File.stat(parent)
|
192
|
+
return false unless parent_stat.directory?
|
193
|
+
parent_uid = parent_stat.uid
|
194
|
+
parent_gid = parent_stat.gid
|
195
|
+
parent_mode = parent_stat.mode
|
196
|
+
parent_mask = 07777
|
197
|
+
rescue Errno::ENOENT
|
198
|
+
end
|
199
|
+
end
|
200
|
+
if parent_uid && Process.uid == parent_uid
|
201
|
+
unless parent_mask & 0300 == 0300 && parent_mode & 0300 == 0300
|
202
|
+
return false
|
203
|
+
end
|
204
|
+
elsif parent_uid && parent_gid && (Process.gid == parent_gid || Process.groups.include?(parent_gid))
|
205
|
+
unless parent_mask & 0030 == 0030 && parent_mode & 0030 == 0030
|
206
|
+
return false
|
207
|
+
end
|
208
|
+
elsif parent_uid && parent_gid
|
209
|
+
unless parent_mask & 0003 == 0003 && parent_mode & 0003 == 0003
|
210
|
+
return false
|
211
|
+
end
|
212
|
+
else
|
213
|
+
return false
|
214
|
+
end
|
215
|
+
specs.each {|key, val|
|
216
|
+
case key
|
217
|
+
when /\Auser\z/
|
218
|
+
return false if Process.uid != val
|
219
|
+
when /\Agroup\z/
|
220
|
+
unless Process.gid == val ||
|
221
|
+
Process.groups.include?(val) ||
|
222
|
+
(parent_mask && parent_gid && parent_mask & 02000 != 0 && parent_mode & 02000 != 0 && parent_gid == val)
|
223
|
+
return false
|
224
|
+
end
|
225
|
+
end
|
226
|
+
}
|
227
|
+
return true
|
228
|
+
end
|
229
|
+
|
230
|
+
def check
|
231
|
+
stats = {}
|
232
|
+
errors = {}
|
233
|
+
fixable = {}
|
234
|
+
@paths.each {|path|
|
235
|
+
specs = @filemap[path]
|
236
|
+
errors[path] ||= []
|
237
|
+
fixable[path] ||= []
|
238
|
+
begin
|
239
|
+
stat = File.stat(path)
|
240
|
+
rescue Errno::ENOENT
|
241
|
+
parent = File.dirname(path)
|
242
|
+
if creatable_target?(specs, @filemap[parent], parent)
|
243
|
+
fixable[path] = specs
|
244
|
+
else
|
245
|
+
errors[path] << 'not-exist'
|
246
|
+
end
|
247
|
+
end
|
248
|
+
if stat
|
249
|
+
stats[path] = stat
|
250
|
+
specs.each {|key, val|
|
251
|
+
case key
|
252
|
+
when /\Auser\z/
|
253
|
+
errors[path] << [key, val, stat] if stat.uid != val
|
254
|
+
when /\Agroup\z/
|
255
|
+
if stat.gid != val
|
256
|
+
if specs['user'] == Process.uid && Process.groups.include?(val)
|
257
|
+
fixable[path] << [key, val, stat]
|
258
|
+
else
|
259
|
+
errors[path] << [key, val, stat]
|
260
|
+
end
|
261
|
+
end
|
262
|
+
when /\Amode\z/
|
263
|
+
mode, mask = val
|
264
|
+
if stat.mode & mask != mode
|
265
|
+
if specs['user'] == Process.uid
|
266
|
+
fixable[path] << [key, val, stat]
|
267
|
+
else
|
268
|
+
errors[path] << [key, val, stat]
|
269
|
+
end
|
270
|
+
end
|
271
|
+
when /\Afile\z/
|
272
|
+
errors[path] << [key, val, stat] if !stat.file?
|
273
|
+
when /\Adir\z/
|
274
|
+
errors[path] << [key, val, stat] if !stat.directory?
|
275
|
+
else
|
276
|
+
raise "unexpected file spec: #{key.inspect} (#{path.inspect})"
|
277
|
+
end
|
278
|
+
}
|
279
|
+
end
|
280
|
+
errors.delete(path) if errors[path].empty?
|
281
|
+
fixable.delete(path) if fixable[path].empty?
|
282
|
+
}
|
283
|
+
if errors.empty?
|
284
|
+
@stats = stats
|
285
|
+
@fixable = fixable
|
286
|
+
true
|
287
|
+
else
|
288
|
+
@errors = errors
|
289
|
+
false
|
290
|
+
end
|
291
|
+
end
|
292
|
+
attr_reader :stats
|
293
|
+
|
294
|
+
def inspect_uid(uid)
|
295
|
+
begin
|
296
|
+
info = Etc.getpwuid(uid)
|
297
|
+
rescue ArgumentError
|
298
|
+
uid.to_s
|
299
|
+
end
|
300
|
+
"#{uid} (#{info.name})"
|
301
|
+
end
|
302
|
+
|
303
|
+
def inspect_gid(gid)
|
304
|
+
begin
|
305
|
+
info = Etc.getgrgid(gid)
|
306
|
+
rescue ArgumentError
|
307
|
+
gid.to_s
|
308
|
+
end
|
309
|
+
"#{gid} (#{info.name})"
|
310
|
+
end
|
311
|
+
|
312
|
+
def print_errors
|
313
|
+
@errors.keys.sort.each {|path|
|
314
|
+
@errors[path].each {|errinfo|
|
315
|
+
key, val, *rest = errinfo
|
316
|
+
case key
|
317
|
+
when /\Auser\z/
|
318
|
+
stat, = rest
|
319
|
+
puts "#{path.inspect}: uid expected #{inspect_uid val} but #{inspect_uid stat.uid}"
|
320
|
+
when /\Agroup\z/
|
321
|
+
stat, = rest
|
322
|
+
puts "#{path.inspect}: gid expected #{inspect_gid val} but #{inspect_gid stat.gid}"
|
323
|
+
when %r{\Amode\z}
|
324
|
+
mode, mask = val
|
325
|
+
stat, = rest
|
326
|
+
puts "#{path.inspect}: mode expected #{"%o" % mode} but #{"%o" % (stat.mode & mask)} (mask=#{"%o" % mask})"
|
327
|
+
when /\Afile\z/
|
328
|
+
stat, = rest
|
329
|
+
puts "#{path.inspect}: regular file expected but #{stat.ftype}"
|
330
|
+
when /\Adir\z/
|
331
|
+
stat, = rest
|
332
|
+
puts "#{path.inspect}: directory expected but #{stat.ftype}"
|
333
|
+
when /\Anot-exist\z/
|
334
|
+
puts "#{path.inspect}: not exist"
|
335
|
+
else
|
336
|
+
raise "unexpected file spec: #{key.inspect} (#{path.inspect})"
|
337
|
+
end
|
338
|
+
}
|
339
|
+
}
|
340
|
+
end
|
341
|
+
|
342
|
+
def fix_permission(path, uid, gid, mode, mask, stat=nil)
|
343
|
+
if !stat
|
344
|
+
begin
|
345
|
+
stat = File.stat(path)
|
346
|
+
rescue Errno::ENOENT
|
347
|
+
puts "#{path.inspect}: not exist."
|
348
|
+
exit 1
|
349
|
+
end
|
350
|
+
end
|
351
|
+
if uid && stat.uid != uid
|
352
|
+
puts "#{path.inspect}: uid expected #{inspect_uid uid} but #{inspect_uid stat.uid}"
|
353
|
+
exit 1
|
354
|
+
end
|
355
|
+
if gid && stat.gid != gid
|
356
|
+
File.chown(-1, gid, path)
|
357
|
+
end
|
358
|
+
if mode && stat.mode & mask != mode
|
359
|
+
File.chmod(stat.mode & ~mask | mode, path)
|
360
|
+
end
|
361
|
+
stat = File.stat(path)
|
362
|
+
if gid && stat.gid != gid
|
363
|
+
puts "#{path.inspect}: gid expected #{inspect_uid gid} but #{inspect_gid stat.gid}"
|
364
|
+
exit 1
|
365
|
+
end
|
366
|
+
if mode && stat.mode & mask != mode
|
367
|
+
puts "#{path.inspect}: mode expected #{"%o" % gid} but #{"%o" % stat.gid}"
|
368
|
+
exit 1
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
def fix_directories
|
373
|
+
dirs = @paths.reject {|path| !@filemap[path]['dir'] }
|
374
|
+
dirs.each {|dir|
|
375
|
+
uid = @filemap[dir]['user']
|
376
|
+
gid = @filemap[dir]['group']
|
377
|
+
mode, mask = @filemap[dir]['mode']
|
378
|
+
stat = @stats[dir]
|
379
|
+
if !stat
|
380
|
+
Dir.mkdir(dir, mode)
|
381
|
+
stat = File.stat(dir)
|
382
|
+
end
|
383
|
+
fix_permission(dir, uid, gid, mode, mask, stat)
|
384
|
+
}
|
385
|
+
end
|
386
|
+
|
387
|
+
def fix_files
|
388
|
+
errors = 0
|
389
|
+
paths = @paths.reject {|path| @filemap[path]['dir'] }
|
390
|
+
paths.each {|path|
|
391
|
+
uid = @filemap[path]['user']
|
392
|
+
gid = @filemap[path]['group']
|
393
|
+
mode, mask = @filemap[path]['mode']
|
394
|
+
begin
|
395
|
+
stat = File.stat(path)
|
396
|
+
rescue Errno::ENOENT
|
397
|
+
puts "#{path.inspect}: not generated."
|
398
|
+
exit 1
|
399
|
+
end
|
400
|
+
fix_permission(path, uid, gid, mode, mask, stat)
|
401
|
+
}
|
402
|
+
end
|
403
|
+
|
404
|
+
end
|
405
|
+
|
406
|
+
cdir = chkbuild_user.dir
|
407
|
+
cuid = chkbuild_user.uid
|
408
|
+
cgid = chkbuild_user.gid
|
409
|
+
ldir = local_user.dir
|
410
|
+
luid = local_user.uid
|
411
|
+
lgid = local_user.gid
|
412
|
+
|
413
|
+
if cdir == ldir
|
414
|
+
puts "same home directory for #{local_user.name.inspect} and #{chkbuild_user.name.inspect}: #{cdir.inspect}"
|
415
|
+
exit 1
|
416
|
+
end
|
417
|
+
|
418
|
+
host_pair = "#{local_host}-#{remote_host}"
|
419
|
+
filemap = FileMap.new(<<"End")
|
420
|
+
#{cdir} user=#{luid} group=#{cgid} mode=2750/7777
|
421
|
+
#{cdir}/.ssh user=#{luid} group=#{cgid} mode=2750/7777
|
422
|
+
#{cdir}/.ssh/chkbuild-#{host_pair} user=#{luid} group=#{cgid} mode=640/777 file
|
423
|
+
#{cdir}/.ssh/known_hosts user=#{luid} group=#{cgid} mode=640/777 file
|
424
|
+
#{cdir}/build/ user=#{luid} group=#{cgid} mode=2775/7777
|
425
|
+
#{cdir}/public_html/ user=#{luid} group=#{cgid} mode=2775/7777
|
426
|
+
|
427
|
+
#{ldir}/.ssh/chkbuild-#{host_pair} user=#{luid} group=#{lgid} mode=400/777 file
|
428
|
+
#{ldir}/.ssh/chkbuild-#{host_pair}.pub user=#{luid} group=#{lgid} mode=444/777 file
|
429
|
+
End
|
430
|
+
|
431
|
+
unless filemap.check
|
432
|
+
filemap.print_errors
|
433
|
+
exit 1
|
434
|
+
end
|
435
|
+
|
436
|
+
stats = filemap.stats
|
437
|
+
|
438
|
+
local_secret_key = "#{ldir}/.ssh/chkbuild-#{host_pair}"
|
439
|
+
local_public_key = "#{ldir}/.ssh/chkbuild-#{host_pair}.pub"
|
440
|
+
if stats[local_secret_key] && !stats[local_public_key]
|
441
|
+
puts "#{local_public_key.inspect}: local ssh secret key exists but no public key"
|
442
|
+
exit 1
|
443
|
+
end
|
444
|
+
if !stats[local_secret_key] && stats[local_public_key]
|
445
|
+
puts "#{local_secret_key.inspect}: local ssh public key exists but no secret key"
|
446
|
+
exit 1
|
447
|
+
end
|
448
|
+
|
449
|
+
chkbuild_secret_key = "#{cdir}/.ssh/chkbuild-#{host_pair}"
|
450
|
+
if stats[chkbuild_secret_key]
|
451
|
+
if !stats[local_secret_key]
|
452
|
+
puts "#{chkbuild_secret_key.inspect} exists but not #{local_secret_key.inspect}"
|
453
|
+
exit 1
|
454
|
+
end
|
455
|
+
if !FileUtils.compare_file(chkbuild_secret_key, local_secret_key)
|
456
|
+
puts "different content: #{chkbuild_secret_key.inspect} and #{local_secret_key.inspect}"
|
457
|
+
exit 1
|
458
|
+
end
|
459
|
+
end
|
460
|
+
|
461
|
+
puts "trying to check rsync & ssh #{remote_user}@#{remote_host} possible..."
|
462
|
+
output = `ssh #{remote_user}@#{remote_host} rsync --version`
|
463
|
+
if $?.to_i != 0
|
464
|
+
puts "ssh/rsync failed."
|
465
|
+
exit 1
|
466
|
+
end
|
467
|
+
if /rsync\s+version\s+([\d.]+)/ !~ output
|
468
|
+
puts "unexpected ssh/rsync output:"
|
469
|
+
puts "--------------------------------"
|
470
|
+
puts output
|
471
|
+
puts "--------------------------------"
|
472
|
+
exit 1
|
473
|
+
end
|
474
|
+
rsync_version = $1
|
475
|
+
if (rsync_version.split(/\./).map {|n| n.to_i } <=> [2,6,3]) < 0
|
476
|
+
puts "rsync in #{remote_host.inspect} is too old: #{rsync_version} (must be newer than or equal 2.6.3)"
|
477
|
+
exit 1
|
478
|
+
end
|
479
|
+
|
480
|
+
######################################################################
|
481
|
+
|
482
|
+
filemap.fix_directories
|
483
|
+
|
484
|
+
if !stats[local_secret_key]
|
485
|
+
system "ssh-keygen", "-t", "dsa", "-N", "", "-f", local_secret_key, "-C", "chkbuild@#{local_host}@#{remote_host}"
|
486
|
+
exit 1 if $?.to_i != 0
|
487
|
+
end
|
488
|
+
begin
|
489
|
+
File.unlink chkbuild_secret_key
|
490
|
+
rescue Errno::ENOENT
|
491
|
+
end
|
492
|
+
FileUtils.copy_file(local_secret_key, chkbuild_secret_key)
|
493
|
+
|
494
|
+
chkbuild_known_hosts = "#{cdir}/.ssh/known_hosts"
|
495
|
+
local_known_hosts = "#{ldir}/.ssh/known_hosts"
|
496
|
+
known_hosts_line = nil
|
497
|
+
if File.exist? local_known_hosts
|
498
|
+
IO.foreach(local_known_hosts) {|line|
|
499
|
+
next if /\A#/ =~ line || /\A\s*\z/ =~ line
|
500
|
+
if /\A\S+/ =~ line
|
501
|
+
rest = $'
|
502
|
+
hostnames = $&.split(/,/)
|
503
|
+
if hostnames.include? remote_host
|
504
|
+
addrs = [remote_host]
|
505
|
+
Socket.getaddrinfo(remote_host, "ssh").each {|addrfamily, port, host, addr|
|
506
|
+
if addrfamily == 'AF_INET'
|
507
|
+
addrs << addr
|
508
|
+
end
|
509
|
+
}
|
510
|
+
addrs.uniq!
|
511
|
+
known_hosts_line = "#{addrs.join(',')}#{rest}"
|
512
|
+
break
|
513
|
+
end
|
514
|
+
end
|
515
|
+
}
|
516
|
+
end
|
517
|
+
open(chkbuild_known_hosts, File::RDWR|File::CREAT) {|f|
|
518
|
+
if known_hosts_line
|
519
|
+
f.each_line {|line|
|
520
|
+
if line == known_hosts_line
|
521
|
+
known_hosts_line = nil
|
522
|
+
end
|
523
|
+
}
|
524
|
+
f.puts known_hosts_line if known_hosts_line
|
525
|
+
end
|
526
|
+
}
|
527
|
+
|
528
|
+
filemap.fix_files
|
529
|
+
|
530
|
+
public_key = File.read(local_public_key).chomp
|
531
|
+
|
532
|
+
puts "setup remote files using ssh #{remote_user}@#{remote_host}..."
|
533
|
+
IO.popen("ssh #{remote_user}@#{remote_host} sh -s", "w") {|f|
|
534
|
+
commands = <<'End'
|
535
|
+
[ -d .ssh ] || (
|
536
|
+
mkdir .ssh
|
537
|
+
chmod 700 .ssh
|
538
|
+
)
|
539
|
+
End
|
540
|
+
d = []
|
541
|
+
remote_dir.scan(%r{[^/]+}) {|n|
|
542
|
+
next if /\A\.\.?\z/ =~ n
|
543
|
+
d << n
|
544
|
+
dir = d.join('/')
|
545
|
+
commands << <<"End"
|
546
|
+
[ -d #{dir} ] || (
|
547
|
+
mkdir #{dir}
|
548
|
+
chmod 755 #{dir}
|
549
|
+
)
|
550
|
+
End
|
551
|
+
}
|
552
|
+
dir = d.join('/')
|
553
|
+
commands << <<"End"
|
554
|
+
cat <<E > .ssh/chkbuild-rsyncd-#{host_pair}.conf
|
555
|
+
[upload]
|
556
|
+
path = $HOME/#{dir}
|
557
|
+
use chroot = no
|
558
|
+
read only = no
|
559
|
+
write only = yes
|
560
|
+
E
|
561
|
+
[ -f .ssh/authorized_keys ] || touch .ssh/authorized_keys
|
562
|
+
cp .ssh/authorized_keys .ssh/authorized_keys.bak &&
|
563
|
+
cat - .ssh/authorized_keys <<E | sort | uniq > .ssh/authorized_keys.new &&
|
564
|
+
command="`which rsync` --server --daemon --config=$HOME/.ssh/chkbuild-rsyncd-#{host_pair}.conf .",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty #{public_key}
|
565
|
+
E
|
566
|
+
mv .ssh/authorized_keys.new .ssh/authorized_keys
|
567
|
+
End
|
568
|
+
f << commands
|
569
|
+
}
|
570
|
+
|
571
|
+
puts "rsync target: #{remote_user}@#{remote_host}::upload"
|
572
|
+
|