]> git.proxmox.com Git - pve-storage.git/blob - PVE/Storage/DirPlugin.pm
fix #3307: make it possible to set protection for backups
[pve-storage.git] / PVE / Storage / DirPlugin.pm
1 package PVE::Storage::DirPlugin;
2
3 use strict;
4 use warnings;
5
6 use Cwd;
7 use File::Path;
8 use IO::File;
9 use POSIX;
10
11 use PVE::Storage::Plugin;
12 use PVE::JSONSchema qw(get_standard_option);
13
14 use base qw(PVE::Storage::Plugin);
15
16 # Configuration
17
18 sub type {
19 return 'dir';
20 }
21
22 sub plugindata {
23 return {
24 content => [ { images => 1, rootdir => 1, vztmpl => 1, iso => 1, backup => 1, snippets => 1, none => 1 },
25 { images => 1, rootdir => 1 }],
26 format => [ { raw => 1, qcow2 => 1, vmdk => 1, subvol => 1 } , 'raw' ],
27 };
28 }
29
30 sub properties {
31 return {
32 path => {
33 description => "File system path.",
34 type => 'string', format => 'pve-storage-path',
35 },
36 mkdir => {
37 description => "Create the directory if it doesn't exist.",
38 type => 'boolean',
39 default => 'yes',
40 },
41 is_mountpoint => {
42 description =>
43 "Assume the given path is an externally managed mountpoint " .
44 "and consider the storage offline if it is not mounted. ".
45 "Using a boolean (yes/no) value serves as a shortcut to using the target path in this field.",
46 type => 'string',
47 default => 'no',
48 },
49 bwlimit => get_standard_option('bwlimit'),
50 };
51 }
52
53 sub options {
54 return {
55 path => { fixed => 1 },
56 nodes => { optional => 1 },
57 shared => { optional => 1 },
58 disable => { optional => 1 },
59 maxfiles => { optional => 1 },
60 'prune-backups' => { optional => 1 },
61 content => { optional => 1 },
62 format => { optional => 1 },
63 mkdir => { optional => 1 },
64 is_mountpoint => { optional => 1 },
65 bwlimit => { optional => 1 },
66 preallocation => { optional => 1 },
67 };
68 }
69
70 # Storage implementation
71 #
72
73 # NOTE: should ProcFSTools::is_mounted accept an optional cache like this?
74 sub path_is_mounted {
75 my ($mountpoint, $mountdata) = @_;
76
77 $mountpoint = Cwd::realpath($mountpoint); # symlinks
78 return 0 if !defined($mountpoint); # path does not exist
79
80 $mountdata = PVE::ProcFSTools::parse_proc_mounts() if !$mountdata;
81 return 1 if grep { $_->[1] eq $mountpoint } @$mountdata;
82 return undef;
83 }
84
85 sub parse_is_mountpoint {
86 my ($scfg) = @_;
87 my $is_mp = $scfg->{is_mountpoint};
88 return undef if !defined $is_mp;
89 if (defined(my $bool = PVE::JSONSchema::parse_boolean($is_mp))) {
90 return $bool ? $scfg->{path} : undef;
91 }
92 return $is_mp; # contains a path
93 }
94
95 # FIXME remove on the next APIAGE reset.
96 # Deprecated, use get_volume_attribute instead.
97 sub get_volume_notes {
98 my ($class, $scfg, $storeid, $volname, $timeout) = @_;
99
100 my ($vtype) = $class->parse_volname($volname);
101 return if $vtype ne 'backup';
102
103 my $path = $class->filesystem_path($scfg, $volname);
104 $path .= $class->SUPER::NOTES_EXT;
105
106 return PVE::Tools::file_get_contents($path) if -f $path;
107
108 return '';
109 }
110
111 # FIXME remove on the next APIAGE reset.
112 # Deprecated, use update_volume_attribute instead.
113 sub update_volume_notes {
114 my ($class, $scfg, $storeid, $volname, $notes, $timeout) = @_;
115
116 my ($vtype) = $class->parse_volname($volname);
117 die "only backups can have notes\n" if $vtype ne 'backup';
118
119 my $path = $class->filesystem_path($scfg, $volname);
120 $path .= $class->SUPER::NOTES_EXT;
121
122 if (defined($notes) && $notes ne '') {
123 PVE::Tools::file_set_contents($path, $notes);
124 } else {
125 unlink $path or $! == ENOENT or die "could not delete notes - $!\n";
126 }
127 return;
128 }
129
130 sub get_volume_attribute {
131 my ($class, $scfg, $storeid, $volname, $attribute) = @_;
132
133 if ($attribute eq 'notes') {
134 return $class->get_volume_notes($scfg, $storeid, $volname);
135 }
136
137 my ($vtype) = $class->parse_volname($volname);
138 return if $vtype ne 'backup';
139
140 if ($attribute eq 'protected') {
141 my $path = $class->filesystem_path($scfg, $volname);
142 return -e PVE::Storage::protection_file_path($path) ? 1 : 0;
143 }
144
145 return;
146 }
147
148 sub update_volume_attribute {
149 my ($class, $scfg, $storeid, $volname, $attribute, $value) = @_;
150
151 if ($attribute eq 'notes') {
152 return $class->update_volume_notes($scfg, $storeid, $volname, $value);
153 }
154
155 my ($vtype) = $class->parse_volname($volname);
156 die "only backups support attribute '$attribute'\n" if $vtype ne 'backup';
157
158 if ($attribute eq 'protected') {
159 my $path = $class->filesystem_path($scfg, $volname);
160 my $protection_path = PVE::Storage::protection_file_path($path);
161
162 return if !((-e $protection_path) xor $value); # protection status already correct
163
164 if ($value) {
165 my $fh = IO::File->new($protection_path, O_CREAT, 0644)
166 or die "unable to create protection file '$protection_path' - $!\n";
167 close($fh);
168 } else {
169 unlink $protection_path or $! == ENOENT
170 or die "could not delete protection file '$protection_path' - $!\n";
171 }
172
173 return;
174 }
175
176 die "attribute '$attribute' is not supported for storage type '$scfg->{type}'\n";
177 }
178
179 sub status {
180 my ($class, $storeid, $scfg, $cache) = @_;
181
182 if (defined(my $mp = parse_is_mountpoint($scfg))) {
183 $cache->{mountdata} = PVE::ProcFSTools::parse_proc_mounts()
184 if !$cache->{mountdata};
185
186 return undef if !path_is_mounted($mp, $cache->{mountdata});
187 }
188
189 return $class->SUPER::status($storeid, $scfg, $cache);
190 }
191
192
193 sub activate_storage {
194 my ($class, $storeid, $scfg, $cache) = @_;
195
196 my $path = $scfg->{path};
197 if (!defined($scfg->{mkdir}) || $scfg->{mkdir}) {
198 mkpath $path;
199 }
200
201 my $mp = parse_is_mountpoint($scfg);
202 if (defined($mp) && !path_is_mounted($mp, $cache->{mountdata})) {
203 die "unable to activate storage '$storeid' - " .
204 "directory is expected to be a mount point but is not mounted: '$mp'\n";
205 }
206
207 $class->SUPER::activate_storage($storeid, $scfg, $cache);
208 }
209
210 sub check_config {
211 my ($self, $sectionId, $config, $create, $skipSchemaCheck) = @_;
212 my $opts = PVE::SectionConfig::check_config($self, $sectionId, $config, $create, $skipSchemaCheck);
213 return $opts if !$create;
214 if ($opts->{path} !~ m@^/[-/a-zA-Z0-9_.]+$@) {
215 die "illegal path for directory storage: $opts->{path}\n";
216 }
217 return $opts;
218 }
219
220 1;