-
Notifications
You must be signed in to change notification settings - Fork 87
/
file.rb
211 lines (173 loc) · 5.66 KB
/
file.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
#
# author: Christoph Hartmann
# author: Dominik Richter
require_relative "file/local"
require_relative "file/remote"
require_relative "extras/stat"
module Train
class File # rubocop:disable Metrics/ClassLength
def initialize(backend, path, follow_symlink = true)
@backend = backend
@path = path || ""
@follow_symlink = follow_symlink
sanitize_filename(path)
end
# This method gets override by particular os class.
def sanitize_filename(_path)
nil
end
# interface methods: these fields should be implemented by every
# backend File
DATA_FIELDS = %w{
exist? mode owner group uid gid content mtime size selinux_label path
}.freeze
DATA_FIELDS.each do |m|
next if m == "path"
define_method m do
raise NotImplementedError, "File must implement the #{m}() method."
end
end
def to_json
res = Hash[DATA_FIELDS.map { |x| [x, method(x).call] }]
# additional fields provided as input
res["type"] = type
res["follow_symlink"] = @follow_symlink
res
end
def type
:unknown
end
def source
if @follow_symlink
self.class.new(@backend, @path, false)
else
self
end
end
def source_path
@path
end
# product_version is primarily used by Windows operating systems only and will be overwritten
# in Windows-related classes. Since this field is returned for all file objects, the acceptable
# default value is nil
def product_version
nil
end
# file_version is primarily used by Windows operating systems only and will be overwritten
# in Windows-related classes. Since this field is returned for all file objects, the acceptable
# default value is nil
def file_version
nil
end
def version?(version)
(product_version == version) ||
(file_version == version)
end
def block_device?
type.to_s == "block_device"
end
def character_device?
type.to_s == "character_device"
end
def pipe?
type.to_s == "pipe"
end
def file?
type.to_s == "file"
end
def socket?
type.to_s == "socket"
end
def directory?
type.to_s == "directory"
end
def symlink?
source.type.to_s == "symlink"
end
def owned_by?(sth)
owner == sth
end
def path
if symlink? && @follow_symlink
link_path
else
@path
end
end
# if the OS-specific file class supports inquirying as to whether the
# file/device is mounted, the #mounted method should return a command
# object whose stdout will not be nil if indeed the device is mounted.
#
# if the OS-specific file class does not support checking for mount
# status, the method should not be implemented and this method will
# return false.
def mounted?
return false unless respond_to?(:mounted)
!mounted.nil? && !mounted.stdout.nil? && !mounted.stdout.empty?
end
def md5sum
# Skip processing rest of method if fallback method is selected
return perform_checksum_ruby(:md5) if defined?(@ruby_checksum_fallback)
checksum = if @backend.os.family == "windows"
perform_checksum_windows(:md5)
else
@md5_command ||= case @backend.os.family
when "darwin"
"md5 -r"
when "solaris"
"digest -a md5"
else
"md5sum"
end
perform_checksum_unix(@md5_command)
end
checksum || perform_checksum_ruby(:md5)
end
def sha256sum
# Skip processing rest of method if fallback method is selected
return perform_checksum_ruby(:sha256) if defined?(@ruby_checksum_fallback)
checksum = if @backend.os.family == "windows"
perform_checksum_windows(:sha256)
else
@sha256_command ||= case @backend.os.family
when "darwin", "hpux", "qnx"
"shasum -a 256"
when "solaris"
"digest -a sha256"
else
"sha256sum"
end
perform_checksum_unix(@sha256_command)
end
checksum || perform_checksum_ruby(:sha256)
end
private
def perform_checksum_unix(cmd)
res = @backend.run_command("#{cmd} #{@path}")
res.stdout.split(" ").first if res.exit_status == 0
end
def perform_checksum_windows(method)
cmd = "CertUtil -hashfile #{@path} #{method.to_s.upcase}"
res = @backend.run_command(cmd)
res.stdout.split("\r\n")[1].tr(" ", "") if res.exit_status == 0
end
# This pulls the content of the file to the machine running Train and uses
# Digest to perform the checksum. This is less efficient than using remote
# system binaries and can lead to incorrect results due to encoding.
def perform_checksum_ruby(method)
# This is used to skip attempting other checksum methods. If this is set
# then we know all other methods have failed.
@ruby_checksum_fallback = true
case method
when :md5
res = Digest::MD5.new
when :sha256
res = Digest::SHA256.new
end
res.update(content)
res.hexdigest
rescue TypeError => _
nil
end
end
end