4 [ STDIN, STDOUT, STDERR].each
{ |io
| io
.binmode
}
5 $stderr.sync
= $stdout.sync
= true
7 trap('INT') { exit
130 }
8 trap('PIPE') { exit
0 }
10 # this is to be compatible with config files used by the Perl tools
11 def parse_config_file
!(path
, overwrite
= false)
13 File
.open(path
).each_line
do |line
|
15 if /^(domain|class)\s*=\s*(\S+)/.match(line
)
17 elsif m
= /^(?:trackers|hosts)\s*=\s*(.*)/.match(line
)
18 dest
[:hosts] = $1.split(/\s*,\s*/)
19 elsif m
= /^timeout\s*=\s*(.*)/.match(line
)
20 dest
[:timeout] = m
[1].to_f
22 STDERR.puts
"Ignored configuration line: #{line}" unless /^#/.match(line
)
28 # parse the default config file if one exists
29 def_file
= File
.expand_path("~/.mogilefs-client.conf")
30 def_cfg
= File
.exist
?(def_file
) ? parse_config_file
!(def_file
) : {}
32 # parse the command-line first, these options take precedence over all else
38 cat
= { :raw => false }
41 x
.banner
= "Usage: #{$0} [options] <command> [<arguments>]"
44 x
.on('-c', '--config=/path/to/config',
45 'config file to load') { |file
| config_file
= file
}
47 x
.on('-t', '--trackers=host1[,host2]', '--hosts=host1[,host2]', Array
,
48 'hostnames/IP addresses of trackers') do |trackers
|
49 cli_cfg
[:hosts] = trackers
52 x
.on('-e', 'True if key exists') { test
[:e] = true }
53 x
.on('-r', '--raw', 'show raw big_info file information') { cat
[:raw] = true }
55 x
.on('-C', '--class=s', 'class') { |klass
| cli_cfg
[:class] = klass
}
56 x
.on('-d', '--domain=s', 'domain') { |domain
| cli_cfg
[:domain] = domain
}
57 x
.on('-l', "long listing format (`ls' command)") { ls_l
= true }
58 x
.on('-h', '--human-readable',
59 "print sizes in human-readable format (`ls' command)") { ls_h
= true }
62 x
.on('--help', 'Show this help message.') { puts x
; exit
}
66 # parse the config file specified at the command-line
67 file_cfg
= config_file
? parse_config_file
!(config_file
, true) : {}
69 # read environment variables, too. This Ruby API favors the term
70 # "hosts", however upstream MogileFS teminology favors "trackers" instead.
71 # Favor the term more consistent with what the MogileFS inventors used.
73 if ENV["MOG_TRACKERS"]
74 env_cfg
[:hosts] = ENV["MOG_TRACKERS"].split(/\s*,\s*/)
76 if ENV["MOG_HOSTS"] && (env_cfg
[:hosts] || []).empty
?
77 env_cfg
[:hosts] = ENV["MOG_HOSTS"].split(/\s*,\s*/)
79 env_cfg
[:domain] = ENV["MOG_DOMAIN"] if ENV["MOG_DOMAIN"]
80 env_cfg
[:class] = ENV["MOG_CLASS"] if ENV["MOG_CLASS"]
82 # merge the configs, favoring them in order specified:
83 cfg
= {}.merge(def_cfg
).merge(env_cfg
).merge(file_cfg
).merge(cli_cfg
)
87 err
<< "trackers must be specified" if cfg
[:hosts].nil? || cfg
[:hosts].empty
?
88 err
<< "domain must be specified" unless cfg
[:domain]
90 STDERR.puts
"Errors:\n #{err.join("\n ")}"
91 STDERR.puts
ARGV.options
95 unless cmd
= ARGV.shift
96 STDERR.puts
ARGV.options
100 cfg
[:timeout] ||= 30 # longer timeout for interactive use
101 mg
= MogileFS
::MogileFS.new(cfg
)
103 def store_file_retry(mg
, key
, storage_class
, filepath
)
106 mg
.store_file(key
, storage_class
, filepath
)
107 rescue MogileFS
::UnreadableSocketError,
108 MogileFS
::Backend::NoDevicesError => err
109 if ((tries
+= 1) < 10)
110 STDERR.puts
"Retrying on error: #{err}: #{err.message} tries: #{tries}"
113 STDERR.puts
"FATAL: #{err}: #{err.message} tries: #{tries}"
121 %w(K M G
).each
do |s
|
128 sprintf("%.1f%s", size
, suff
)
134 filename
= ARGV.shift
or raise ArgumentError
, '<filename> <key>'
135 key
= ARGV.shift
or raise ArgumentError
, '<filename> <key>'
136 ARGV.shift
and raise ArgumentError
, '<filename> <key>'
137 store_file_retry(mg
, key
, cfg
[:class], filename
)
139 ARGV.empty
? and raise ArgumentError
, '<key1> [<key2> ...]'
141 if (!cat
[:raw] && key
=~
/^_big_info:/)
142 mg
.bigfile_write(key
, STDOUT, {:verify => true})
144 mg
.get_file_data(key
, STDOUT)
148 prefixes
= ARGV.empty
? ? [ nil ] : ARGV
150 each_key
= lambda
do |key
, size
, devcount
|
151 size
= ls_h
&& size
> 1024 ? human_size(size
) : size
.to_s
152 size
= (' ' * (12 - size
.length
)) << size
# right justify
153 puts
[ sprintf("% 2d", devcount
), size
, key
].pack("A4 A16 A*")
156 each_key
= lambda
{ |key
| puts key
}
158 prefixes
.each
{ |prefix
| mg
.each_key(prefix
, &each_key
) }
160 ARGV.empty
? and raise ArgumentError
, '<key1> [<key2>]'
161 ARGV.each
{ |key
| mg
.delete(key
) }
163 from
= ARGV.shift
or raise ArgumentError
, '<from> <to>'
164 to
= ARGV.shift
or raise ArgumentError
, '<from> <to>'
165 ARGV.shift
and raise ArgumentError
, '<from> <to>'
167 when 'stat' # this outputs a RFC822-like format
168 ARGV.empty
? and raise ArgumentError
, '<key1> [<key2>]'
169 ARGV.each_with_index
do |key
, i
|
170 if size
= mg
.size(key
)
173 mg
.get_paths(key
).each_with_index
do |path
,i
|
174 puts
"URL-#{i}: #{path}"
178 STDERR.puts
"No such key: #{key}"
183 key
= ARGV.shift
or raise ArgumentError
, '<key>'
184 ARGV.shift
and raise ArgumentError
, '<key>'
185 cfg
[:class] or raise ArgumentError
, 'E: --class must be specified'
187 tmp
= Tempfile
.new('mog-tee') # TODO: explore Transfer-Encoding:chunked :)
189 # if stdout is pointing to /dev/null, don't bother installing the filter.
191 if File
.stat('/dev/null') != STDOUT.stat
192 tee_obj
= lambda
do |buf
|
194 [ STDOUT, tmp
].each
{ |io
| rv
= io
.write(buf
) }
197 def tee_obj
.write(buf
)
202 MogileFS
::X.copy_stream(STDIN, tee_obj
)
203 store_file_retry(mg
, key
, cfg
[:class], tmp
.path
)
208 truth
, ok
= true, nil
209 raise ArgumentError
, "-e must be specified" unless (test
.size
== 1)
211 truth
, key
= case ARGV.size
216 raise ArgumentError
, "#{ARGV[0]}: binary operator expected"
220 raise ArgumentError
, "Too many arguments"
223 paths
= mg
.get_paths(key
)
225 ok
= !!(paths
&& paths
.size
> 0)
227 raise ArgumentError
, "Unknown flag: -#{test.keys.first}"
233 raise ArgumentError
, "Unknown command: #{cmd}"
235 rescue ArgumentError
=> err
236 STDERR.puts
"Usage: #{$0} #{cmd} #{err.message}"