12 use PVE
::Cluster
qw(cfs_register_file cfs_read_file);
16 use PVE
::JSONSchema
qw(get_standard_option);
17 use PVE
::Tools
qw($IPV6RE $IPV4RE dir_glob_foreach);
19 use PVE
::AccessControl
;
21 use Time
::HiRes qw
(gettimeofday
);
25 my $nodename = PVE
::INotify
::nodename
();
27 my $cpuinfo= PVE
::ProcFSTools
::read_cpuinfo
();
29 cfs_register_file
('/lxc/', \
&parse_pct_config
, \
&write_pct_config
);
35 format_description
=> 'volume',
36 description
=> 'Volume, device or directory to mount into the container.',
40 format_description
=> '[1|0]',
41 description
=> 'Whether to include the mountpoint in backups.',
46 format
=> 'disk-size',
47 format_description
=> 'DiskSize',
48 description
=> 'Volume size (read only value).',
53 PVE
::JSONSchema
::register_standard_option
('pve-ct-rootfs', {
54 type
=> 'string', format
=> $rootfs_desc,
55 description
=> "Use volume as container root.",
59 PVE
::JSONSchema
::register_standard_option
('pve-lxc-snapshot-name', {
60 description
=> "The name of the snapshot.",
61 type
=> 'string', format
=> 'pve-configid',
69 description
=> "Lock/unlock the VM.",
70 enum
=> [qw(migrate backup snapshot rollback)],
75 description
=> "Specifies whether a VM will be started during system bootup.",
78 startup
=> get_standard_option
('pve-startup-order'),
82 description
=> "Enable/disable Template.",
88 enum
=> ['amd64', 'i386'],
89 description
=> "OS architecture type.",
95 enum
=> ['debian', 'ubuntu', 'centos', 'archlinux'],
96 description
=> "OS type. Corresponds to lxc setup scripts in /usr/share/lxc/config/<ostype>.common.conf.",
101 description
=> "Attach a console device (/dev/console) to the container.",
107 description
=> "Specify the number of tty available to the container",
115 description
=> "Limit of CPU usage. Note if the computer has 2 CPUs, it has total of '2' CPU time. Value '0' indicates no CPU limit.",
123 description
=> "CPU weight for a VM. Argument is used in the kernel fair scheduler. The larger the number is, the more CPU time this VM gets. Number is relative to weights of all the other running VMs.\n\nNOTE: You can disable fair-scheduler configuration by setting this to 0.",
131 description
=> "Amount of RAM for the VM in MB.",
138 description
=> "Amount of SWAP for the VM in MB.",
144 description
=> "Set a host name for the container.",
145 type
=> 'string', format
=> 'dns-name',
151 description
=> "Container description. Only used on the configuration web interface.",
155 type
=> 'string', format
=> 'dns-name-list',
156 description
=> "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
160 type
=> 'string', format
=> 'address-list',
161 description
=> "Sets DNS server IP address for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
163 rootfs
=> get_standard_option
('pve-ct-rootfs'),
166 type
=> 'string', format
=> 'pve-configid',
168 description
=> "Parent snapshot name. This is used internally, and should not be modified.",
172 description
=> "Timestamp for snapshots.",
178 description
=> "Console mode. By default, the console command tries to open a connection to one of the available tty devices. By setting cmode to 'console' it tries to attach to /dev/console instead. If you set cmode to 'shell', it simply invokes a shell inside the container (no login).",
180 enum
=> ['shell', 'console', 'tty'],
186 description
=> "Sets the protection flag of the container. This will prevent the remove operation. This will prevent the CT or CT's disk remove/update operation.",
191 my $valid_lxc_conf_keys = {
195 'lxc.haltsignal' => 1,
196 'lxc.rebootsignal' => 1,
197 'lxc.stopsignal' => 1,
199 'lxc.network.type' => 1,
200 'lxc.network.flags' => 1,
201 'lxc.network.link' => 1,
202 'lxc.network.mtu' => 1,
203 'lxc.network.name' => 1,
204 'lxc.network.hwaddr' => 1,
205 'lxc.network.ipv4' => 1,
206 'lxc.network.ipv4.gateway' => 1,
207 'lxc.network.ipv6' => 1,
208 'lxc.network.ipv6.gateway' => 1,
209 'lxc.network.script.up' => 1,
210 'lxc.network.script.down' => 1,
212 'lxc.console.logfile' => 1,
215 'lxc.devttydir' => 1,
216 'lxc.hook.autodev' => 1,
220 'lxc.mount.entry' => 1,
221 'lxc.mount.auto' => 1,
223 'lxc.rootfs.mount' => 1,
224 'lxc.rootfs.options' => 1,
228 'lxc.aa_profile' => 1,
229 'lxc.aa_allow_incomplete' => 1,
230 'lxc.se_context' => 1,
233 'lxc.hook.pre-start' => 1,
234 'lxc.hook.pre-mount' => 1,
235 'lxc.hook.mount' => 1,
236 'lxc.hook.start' => 1,
237 'lxc.hook.stop' => 1,
238 'lxc.hook.post-stop' => 1,
239 'lxc.hook.clone' => 1,
240 'lxc.hook.destroy' => 1,
243 'lxc.start.auto' => 1,
244 'lxc.start.delay' => 1,
245 'lxc.start.order' => 1,
247 'lxc.environment' => 1,
258 description
=> "Network interface type.",
263 format_description
=> 'String',
264 description
=> 'Name of the network device as seen from inside the container. (lxc.network.name)',
265 pattern
=> '[-_.\w\d]+',
269 format_description
=> 'vmbr<Number>',
270 description
=> 'Bridge to attach the network device to.',
271 pattern
=> '[-_.\w\d]+',
276 format_description
=> 'MAC',
277 description
=> 'Bridge to attach the network device to. (lxc.network.hwaddr)',
278 pattern
=> qr/(?:[a-f0-9]{2}:){5}[a-f0-9]{2}/i,
283 format_description
=> 'Number',
284 description
=> 'Maximum transfer unit of the interface. (lxc.network.mtu)',
285 minimum
=> 64, # minimum ethernet frame is 64 bytes
290 format
=> 'pve-ipv4-config',
291 format_description
=> 'IPv4Format/CIDR',
292 description
=> 'IPv4 address in CIDR format.',
298 format_description
=> 'GatewayIPv4',
299 description
=> 'Default gateway for IPv4 traffic.',
304 format
=> 'pve-ipv6-config',
305 format_description
=> 'IPv6Format/CIDR',
306 description
=> 'IPv6 address in CIDR format.',
312 format_description
=> 'GatewayIPv6',
313 description
=> 'Default gateway for IPv6 traffic.',
318 format_description
=> '[1|0]',
319 description
=> "Controls whether this interface's firewall rules should be used.",
324 format_description
=> 'VlanNo',
327 description
=> "VLAN tag foro this interface.",
331 PVE
::JSONSchema
::register_format
('pve-lxc-network', $netconf_desc);
333 my $MAX_LXC_NETWORKS = 10;
334 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
335 $confdesc->{"net$i"} = {
337 type
=> 'string', format
=> $netconf_desc,
338 description
=> "Specifies network interfaces for the container.",
346 format_description
=> 'Path',
347 description
=> 'Path to the mountpoint as seen from inside the container.',
351 PVE
::JSONSchema
::register_format
('pve-ct-mountpoint', $mp_desc);
355 type
=> 'string', format
=> 'pve-volume-id',
356 description
=> "Reference to unused volumes.",
359 my $MAX_MOUNT_POINTS = 10;
360 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
361 $confdesc->{"mp$i"} = {
363 type
=> 'string', format
=> $mp_desc,
364 description
=> "Use volume as container mount point (experimental feature).",
369 my $MAX_UNUSED_DISKS = $MAX_MOUNT_POINTS;
370 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
371 $confdesc->{"unused$i"} = $unuseddesc;
374 sub write_pct_config
{
375 my ($filename, $conf) = @_;
377 delete $conf->{snapstate
}; # just to be sure
379 my $generate_raw_config = sub {
384 # add description as comment to top of file
385 my $descr = $conf->{description
} || '';
386 foreach my $cl (split(/\n/, $descr)) {
387 $raw .= '#' . PVE
::Tools
::encode_text
($cl) . "\n";
390 foreach my $key (sort keys %$conf) {
391 next if $key eq 'digest' || $key eq 'description' || $key eq 'pending' ||
392 $key eq 'snapshots' || $key eq 'snapname' || $key eq 'lxc';
393 my $value = $conf->{$key};
394 die "detected invalid newline inside property '$key'\n" if $value =~ m/\n/;
395 $raw .= "$key: $value\n";
398 if (my $lxcconf = $conf->{lxc
}) {
399 foreach my $entry (@$lxcconf) {
400 my ($k, $v) = @$entry;
408 my $raw = &$generate_raw_config($conf);
410 foreach my $snapname (sort keys %{$conf->{snapshots
}}) {
411 $raw .= "\n[$snapname]\n";
412 $raw .= &$generate_raw_config($conf->{snapshots
}->{$snapname});
419 my ($key, $value) = @_;
421 die "unknown setting '$key'\n" if !$confdesc->{$key};
423 my $type = $confdesc->{$key}->{type
};
425 if (!defined($value)) {
426 die "got undefined value\n";
429 if ($value =~ m/[\n\r]/) {
430 die "property contains a line feed\n";
433 if ($type eq 'boolean') {
434 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
435 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
436 die "type check ('boolean') failed - got '$value'\n";
437 } elsif ($type eq 'integer') {
438 return int($1) if $value =~ m/^(\d+)$/;
439 die "type check ('integer') failed - got '$value'\n";
440 } elsif ($type eq 'number') {
441 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
442 die "type check ('number') failed - got '$value'\n";
443 } elsif ($type eq 'string') {
444 if (my $fmt = $confdesc->{$key}->{format
}) {
445 PVE
::JSONSchema
::check_format
($fmt, $value);
454 sub parse_pct_config
{
455 my ($filename, $raw) = @_;
457 return undef if !defined($raw);
460 digest
=> Digest
::SHA
::sha1_hex
($raw),
464 $filename =~ m
|/lxc/(\d
+).conf
$|
465 || die "got strange filename '$filename'";
473 my @lines = split(/\n/, $raw);
474 foreach my $line (@lines) {
475 next if $line =~ m/^\s*$/;
477 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
479 $conf->{description
} = $descr if $descr;
481 $conf = $res->{snapshots
}->{$section} = {};
485 if ($line =~ m/^\#(.*)\s*$/) {
486 $descr .= PVE
::Tools
::decode_text
($1) . "\n";
490 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
493 if ($valid_lxc_conf_keys->{$key} || $key =~ m/^lxc\.cgroup\./) {
494 push @{$conf->{lxc
}}, [$key, $value];
496 warn "vm $vmid - unable to parse config: $line\n";
498 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
499 $descr .= PVE
::Tools
::decode_text
($2);
500 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
501 $conf->{snapstate
} = $1;
502 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
505 eval { $value = check_type
($key, $value); };
506 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
507 $conf->{$key} = $value;
509 warn "vm $vmid - unable to parse config: $line\n";
513 $conf->{description
} = $descr if $descr;
515 delete $res->{snapstate
}; # just to be sure
521 my $vmlist = PVE
::Cluster
::get_vmlist
();
523 return $res if !$vmlist || !$vmlist->{ids
};
524 my $ids = $vmlist->{ids
};
526 foreach my $vmid (keys %$ids) {
527 next if !$vmid; # skip CT0
528 my $d = $ids->{$vmid};
529 next if !$d->{node
} || $d->{node
} ne $nodename;
530 next if !$d->{type
} || $d->{type
} ne 'lxc';
531 $res->{$vmid}->{type
} = 'lxc';
536 sub cfs_config_path
{
537 my ($vmid, $node) = @_;
539 $node = $nodename if !$node;
540 return "nodes/$node/lxc/$vmid.conf";
544 my ($vmid, $node) = @_;
546 my $cfspath = cfs_config_path
($vmid, $node);
547 return "/etc/pve/$cfspath";
551 my ($vmid, $node) = @_;
553 $node = $nodename if !$node;
554 my $cfspath = cfs_config_path
($vmid, $node);
556 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath);
557 die "container $vmid does not exists\n" if !defined($conf);
563 my ($vmid, $conf) = @_;
565 my $dir = "/etc/pve/nodes/$nodename/lxc";
568 write_config
($vmid, $conf);
574 unlink config_file
($vmid, $nodename);
578 my ($vmid, $conf) = @_;
580 my $cfspath = cfs_config_path
($vmid);
582 PVE
::Cluster
::cfs_write_file
($cfspath, $conf);
585 # flock: we use one file handle per process, so lock file
586 # can be called multiple times and succeeds for the same process.
588 my $lock_handles = {};
589 my $lockdir = "/run/lock/lxc";
594 return "$lockdir/pve-config-${vmid}.lock";
598 my ($vmid, $timeout) = @_;
600 $timeout = 10 if !$timeout;
603 my $filename = lock_filename
($vmid);
605 mkdir $lockdir if !-d
$lockdir;
607 my $lock_func = sub {
608 if (!$lock_handles->{$$}->{$filename}) {
609 my $fh = new IO
::File
(">>$filename") ||
610 die "can't open file - $!\n";
611 $lock_handles->{$$}->{$filename} = { fh
=> $fh, refcount
=> 0};
614 if (!flock($lock_handles->{$$}->{$filename}->{fh
}, $mode |LOCK_NB
)) {
615 print STDERR
"trying to aquire lock...";
618 $success = flock($lock_handles->{$$}->{$filename}->{fh
}, $mode);
619 # try again on EINTR (see bug #273)
620 if ($success || ($! != EINTR
)) {
625 print STDERR
" failed\n";
626 die "can't aquire lock - $!\n";
629 print STDERR
" OK\n";
632 $lock_handles->{$$}->{$filename}->{refcount
}++;
635 eval { PVE
::Tools
::run_with_timeout
($timeout, $lock_func); };
638 die "can't lock file '$filename' - $err";
645 my $filename = lock_filename
($vmid);
647 if (my $fh = $lock_handles->{$$}->{$filename}->{fh
}) {
648 my $refcount = --$lock_handles->{$$}->{$filename}->{refcount
};
649 if ($refcount <= 0) {
650 $lock_handles->{$$}->{$filename} = undef;
657 my ($vmid, $timeout, $code, @param) = @_;
661 lock_aquire
($vmid, $timeout);
662 eval { $res = &$code(@param) };
674 return defined($confdesc->{$name});
677 # add JSON properties for create and set function
678 sub json_config_properties
{
681 foreach my $opt (keys %$confdesc) {
682 next if $opt eq 'parent' || $opt eq 'snaptime';
683 next if $prop->{$opt};
684 $prop->{$opt} = $confdesc->{$opt};
690 sub json_config_properties_no_rootfs
{
693 foreach my $opt (keys %$confdesc) {
694 next if $prop->{$opt};
695 next if $opt eq 'parent' || $opt eq 'snaptime' || $opt eq 'rootfs';
696 $prop->{$opt} = $confdesc->{$opt};
702 # container status helpers
704 sub list_active_containers
{
706 my $filename = "/proc/net/unix";
708 # similar test is used by lcxcontainers.c: list_active_containers
711 my $fh = IO
::File-
>new ($filename, "r");
714 while (defined(my $line = <$fh>)) {
715 if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) {
717 if ($path =~ m!^@/var/lib/lxc/(\d+)/command$!) {
728 # warning: this is slow
732 my $active_hash = list_active_containers
();
734 return 1 if defined($active_hash->{$vmid});
739 sub get_container_disk_usage
{
740 my ($vmid, $pid, $timeout) = @_;
742 return PVE
::Tools
::df
("/proc/$pid/root/", $timeout // 3);
745 my $last_proc_vmid_stat;
747 my $parse_cpuacct_stat = sub {
750 my $raw = read_cgroup_value
('cpuacct', $vmid, 'cpuacct.stat', 1);
754 if ($raw =~ m/^user (\d+)\nsystem (\d+)\n/) {
767 my $list = $opt_vmid ?
{ $opt_vmid => { type
=> 'lxc' }} : config_list
();
769 my $active_hash = list_active_containers
();
771 my $cpucount = $cpuinfo->{cpus
} || 1;
773 my $cdtime = gettimeofday
;
775 my $uptime = (PVE
::ProcFSTools
::read_proc_uptime
(1))[0];
780 $vmid => ($active_hash->{$vmid} ? find_lxc_pid
($vmid) : undef)
783 foreach my $vmid (keys %$list) {
784 my $d = $list->{$vmid};
786 my $running = defined($active_hash->{$vmid});
788 $d->{status
} = $running ?
'running' : 'stopped';
790 my $cfspath = cfs_config_path
($vmid);
791 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath) || {};
793 $d->{name
} = $conf->{'hostname'} || "CT$vmid";
794 $d->{name
} =~ s/[\s]//g;
796 $d->{cpus
} = $conf->{cpulimit
} || $cpucount;
799 my $res = get_container_disk_usage
($vmid, $pids->{$vmid});
800 $d->{disk
} = $res->{used
};
801 $d->{maxdisk
} = $res->{total
};
804 # use 4GB by default ??
805 if (my $rootfs = $conf->{rootfs
}) {
806 my $rootinfo = parse_ct_mountpoint
($rootfs);
807 $d->{maxdisk
} = int(($rootinfo->{size
} || 4)*1024*1024)*1024;
809 $d->{maxdisk
} = 4*1024*1024*1024;
815 $d->{maxmem
} = ($conf->{memory
}||512)*1024*1024;
816 $d->{maxswap
} = ($conf->{swap
}//0)*1024*1024;
827 $d->{template
} = is_template
($conf);
830 foreach my $vmid (keys %$list) {
831 my $d = $list->{$vmid};
832 next if $d->{status
} ne 'running';
834 my $pid = $pids->{$vmid};
835 my $ctime = (stat("/proc/$pid"))[10]; # 10 = ctime
836 $d->{uptime
} = time - $ctime; # the method lxcfs uses
838 $d->{mem
} = read_cgroup_value
('memory', $vmid, 'memory.usage_in_bytes');
839 $d->{swap
} = read_cgroup_value
('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem
};
841 my $blkio_bytes = read_cgroup_value
('blkio', $vmid, 'blkio.throttle.io_service_bytes', 1);
842 my @bytes = split(/\n/, $blkio_bytes);
843 foreach my $byte (@bytes) {
844 if (my ($key, $value) = $byte =~ /(Read|Write)\s+(\d+)/) {
845 $d->{diskread
} = $2 if $key eq 'Read';
846 $d->{diskwrite
} = $2 if $key eq 'Write';
850 my $pstat = &$parse_cpuacct_stat($vmid);
852 my $used = $pstat->{utime} + $pstat->{stime
};
854 my $old = $last_proc_vmid_stat->{$vmid};
856 $last_proc_vmid_stat->{$vmid} = {
864 my $dtime = ($cdtime - $old->{time}) * $cpucount * $cpuinfo->{user_hz
};
867 my $dutime = $used - $old->{used
};
869 $d->{cpu
} = (($dutime/$dtime)* $cpucount) / $d->{cpus
};
870 $last_proc_vmid_stat->{$vmid} = {
876 $d->{cpu
} = $old->{cpu
};
880 my $netdev = PVE
::ProcFSTools
::read_proc_net_dev
();
882 foreach my $dev (keys %$netdev) {
883 next if $dev !~ m/^veth([1-9]\d*)i/;
885 my $d = $list->{$vmid};
889 $d->{netout
} += $netdev->{$dev}->{receive
};
890 $d->{netin
} += $netdev->{$dev}->{transmit
};
897 sub parse_ct_mountpoint
{
898 my ($data, $noerr) = @_;
903 eval { $res = PVE
::JSONSchema
::parse_property_string
($mp_desc, $data) };
905 return undef if $noerr;
909 if (defined(my $size = $res->{size
})) {
910 $size = PVE
::JSONSchema
::parse_size
($size);
911 if (!defined($size)) {
912 return undef if $noerr;
913 die "invalid size: $size\n";
915 $res->{size
} = $size;
921 sub print_ct_mountpoint
{
922 my ($info, $nomp) = @_;
923 my $skip = $nomp ?
['mp'] : [];
924 return PVE
::JSONSchema
::print_property_string
($info, $mp_desc, $skip);
927 sub print_lxc_network
{
929 return PVE
::JSONSchema
::print_property_string
($net, $netconf_desc);
932 sub parse_lxc_network
{
937 return $res if !$data;
939 $res = PVE
::JSONSchema
::parse_property_string
($netconf_desc, $data);
941 $res->{type
} = 'veth';
942 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
() if !$res->{hwaddr
};
947 sub read_cgroup_value
{
948 my ($group, $vmid, $name, $full) = @_;
950 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
952 return PVE
::Tools
::file_get_contents
($path) if $full;
954 return PVE
::Tools
::file_read_firstline
($path);
957 sub write_cgroup_value
{
958 my ($group, $vmid, $name, $value) = @_;
960 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
961 PVE
::ProcFSTools
::write_proc_entry
($path, $value) if -e
$path;
965 sub find_lxc_console_pids
{
969 PVE
::Tools
::dir_glob_foreach
('/proc', '\d+', sub {
972 my $cmdline = PVE
::Tools
::file_read_firstline
("/proc/$pid/cmdline");
975 my @args = split(/\0/, $cmdline);
977 # serach for lxc-console -n <vmid>
978 return if scalar(@args) != 3;
979 return if $args[1] ne '-n';
980 return if $args[2] !~ m/^\d+$/;
981 return if $args[0] !~ m
|^(/usr/bin
/)?lxc-console
$|;
985 push @{$res->{$vmid}}, $pid;
997 $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
999 PVE
::Tools
::run_command
(['lxc-info', '-n', $vmid, '-p'], outfunc
=> $parser);
1001 die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
1006 # Note: we cannot use Net:IP, because that only allows strict
1008 sub parse_ipv4_cidr
{
1009 my ($cidr, $noerr) = @_;
1011 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 <= 32)) {
1012 return { address
=> $1, netmask
=> $PVE::Network
::ipv4_reverse_mask-
>[$2] };
1015 return undef if $noerr;
1017 die "unable to parse ipv4 address/mask\n";
1023 die "VM is locked ($conf->{'lock'})\n" if $conf->{'lock'};
1026 sub check_protection
{
1027 my ($vm_conf, $err_msg) = @_;
1029 if ($vm_conf->{protection
}) {
1030 die "$err_msg - protection mode enabled\n";
1034 sub update_lxc_config
{
1035 my ($storage_cfg, $vmid, $conf) = @_;
1037 my $dir = "/var/lib/lxc/$vmid";
1039 if ($conf->{template
}) {
1041 unlink "$dir/config";
1048 die "missing 'arch' - internal error" if !$conf->{arch
};
1049 $raw .= "lxc.arch = $conf->{arch}\n";
1051 my $ostype = $conf->{ostype
} || die "missing 'ostype' - internal error";
1052 if ($ostype =~ /^(?:debian | ubuntu | centos | archlinux)$/x) {
1053 $raw .= "lxc.include = /usr/share/lxc/config/$ostype.common.conf\n";
1058 if (!has_dev_console
($conf)) {
1059 $raw .= "lxc.console = none\n";
1060 $raw .= "lxc.cgroup.devices.deny = c 5:1 rwm\n";
1063 my $ttycount = get_tty_count
($conf);
1064 $raw .= "lxc.tty = $ttycount\n";
1066 # some init scripts expects a linux terminal (turnkey).
1067 $raw .= "lxc.environment = TERM=linux\n";
1069 my $utsname = $conf->{hostname
} || "CT$vmid";
1070 $raw .= "lxc.utsname = $utsname\n";
1072 my $memory = $conf->{memory
} || 512;
1073 my $swap = $conf->{swap
} // 0;
1075 my $lxcmem = int($memory*1024*1024);
1076 $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n";
1078 my $lxcswap = int(($memory + $swap)*1024*1024);
1079 $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n";
1081 if (my $cpulimit = $conf->{cpulimit
}) {
1082 $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n";
1083 my $value = int(100000*$cpulimit);
1084 $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n";
1087 my $shares = $conf->{cpuunits
} || 1024;
1088 $raw .= "lxc.cgroup.cpu.shares = $shares\n";
1090 my $mountpoint = parse_ct_mountpoint
($conf->{rootfs
});
1091 $mountpoint->{mp
} = '/';
1093 my ($path, $use_loopdev) = mountpoint_mount_path
($mountpoint, $storage_cfg);
1094 $path = "loop:$path" if $use_loopdev;
1096 $raw .= "lxc.rootfs = $path\n";
1099 foreach my $k (keys %$conf) {
1100 next if $k !~ m/^net(\d+)$/;
1102 my $d = parse_lxc_network
($conf->{$k});
1104 $raw .= "lxc.network.type = veth\n";
1105 $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
1106 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr
});
1107 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name
});
1108 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu
});
1111 if (my $lxcconf = $conf->{lxc
}) {
1112 foreach my $entry (@$lxcconf) {
1113 my ($k, $v) = @$entry;
1114 $netcount++ if $k eq 'lxc.network.type';
1115 $raw .= "$k = $v\n";
1119 $raw .= "lxc.network.type = empty\n" if !$netcount;
1121 File
::Path
::mkpath
("$dir/rootfs");
1123 PVE
::Tools
::file_set_contents
("$dir/config", $raw);
1126 # verify and cleanup nameserver list (replace \0 with ' ')
1127 sub verify_nameserver_list
{
1128 my ($nameserver_list) = @_;
1131 foreach my $server (PVE
::Tools
::split_list
($nameserver_list)) {
1132 PVE
::JSONSchema
::pve_verify_ip
($server);
1133 push @list, $server;
1136 return join(' ', @list);
1139 sub verify_searchdomain_list
{
1140 my ($searchdomain_list) = @_;
1143 foreach my $server (PVE
::Tools
::split_list
($searchdomain_list)) {
1144 # todo: should we add checks for valid dns domains?
1145 push @list, $server;
1148 return join(' ', @list);
1151 sub add_unused_volume
{
1152 my ($config, $volid) = @_;
1154 # skip bind mounts and block devices
1155 return if $volid =~ m
|^/|;
1158 for (my $ind = $MAX_UNUSED_DISKS - 1; $ind >= 0; $ind--) {
1159 my $test = "unused$ind";
1160 if (my $vid = $config->{$test}) {
1161 return if $vid eq $volid; # do not add duplicates
1167 die "To many unused volume - please delete them first.\n" if !$key;
1169 $config->{$key} = $volid;
1174 sub update_pct_config
{
1175 my ($vmid, $conf, $running, $param, $delete) = @_;
1180 my @deleted_volumes;
1184 my $pid = find_lxc_pid
($vmid);
1185 $rootdir = "/proc/$pid/root";
1188 my $hotplug_error = sub {
1190 push @nohotplug, @_;
1197 if (defined($delete)) {
1198 foreach my $opt (@$delete) {
1199 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
1200 die "unable to delete required option '$opt'\n";
1201 } elsif ($opt eq 'swap') {
1202 delete $conf->{$opt};
1203 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
1204 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
1205 delete $conf->{$opt};
1206 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
1207 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1208 next if $hotplug_error->($opt);
1209 delete $conf->{$opt};
1210 } elsif ($opt =~ m/^net(\d)$/) {
1211 delete $conf->{$opt};
1214 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
1215 } elsif ($opt eq 'protection') {
1216 delete $conf->{$opt};
1217 } elsif ($opt =~ m/^unused(\d+)$/) {
1218 next if $hotplug_error->($opt);
1219 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1220 push @deleted_volumes, $conf->{$opt};
1221 delete $conf->{$opt};
1222 } elsif ($opt =~ m/^mp(\d+)$/) {
1223 next if $hotplug_error->($opt);
1224 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1225 my $mountpoint = parse_ct_mountpoint
($conf->{$opt});
1226 add_unused_volume
($conf, $mountpoint->{volume
});
1227 delete $conf->{$opt};
1231 write_config
($vmid, $conf) if $running;
1235 # There's no separate swap size to configure, there's memory and "total"
1236 # memory (iow. memory+swap). This means we have to change them together.
1237 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
1238 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
1239 if (defined($wanted_memory) || defined($wanted_swap)) {
1241 $wanted_memory //= ($conf->{memory
} || 512);
1242 $wanted_swap //= ($conf->{swap
} || 0);
1244 my $total = $wanted_memory + $wanted_swap;
1246 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1247 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1249 $conf->{memory
} = $wanted_memory;
1250 $conf->{swap
} = $wanted_swap;
1252 write_config
($vmid, $conf) if $running;
1255 foreach my $opt (keys %$param) {
1256 my $value = $param->{$opt};
1257 if ($opt eq 'hostname') {
1258 $conf->{$opt} = $value;
1259 } elsif ($opt eq 'onboot') {
1260 $conf->{$opt} = $value ?
1 : 0;
1261 } elsif ($opt eq 'startup') {
1262 $conf->{$opt} = $value;
1263 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1264 next if $hotplug_error->($opt);
1265 $conf->{$opt} = $value;
1266 } elsif ($opt eq 'nameserver') {
1267 next if $hotplug_error->($opt);
1268 my $list = verify_nameserver_list
($value);
1269 $conf->{$opt} = $list;
1270 } elsif ($opt eq 'searchdomain') {
1271 next if $hotplug_error->($opt);
1272 my $list = verify_searchdomain_list
($value);
1273 $conf->{$opt} = $list;
1274 } elsif ($opt eq 'cpulimit') {
1275 next if $hotplug_error->($opt); # FIXME: hotplug
1276 $conf->{$opt} = $value;
1277 } elsif ($opt eq 'cpuunits') {
1278 $conf->{$opt} = $value;
1279 write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
1280 } elsif ($opt eq 'description') {
1281 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
1282 } elsif ($opt =~ m/^net(\d+)$/) {
1284 my $net = parse_lxc_network
($value);
1286 $conf->{$opt} = print_lxc_network
($net);
1288 update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1290 } elsif ($opt eq 'protection') {
1291 $conf->{$opt} = $value ?
1 : 0;
1292 } elsif ($opt =~ m/^mp(\d+)$/) {
1293 next if $hotplug_error->($opt);
1294 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1295 $conf->{$opt} = $value;
1297 } elsif ($opt eq 'rootfs') {
1298 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1299 die "implement me: $opt";
1301 die "implement me: $opt";
1303 write_config
($vmid, $conf) if $running;
1306 if (@deleted_volumes) {
1307 my $storage_cfg = PVE
::Storage
::config
();
1308 foreach my $volume (@deleted_volumes) {
1309 delete_mountpoint_volume
($storage_cfg, $vmid, $volume);
1314 my $storage_cfg = PVE
::Storage
::config
();
1315 create_disks
($storage_cfg, $vmid, $conf, $conf);
1318 # This should be the last thing we do here
1319 if ($running && scalar(@nohotplug)) {
1320 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1324 sub has_dev_console
{
1327 return !(defined($conf->{console
}) && !$conf->{console
});
1333 return $conf->{tty
} // $confdesc->{tty
}->{default};
1339 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1342 sub get_console_command
{
1343 my ($vmid, $conf) = @_;
1345 my $cmode = get_cmode
($conf);
1347 if ($cmode eq 'console') {
1348 return ['lxc-console', '-n', $vmid, '-t', 0];
1349 } elsif ($cmode eq 'tty') {
1350 return ['lxc-console', '-n', $vmid];
1351 } elsif ($cmode eq 'shell') {
1352 return ['lxc-attach', '--clear-env', '-n', $vmid];
1354 die "internal error";
1358 sub get_primary_ips
{
1361 # return data from net0
1363 return undef if !defined($conf->{net0
});
1364 my $net = parse_lxc_network
($conf->{net0
});
1366 my $ipv4 = $net->{ip
};
1368 if ($ipv4 =~ /^(dhcp|manual)$/) {
1374 my $ipv6 = $net->{ip6
};
1376 if ($ipv6 =~ /^(auto|dhcp|manual)$/) {
1383 return ($ipv4, $ipv6);
1386 sub delete_mountpoint_volume
{
1387 my ($storage_cfg, $vmid, $volume) = @_;
1389 # skip bind mounts and block devices
1390 if ($volume =~ m
|^/|) {
1394 my ($vtype, $name, $owner) = PVE
::Storage
::parse_volname
($storage_cfg, $volume);
1395 PVE
::Storage
::vdisk_free
($storage_cfg, $volume) if $vmid == $owner;
1398 sub destroy_lxc_container
{
1399 my ($storage_cfg, $vmid, $conf) = @_;
1401 foreach_mountpoint
($conf, sub {
1402 my ($ms, $mountpoint) = @_;
1403 delete_mountpoint_volume
($storage_cfg, $vmid, $mountpoint->{volume
});
1406 rmdir "/var/lib/lxc/$vmid/rootfs";
1407 unlink "/var/lib/lxc/$vmid/config";
1408 rmdir "/var/lib/lxc/$vmid";
1409 destroy_config
($vmid);
1411 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1412 #PVE::Tools::run_command($cmd);
1415 sub vm_stop_cleanup
{
1416 my ($storage_cfg, $vmid, $conf, $keepActive) = @_;
1421 my $vollist = get_vm_volumes
($conf);
1422 PVE
::Storage
::deactivate_volumes
($storage_cfg, $vollist);
1425 warn $@ if $@; # avoid errors - just warn
1428 my $safe_num_ne = sub {
1431 return 0 if !defined($a) && !defined($b);
1432 return 1 if !defined($a);
1433 return 1 if !defined($b);
1438 my $safe_string_ne = sub {
1441 return 0 if !defined($a) && !defined($b);
1442 return 1 if !defined($a);
1443 return 1 if !defined($b);
1449 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1451 if ($newnet->{type
} ne 'veth') {
1452 # for when there are physical interfaces
1453 die "cannot update interface of type $newnet->{type}";
1456 my $veth = "veth${vmid}i${netid}";
1457 my $eth = $newnet->{name
};
1459 if (my $oldnetcfg = $conf->{$opt}) {
1460 my $oldnet = parse_lxc_network
($oldnetcfg);
1462 if (&$safe_string_ne($oldnet->{hwaddr
}, $newnet->{hwaddr
}) ||
1463 &$safe_string_ne($oldnet->{name
}, $newnet->{name
})) {
1465 PVE
::Network
::veth_delete
($veth);
1466 delete $conf->{$opt};
1467 write_config
($vmid, $conf);
1469 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1471 } elsif (&$safe_string_ne($oldnet->{bridge
}, $newnet->{bridge
}) ||
1472 &$safe_num_ne($oldnet->{tag
}, $newnet->{tag
}) ||
1473 &$safe_num_ne($oldnet->{firewall
}, $newnet->{firewall
})) {
1475 if ($oldnet->{bridge
}) {
1476 PVE
::Network
::tap_unplug
($veth);
1477 foreach (qw(bridge tag firewall)) {
1478 delete $oldnet->{$_};
1480 $conf->{$opt} = print_lxc_network
($oldnet);
1481 write_config
($vmid, $conf);
1484 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1485 foreach (qw(bridge tag firewall)) {
1486 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1488 $conf->{$opt} = print_lxc_network
($oldnet);
1489 write_config
($vmid, $conf);
1492 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1495 update_ipconfig
($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1499 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
1501 my $veth = "veth${vmid}i${netid}";
1502 my $vethpeer = $veth . "p";
1503 my $eth = $newnet->{name
};
1505 PVE
::Network
::veth_create
($veth, $vethpeer, $newnet->{bridge
}, $newnet->{hwaddr
});
1506 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1508 # attach peer in container
1509 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1510 PVE
::Tools
::run_command
($cmd);
1512 # link up peer in container
1513 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1514 PVE
::Tools
::run_command
($cmd);
1516 my $done = { type
=> 'veth' };
1517 foreach (qw(bridge tag firewall hwaddr name)) {
1518 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1520 $conf->{$opt} = print_lxc_network
($done);
1522 write_config
($vmid, $conf);
1525 sub update_ipconfig
{
1526 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1528 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
1530 my $optdata = parse_lxc_network
($conf->{$opt});
1534 my $cmdargs = shift;
1535 PVE
::Tools
::run_command
(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
1537 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
1539 my $change_ip_config = sub {
1540 my ($ipversion) = @_;
1542 my $family_opt = "-$ipversion";
1543 my $suffix = $ipversion == 4 ?
'' : $ipversion;
1544 my $gw= "gw$suffix";
1545 my $ip= "ip$suffix";
1547 my $newip = $newnet->{$ip};
1548 my $newgw = $newnet->{$gw};
1549 my $oldip = $optdata->{$ip};
1551 my $change_ip = &$safe_string_ne($oldip, $newip);
1552 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
1554 return if !$change_ip && !$change_gw;
1556 # step 1: add new IP, if this fails we cancel
1557 my $is_real_ip = ($newip && $newip !~ /^(?:auto|dhcp|manual)$/);
1558 if ($change_ip && $is_real_ip) {
1559 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
1566 # step 2: replace gateway
1567 # If this fails we delete the added IP and cancel.
1568 # If it succeeds we save the config and delete the old IP, ignoring
1569 # errors. The config is then saved.
1570 # Note: 'ip route replace' can add
1574 if ($is_real_ip && !PVE
::Network
::is_ip_in_cidr
($newgw, $newip, $ipversion)) {
1575 &$ipcmd($family_opt, 'route', 'add', $newgw, 'dev', $eth);
1577 &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw);
1581 # the route was not replaced, the old IP is still available
1582 # rollback (delete new IP) and cancel
1584 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
1585 warn $@ if $@; # no need to die here
1590 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
1591 # if the route was not deleted, the guest might have deleted it manually
1597 # from this point on we save the configuration
1598 # step 3: delete old IP ignoring errors
1599 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
1600 # We need to enable promote_secondaries, otherwise our newly added
1601 # address will be removed along with the old one.
1604 if ($ipversion == 4) {
1605 &$nscmd({ outfunc
=> sub { $promote = int(shift) } },
1606 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1607 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1609 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1611 warn $@ if $@; # no need to die here
1613 if ($ipversion == 4) {
1614 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1618 foreach my $property ($ip, $gw) {
1619 if ($newnet->{$property}) {
1620 $optdata->{$property} = $newnet->{$property};
1622 delete $optdata->{$property};
1625 $conf->{$opt} = print_lxc_network
($optdata);
1626 write_config
($vmid, $conf);
1627 $lxc_setup->setup_network($conf);
1630 &$change_ip_config(4);
1631 &$change_ip_config(6);
1635 # Internal snapshots
1637 # NOTE: Snapshot create/delete involves several non-atomic
1638 # action, and can take a long time.
1639 # So we try to avoid locking the file and use 'lock' variable
1640 # inside the config file instead.
1642 my $snapshot_copy_config = sub {
1643 my ($source, $dest) = @_;
1645 foreach my $k (keys %$source) {
1646 next if $k eq 'snapshots';
1647 next if $k eq 'snapstate';
1648 next if $k eq 'snaptime';
1649 next if $k eq 'vmstate';
1650 next if $k eq 'lock';
1651 next if $k eq 'digest';
1652 next if $k eq 'description';
1654 $dest->{$k} = $source->{$k};
1658 my $snapshot_prepare = sub {
1659 my ($vmid, $snapname, $comment) = @_;
1663 my $updatefn = sub {
1665 my $conf = load_config
($vmid);
1667 die "you can't take a snapshot if it's a template\n"
1668 if is_template
($conf);
1672 $conf->{lock} = 'snapshot';
1674 die "snapshot name '$snapname' already used\n"
1675 if defined($conf->{snapshots
}->{$snapname});
1677 my $storecfg = PVE
::Storage
::config
();
1678 die "snapshot feature is not available\n" if !has_feature
('snapshot', $conf, $storecfg);
1680 $snap = $conf->{snapshots
}->{$snapname} = {};
1682 &$snapshot_copy_config($conf, $snap);
1684 $snap->{'snapstate'} = "prepare";
1685 $snap->{'snaptime'} = time();
1686 $snap->{'description'} = $comment if $comment;
1687 $conf->{snapshots
}->{$snapname} = $snap;
1689 write_config
($vmid, $conf);
1692 lock_container
($vmid, 10, $updatefn);
1697 my $snapshot_commit = sub {
1698 my ($vmid, $snapname) = @_;
1700 my $updatefn = sub {
1702 my $conf = load_config
($vmid);
1704 die "missing snapshot lock\n"
1705 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
1707 die "snapshot '$snapname' does not exist\n"
1708 if !defined($conf->{snapshots
}->{$snapname});
1710 die "wrong snapshot state\n"
1711 if !($conf->{snapshots
}->{$snapname}->{'snapstate'} &&
1712 $conf->{snapshots
}->{$snapname}->{'snapstate'} eq "prepare");
1714 delete $conf->{snapshots
}->{$snapname}->{'snapstate'};
1715 delete $conf->{lock};
1716 $conf->{parent
} = $snapname;
1718 write_config
($vmid, $conf);
1721 lock_container
($vmid, 10 ,$updatefn);
1725 my ($feature, $conf, $storecfg, $snapname) = @_;
1729 foreach_mountpoint
($conf, sub {
1730 my ($ms, $mountpoint) = @_;
1732 return if $err; # skip further test
1734 $err = 1 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature, $mountpoint->{volume
}, $snapname);
1736 # TODO: implement support for mountpoints
1737 die "unable to handle mountpoint '$ms' - feature not implemented\n"
1741 return $err ?
0 : 1;
1744 sub snapshot_create
{
1745 my ($vmid, $snapname, $comment) = @_;
1747 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1749 my $conf = load_config
($vmid);
1751 my $running = check_running
($vmid);
1754 PVE
::Tools
::run_command
(['/usr/bin/lxc-freeze', '-n', $vmid]);
1755 PVE
::Tools
::run_command
(['/bin/sync']);
1758 my $storecfg = PVE
::Storage
::config
();
1759 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1760 my $volid = $rootinfo->{volume
};
1763 PVE
::Tools
::run_command
(['/usr/bin/lxc-unfreeze', '-n', $vmid]);
1766 PVE
::Storage
::volume_snapshot
($storecfg, $volid, $snapname);
1767 &$snapshot_commit($vmid, $snapname);
1770 snapshot_delete
($vmid, $snapname, 1);
1775 sub snapshot_delete
{
1776 my ($vmid, $snapname, $force) = @_;
1782 my $updatefn = sub {
1784 $conf = load_config
($vmid);
1786 die "you can't delete a snapshot if vm is a template\n"
1787 if is_template
($conf);
1789 $snap = $conf->{snapshots
}->{$snapname};
1793 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1795 $snap->{snapstate
} = 'delete';
1797 write_config
($vmid, $conf);
1800 lock_container
($vmid, 10, $updatefn);
1802 my $storecfg = PVE
::Storage
::config
();
1804 my $del_snap = sub {
1808 if ($conf->{parent
} eq $snapname) {
1809 if ($conf->{snapshots
}->{$snapname}->{snapname
}) {
1810 $conf->{parent
} = $conf->{snapshots
}->{$snapname}->{parent
};
1812 delete $conf->{parent
};
1816 delete $conf->{snapshots
}->{$snapname};
1818 write_config
($vmid, $conf);
1821 my $rootfs = $conf->{snapshots
}->{$snapname}->{rootfs
};
1822 my $rootinfo = parse_ct_mountpoint
($rootfs);
1823 my $volid = $rootinfo->{volume
};
1826 PVE
::Storage
::volume_snapshot_delete
($storecfg, $volid, $snapname);
1830 if(!$err || ($err && $force)) {
1831 lock_container
($vmid, 10, $del_snap);
1833 die "Can't delete snapshot: $vmid $snapname $err\n";
1838 sub snapshot_rollback
{
1839 my ($vmid, $snapname) = @_;
1841 my $storecfg = PVE
::Storage
::config
();
1843 my $conf = load_config
($vmid);
1845 die "you can't rollback if vm is a template\n" if is_template
($conf);
1847 my $snap = $conf->{snapshots
}->{$snapname};
1849 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1851 my $rootfs = $snap->{rootfs
};
1852 my $rootinfo = parse_ct_mountpoint
($rootfs);
1853 my $volid = $rootinfo->{volume
};
1855 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $volid, $snapname);
1857 my $updatefn = sub {
1859 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1860 if $snap->{snapstate
};
1864 system("lxc-stop -n $vmid --kill") if check_running
($vmid);
1866 die "unable to rollback vm $vmid: vm is running\n"
1867 if check_running
($vmid);
1869 $conf->{lock} = 'rollback';
1873 # copy snapshot config to current config
1875 my $tmp_conf = $conf;
1876 &$snapshot_copy_config($tmp_conf->{snapshots
}->{$snapname}, $conf);
1877 $conf->{snapshots
} = $tmp_conf->{snapshots
};
1878 delete $conf->{snaptime
};
1879 delete $conf->{snapname
};
1880 $conf->{parent
} = $snapname;
1882 write_config
($vmid, $conf);
1885 my $unlockfn = sub {
1886 delete $conf->{lock};
1887 write_config
($vmid, $conf);
1890 lock_container
($vmid, 10, $updatefn);
1892 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $volid, $snapname);
1894 lock_container
($vmid, 5, $unlockfn);
1897 sub template_create
{
1898 my ($vmid, $conf) = @_;
1900 my $storecfg = PVE
::Storage
::config
();
1902 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1903 my $volid = $rootinfo->{volume
};
1905 die "Template feature is not available for '$volid'\n"
1906 if !PVE
::Storage
::volume_has_feature
($storecfg, 'template', $volid);
1908 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
1910 my $template_volid = PVE
::Storage
::vdisk_create_base
($storecfg, $volid);
1911 $rootinfo->{volume
} = $template_volid;
1912 $conf->{rootfs
} = print_ct_mountpoint
($rootinfo, 1);
1914 write_config
($vmid, $conf);
1920 return 1 if defined $conf->{template
} && $conf->{template
} == 1;
1923 sub mountpoint_names
{
1926 my @names = ('rootfs');
1928 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1929 push @names, "mp$i";
1932 return $reverse ?
reverse @names : @names;
1935 # The container might have *different* symlinks than the host. realpath/abs_path
1936 # use the actual filesystem to resolve links.
1937 sub sanitize_mountpoint
{
1939 $mp = '/' . $mp; # we always start with a slash
1940 $mp =~ s
@/{2,}@/@g; # collapse sequences of slashes
1941 $mp =~ s
@/\./@@g; # collapse /./
1942 $mp =~ s
@/\.(/)?
$@$1@; # collapse a trailing /. or /./
1943 $mp =~ s
@(.*)/[^/]+/\.\./@$1/@g; # collapse /../ without regard for symlinks
1944 $mp =~ s
@/\.\
.(/)?
$@$1@; # collapse trailing /.. or /../ disregarding symlinks
1948 sub foreach_mountpoint_full
{
1949 my ($conf, $reverse, $func) = @_;
1951 foreach my $key (mountpoint_names
($reverse)) {
1952 my $value = $conf->{$key};
1953 next if !defined($value);
1954 my $mountpoint = parse_ct_mountpoint
($value, 1);
1955 next if !defined($mountpoint);
1957 # just to be sure: rootfs is /
1958 my $path = $key eq 'rootfs' ?
'/' : $mountpoint->{mp
};
1959 $mountpoint->{mp
} = sanitize_mountpoint
($path);
1961 $path = $mountpoint->{volume
};
1962 $mountpoint->{volume
} = sanitize_mountpoint
($path) if $path =~ m
|^/|;
1964 &$func($key, $mountpoint);
1968 sub foreach_mountpoint
{
1969 my ($conf, $func) = @_;
1971 foreach_mountpoint_full
($conf, 0, $func);
1974 sub foreach_mountpoint_reverse
{
1975 my ($conf, $func) = @_;
1977 foreach_mountpoint_full
($conf, 1, $func);
1980 sub check_ct_modify_config_perm
{
1981 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
1983 return 1 if $authuser ne 'root@pam';
1985 foreach my $opt (@$key_list) {
1987 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
1988 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
1989 } elsif ($opt eq 'rootfs' || $opt =~ /^mp\d+$/) {
1990 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
1991 } elsif ($opt eq 'memory' || $opt eq 'swap') {
1992 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
1993 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
1994 $opt eq 'searchdomain' || $opt eq 'hostname') {
1995 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
1997 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
2005 my ($vmid, $storage_cfg, $conf, $noerr) = @_;
2007 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2008 my $volid_list = get_vm_volumes
($conf);
2010 foreach_mountpoint_reverse
($conf, sub {
2011 my ($ms, $mountpoint) = @_;
2013 my $volid = $mountpoint->{volume
};
2014 my $mount = $mountpoint->{mp
};
2016 return if !$volid || !$mount;
2018 my $mount_path = "$rootdir/$mount";
2019 $mount_path =~ s!/+!/!g;
2021 return if !PVE
::ProcFSTools
::is_mounted
($mount_path);
2024 PVE
::Tools
::run_command
(['umount', '-d', $mount_path]);
2037 my ($vmid, $storage_cfg, $conf) = @_;
2039 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2040 File
::Path
::make_path
($rootdir);
2042 my $volid_list = get_vm_volumes
($conf);
2043 PVE
::Storage
::activate_volumes
($storage_cfg, $volid_list);
2046 foreach_mountpoint
($conf, sub {
2047 my ($ms, $mountpoint) = @_;
2049 my $volid = $mountpoint->{volume
};
2050 my $mount = $mountpoint->{mp
};
2052 return if !$volid || !$mount;
2054 my $image_path = PVE
::Storage
::path
($storage_cfg, $volid);
2055 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2056 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2058 die "unable to mount base volume - internal error" if $isBase;
2060 mountpoint_mount
($mountpoint, $rootdir, $storage_cfg);
2064 warn "mounting container failed - $err";
2065 umount_all
($vmid, $storage_cfg, $conf, 1);
2072 sub mountpoint_mount_path
{
2073 my ($mountpoint, $storage_cfg, $snapname) = @_;
2075 return mountpoint_mount
($mountpoint, undef, $storage_cfg, $snapname);
2078 my $check_mount_path = sub {
2080 $path = File
::Spec-
>canonpath($path);
2081 my $real = Cwd
::realpath
($path);
2082 if ($real ne $path) {
2083 die "mount path modified by symlink: $path != $real";
2087 # use $rootdir = undef to just return the corresponding mount path
2088 sub mountpoint_mount
{
2089 my ($mountpoint, $rootdir, $storage_cfg, $snapname) = @_;
2091 my $volid = $mountpoint->{volume
};
2092 my $mount = $mountpoint->{mp
};
2094 return if !$volid || !$mount;
2098 if (defined($rootdir)) {
2099 $rootdir =~ s!/+$!!;
2100 $mount_path = "$rootdir/$mount";
2101 $mount_path =~ s!/+!/!g;
2102 &$check_mount_path($mount_path);
2103 File
::Path
::mkpath
($mount_path);
2106 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2108 die "unknown snapshot path for '$volid'" if !$storage && defined($snapname);
2112 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
2113 my $path = PVE
::Storage
::path
($storage_cfg, $volid, $snapname);
2115 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2116 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2118 $format = 'iso' if $vtype eq 'iso'; # allow to handle iso files
2120 if ($format eq 'subvol') {
2123 if ($scfg->{type
} eq 'zfspool') {
2124 my $path_arg = $path;
2125 $path_arg =~ s!^/+!!;
2126 PVE
::Tools
::run_command
(['mount', '-o', 'ro', '-t', 'zfs', $path_arg, $mount_path]);
2128 die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
2131 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $path, $mount_path]);
2134 return wantarray ?
($path, 0) : $path;
2135 } elsif ($format eq 'raw' || $format eq 'iso') {
2136 my $use_loopdev = 0;
2138 if ($scfg->{path
}) {
2139 push @extra_opts, '-o', 'loop';
2141 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm' || $scfg->{type
} eq 'rbd') {
2144 die "unsupported storage type '$scfg->{type}'\n";
2147 if ($format eq 'iso') {
2148 PVE
::Tools
::run_command
(['mount', '-o', 'ro', @extra_opts, $path, $mount_path]);
2149 } elsif ($isBase || defined($snapname)) {
2150 PVE
::Tools
::run_command
(['mount', '-o', 'ro,noload', @extra_opts, $path, $mount_path]);
2152 PVE
::Tools
::run_command
(['mount', @extra_opts, $path, $mount_path]);
2155 return wantarray ?
($path, $use_loopdev) : $path;
2157 die "unsupported image format '$format'\n";
2159 } elsif ($volid =~ m
|^/dev/.+|) {
2160 PVE
::Tools
::run_command
(['mount', $volid, $mount_path]) if $mount_path;
2161 return wantarray ?
($volid, 0) : $volid;
2162 } elsif ($volid !~ m
|^/dev/.+| && $volid =~ m
|^/.+| && -d
$volid) {
2163 &$check_mount_path($volid);
2164 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $volid, $mount_path]) if $mount_path;
2165 return wantarray ?
($volid, 0) : $volid;
2168 die "unsupported storage";
2171 sub get_vm_volumes
{
2172 my ($conf, $excludes) = @_;
2176 foreach_mountpoint
($conf, sub {
2177 my ($ms, $mountpoint) = @_;
2179 return if $excludes && $ms eq $excludes;
2181 my $volid = $mountpoint->{volume
};
2183 return if !$volid || $volid =~ m
|^/|;
2185 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2188 push @$vollist, $volid;
2197 PVE
::Tools
::run_command
(['mkfs.ext4', '-O', 'mmp', $dev]);
2201 my ($storage_cfg, $volid) = @_;
2203 if ($volid =~ m!^/dev/.+!) {
2208 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2210 die "cannot format volume '$volid' with no storage\n" if !$storage;
2212 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
2214 my $path = PVE
::Storage
::path
($storage_cfg, $volid);
2216 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2217 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2219 die "cannot format volume '$volid' (format == $format)\n"
2220 if $format ne 'raw';
2226 my ($storecfg, $vollist) = @_;
2228 foreach my $volid (@$vollist) {
2229 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2235 my ($storecfg, $vmid, $settings, $conf) = @_;
2240 foreach_mountpoint
($settings, sub {
2241 my ($ms, $mountpoint) = @_;
2243 my $volid = $mountpoint->{volume
};
2244 my $mp = $mountpoint->{mp
};
2246 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2248 return if !$storage;
2250 if ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/) {
2251 my ($storeid, $size_gb) = ($1, $2);
2253 my $size_kb = int(${size_gb
}*1024) * 1024;
2255 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2256 # fixme: use better naming ct-$vmid-disk-X.raw?
2258 if ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs') {
2260 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw',
2262 format_disk
($storecfg, $volid);
2264 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2267 } elsif ($scfg->{type
} eq 'zfspool') {
2269 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2271 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm') {
2273 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2274 format_disk
($storecfg, $volid);
2276 } elsif ($scfg->{type
} eq 'rbd') {
2278 die "krbd option must be enabled on storage type '$scfg->{type}'\n" if !$scfg->{krbd
};
2279 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2280 format_disk
($storecfg, $volid);
2282 die "unable to create containers on storage type '$scfg->{type}'\n";
2284 push @$vollist, $volid;
2285 my $new_mountpoint = { volume
=> $volid, size
=> $size_kb*1024, mp
=> $mp };
2286 $conf->{$ms} = print_ct_mountpoint
($new_mountpoint, $ms eq 'rootfs');
2288 # use specified/existing volid
2292 # free allocated images on error
2294 destroy_disks
($storecfg, $vollist);
2300 # bash completion helper
2302 sub complete_os_templates
{
2303 my ($cmdname, $pname, $cvalue) = @_;
2305 my $cfg = PVE
::Storage
::config
();
2309 if ($cvalue =~ m/^([^:]+):/) {
2313 my $vtype = $cmdname eq 'restore' ?
'backup' : 'vztmpl';
2314 my $data = PVE
::Storage
::template_list
($cfg, $storeid, $vtype);
2317 foreach my $id (keys %$data) {
2318 foreach my $item (@{$data->{$id}}) {
2319 push @$res, $item->{volid
} if defined($item->{volid
});
2326 my $complete_ctid_full = sub {
2329 my $idlist = vmstatus
();
2331 my $active_hash = list_active_containers
();
2335 foreach my $id (keys %$idlist) {
2336 my $d = $idlist->{$id};
2337 if (defined($running)) {
2338 next if $d->{template
};
2339 next if $running && !$active_hash->{$id};
2340 next if !$running && $active_hash->{$id};
2349 return &$complete_ctid_full();
2352 sub complete_ctid_stopped
{
2353 return &$complete_ctid_full(0);
2356 sub complete_ctid_running
{
2357 return &$complete_ctid_full(1);