]> git.proxmox.com Git - pve-storage.git/blob - PVE/API2/Storage/Status.pm
fix upload permissions. Only allow iso/tmpl upload.
[pve-storage.git] / PVE / API2 / Storage / Status.pm
1 package PVE::API2::Storage::Status;
2
3 use strict;
4 use warnings;
5
6 use File::Path;
7 use File::Basename;
8 use PVE::Tools;
9 use PVE::INotify;
10 use PVE::Cluster qw(cfs_read_file);
11 use PVE::Storage;
12 use PVE::API2::Storage::Content;
13 use PVE::RESTHandler;
14 use PVE::RPCEnvironment;
15 use PVE::JSONSchema qw(get_standard_option);
16 use PVE::Exception qw(raise_param_exc);
17
18 use base qw(PVE::RESTHandler);
19
20 __PACKAGE__->register_method ({
21 subclass => "PVE::API2::Storage::Content",
22 # set fragment delimiter (no subdirs) - we need that, because volume
23 # IDs may contain a slash '/'
24 fragmentDelimiter => '',
25 path => '{storage}/content',
26 });
27
28 __PACKAGE__->register_method ({
29 name => 'index',
30 path => '',
31 method => 'GET',
32 description => "Get status for all datastores.",
33 permissions => {
34 description => "Only list entries where you have 'Datastore.Audit' or 'Datastore.AllocateSpace' permissions on '/storage/<storage>'",
35 user => 'all',
36 },
37 protected => 1,
38 proxyto => 'node',
39 parameters => {
40 additionalProperties => 0,
41 properties => {
42 node => get_standard_option('pve-node'),
43 storage => get_standard_option
44 ('pve-storage-id', {
45 description => "Only list status for specified storage",
46 optional => 1,
47 }),
48 content => {
49 description => "Only list stores which support this content type.",
50 type => 'string', format => 'pve-storage-content',
51 optional => 1,
52 },
53 },
54 },
55 returns => {
56 type => 'array',
57 items => {
58 type => "object",
59 properties => { storage => { type => 'string' } },
60 },
61 links => [ { rel => 'child', href => "{storage}" } ],
62 },
63 code => sub {
64 my ($param) = @_;
65
66 my $rpcenv = PVE::RPCEnvironment::get();
67 my $authuser = $rpcenv->get_user();
68
69 my $cfg = cfs_read_file("storage.cfg");
70
71 my $info = PVE::Storage::storage_info($cfg, $param->{content});
72
73 raise_param_exc({ storage => "No such storage." })
74 if $param->{storage} && !defined($info->{$param->{storage}});
75
76 my $res = {};
77 my @sids = PVE::Storage::storage_ids($cfg);
78 foreach my $storeid (@sids) {
79 my $privs = [ 'Datastore.Audit', 'Datastore.AllocateSpace' ];
80 next if !$rpcenv->check_any($authuser, "/storage/$storeid", $privs, 1);
81 next if $param->{storage} && $param->{storage} ne $storeid;
82 $res->{$storeid} = $info->{$storeid};
83 }
84
85 return PVE::RESTHandler::hash_to_array($res, 'storage');
86 }});
87
88 __PACKAGE__->register_method ({
89 name => 'diridx',
90 path => '{storage}',
91 method => 'GET',
92 description => "",
93 permissions => {
94 check => ['perm', '/storage/{storage}', ['Datastore.Audit', 'Datastore.AllocateSpace'], any => 1],
95 },
96 parameters => {
97 additionalProperties => 0,
98 properties => {
99 node => get_standard_option('pve-node'),
100 storage => get_standard_option('pve-storage-id'),
101 },
102 },
103 returns => {
104 type => 'array',
105 items => {
106 type => "object",
107 properties => {
108 subdir => { type => 'string' },
109 },
110 },
111 links => [ { rel => 'child', href => "{subdir}" } ],
112 },
113 code => sub {
114 my ($param) = @_;
115
116 my $res = [
117 { subdir => 'status' },
118 { subdir => 'content' },
119 { subdir => 'upload' },
120 { subdir => 'rrd' },
121 { subdir => 'rrddata' },
122 ];
123
124 return $res;
125 }});
126
127 __PACKAGE__->register_method ({
128 name => 'read_status',
129 path => '{storage}/status',
130 method => 'GET',
131 description => "Read storage status.",
132 permissions => {
133 check => ['perm', '/storage/{storage}', ['Datastore.Audit', 'Datastore.AllocateSpace'], any => 1],
134 },
135 protected => 1,
136 proxyto => 'node',
137 parameters => {
138 additionalProperties => 0,
139 properties => {
140 node => get_standard_option('pve-node'),
141 storage => get_standard_option('pve-storage-id'),
142 },
143 },
144 returns => {
145 type => "object",
146 properties => {},
147 },
148 code => sub {
149 my ($param) = @_;
150
151 my $cfg = cfs_read_file("storage.cfg");
152
153 my $info = PVE::Storage::storage_info($cfg, $param->{content});
154
155 my $data = $info->{$param->{storage}};
156
157 raise_param_exc({ storage => "No such storage." })
158 if !defined($data);
159
160 return $data;
161 }});
162
163 __PACKAGE__->register_method ({
164 name => 'rrd',
165 path => '{storage}/rrd',
166 method => 'GET',
167 description => "Read storage RRD statistics (returns PNG).",
168 permissions => {
169 check => ['perm', '/storage/{storage}', ['Datastore.Audit', 'Datastore.AllocateSpace'], any => 1],
170 },
171 protected => 1,
172 proxyto => 'node',
173 parameters => {
174 additionalProperties => 0,
175 properties => {
176 node => get_standard_option('pve-node'),
177 storage => get_standard_option('pve-storage-id'),
178 timeframe => {
179 description => "Specify the time frame you are interested in.",
180 type => 'string',
181 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
182 },
183 ds => {
184 description => "The list of datasources you want to display.",
185 type => 'string', format => 'pve-configid-list',
186 },
187 cf => {
188 description => "The RRD consolidation function",
189 type => 'string',
190 enum => [ 'AVERAGE', 'MAX' ],
191 optional => 1,
192 },
193 },
194 },
195 returns => {
196 type => "object",
197 properties => {
198 filename => { type => 'string' },
199 },
200 },
201 code => sub {
202 my ($param) = @_;
203
204 return PVE::Cluster::create_rrd_graph(
205 "pve2-storage/$param->{node}/$param->{storage}",
206 $param->{timeframe}, $param->{ds}, $param->{cf});
207
208 }});
209
210 __PACKAGE__->register_method ({
211 name => 'rrddata',
212 path => '{storage}/rrddata',
213 method => 'GET',
214 description => "Read storage RRD statistics.",
215 permissions => {
216 check => ['perm', '/storage/{storage}', ['Datastore.Audit', 'Datastore.AllocateSpace'], any => 1],
217 },
218 protected => 1,
219 proxyto => 'node',
220 parameters => {
221 additionalProperties => 0,
222 properties => {
223 node => get_standard_option('pve-node'),
224 storage => get_standard_option('pve-storage-id'),
225 timeframe => {
226 description => "Specify the time frame you are interested in.",
227 type => 'string',
228 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
229 },
230 cf => {
231 description => "The RRD consolidation function",
232 type => 'string',
233 enum => [ 'AVERAGE', 'MAX' ],
234 optional => 1,
235 },
236 },
237 },
238 returns => {
239 type => "array",
240 items => {
241 type => "object",
242 properties => {},
243 },
244 },
245 code => sub {
246 my ($param) = @_;
247
248 return PVE::Cluster::create_rrd_data(
249 "pve2-storage/$param->{node}/$param->{storage}",
250 $param->{timeframe}, $param->{cf});
251 }});
252
253 # makes no sense for big images and backup files (because it
254 # create a copy of the file).
255 __PACKAGE__->register_method ({
256 name => 'upload',
257 path => '{storage}/upload',
258 method => 'POST',
259 description => "Upload templates and ISO images.",
260 permissions => {
261 check => ['perm', '/storage/{storage}', ['Datastore.AllocateTemplate']],
262 },
263 protected => 1,
264 parameters => {
265 additionalProperties => 0,
266 properties => {
267 node => get_standard_option('pve-node'),
268 storage => get_standard_option('pve-storage-id'),
269 content => {
270 description => "Content type.",
271 type => 'string', format => 'pve-storage-content',
272 },
273 filename => {
274 description => "The name of the file to create.",
275 type => 'string',
276 },
277 tmpfilename => {
278 description => "The source file name. This parameter is usually set by the REST handler. You can only overwrite it when connecting to the trustet port on localhost.",
279 type => 'string',
280 optional => 1,
281 },
282 },
283 },
284 returns => { type => "string" },
285 code => sub {
286 my ($param) = @_;
287
288 my $rpcenv = PVE::RPCEnvironment::get();
289
290 my $user = $rpcenv->get_user();
291
292 my $cfg = cfs_read_file("storage.cfg");
293
294 my $node = $param->{node};
295 my $scfg = PVE::Storage::storage_check_enabled($cfg, $param->{storage}, $node);
296
297 die "cant upload to storage type '$scfg->{type}'"
298 if !($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs');
299
300 my $content = $param->{content};
301
302 my $tmpfilename = $param->{tmpfilename};
303 die "missing temporary file name\n" if !$tmpfilename;
304
305 my $size = -s $tmpfilename;
306 die "temporary file '$tmpfilename' does not exists\n" if !defined($size);
307
308 my $filename = $param->{filename};
309
310 chomp $filename;
311 $filename =~ s/^.*[\/\\]//;
312 $filename =~ s/\s/_/g;
313
314 my $path;
315
316 if ($content eq 'iso') {
317 if ($filename !~ m![^/]+\.[Ii][Ss][Oo]$!) {
318 raise_param_exc({ filename => "missing '.iso' extension" });
319 }
320 $path = PVE::Storage::get_iso_dir($cfg, $param->{storage});
321 } elsif ($content eq 'vztmpl') {
322 if ($filename !~ m![^/]+\.tar\.gz$!) {
323 raise_param_exc({ filename => "missing '.tar.gz' extension" });
324 }
325 $path = PVE::Storage::get_vztmpl_dir($cfg, $param->{storage});
326 } else {
327 raise_param_exc({ content => "upload content type '$content' not allowed" });
328 }
329
330 die "storage '$param->{storage}' does not support '$content' content\n"
331 if !$scfg->{content}->{$content};
332
333 my $dest = "$path/$filename";
334 my $dirname = dirname($dest);
335
336 # we simply overwrite when destination when file already exists
337
338 my $cmd;
339 if ($node ne 'localhost' && $node ne PVE::INotify::nodename()) {
340 my $remip = PVE::Cluster::remote_node_ip($node);
341
342 my @ssh_options = ('-o', 'BatchMode=yes', '-c', 'blowfish-cbc');
343
344 my @remcmd = ('/usr/bin/ssh', @ssh_options, $remip);
345
346 eval {
347 # activate remote storage
348 PVE::Tools::run_command([@remcmd, '/usr/sbin/pvesm', 'status',
349 '--storage', $param->{storage}]);
350 };
351 die "can't activate storage '$param->{storage}' on node '$node'\n" if $@;
352
353 PVE::Tools::run_command([@remcmd, '/bin/mkdir', '-p', $dirname],
354 errmsg => "mkdir failed");
355
356 $cmd = ['/usr/bin/scp', @ssh_options, $tmpfilename, "$remip:$dest"];
357 } else {
358 PVE::Storage::activate_storage($cfg, $param->{storage});
359 File::Path::make_path($dirname);
360 $cmd = ['cp', $tmpfilename, $dest];
361 }
362
363 my $worker = sub {
364 my $upid = shift;
365
366 print "starting file import from: $tmpfilename\n";
367 print "target node: $node\n";
368 print "target file: $dest\n";
369 print "file size is: $size\n";
370 print "command: " . join(' ', @$cmd) . "\n";
371
372 eval { PVE::Tools::run_command($cmd, errmsg => 'import failed'); };
373 if (my $err = $@) {
374 unlink $dest;
375 die $err;
376 }
377 print "finished file import successfully\n";
378 };
379
380 return $rpcenv->fork_worker('imgcopy', undef, $user, $worker);
381 }});
382
383 1;