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) = @_;
742 return PVE
::Tools
::df
("/proc/$pid/root/", 1);
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];
777 foreach my $vmid (keys %$list) {
778 my $d = $list->{$vmid};
780 eval { $d->{pid
} = find_lxc_pid
($vmid) if defined($active_hash->{$vmid}); };
781 warn $@ if $@; # ignore errors (consider them stopped)
783 $d->{status
} = $d->{pid
} ?
'running' : 'stopped';
785 my $cfspath = cfs_config_path
($vmid);
786 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath) || {};
788 $d->{name
} = $conf->{'hostname'} || "CT$vmid";
789 $d->{name
} =~ s/[\s]//g;
791 $d->{cpus
} = $conf->{cpulimit
} || $cpucount;
794 my $res = get_container_disk_usage
($vmid, $d->{pid
});
795 $d->{disk
} = $res->{used
};
796 $d->{maxdisk
} = $res->{total
};
799 # use 4GB by default ??
800 if (my $rootfs = $conf->{rootfs
}) {
801 my $rootinfo = parse_ct_mountpoint
($rootfs);
802 $d->{maxdisk
} = int(($rootinfo->{size
} || 4)*1024*1024)*1024;
804 $d->{maxdisk
} = 4*1024*1024*1024;
810 $d->{maxmem
} = ($conf->{memory
}||512)*1024*1024;
811 $d->{maxswap
} = ($conf->{swap
}//0)*1024*1024;
822 $d->{template
} = is_template
($conf);
825 foreach my $vmid (keys %$list) {
826 my $d = $list->{$vmid};
829 next if !$pid; # skip stopped CTs
831 my $ctime = (stat("/proc/$pid"))[10]; # 10 = ctime
832 $d->{uptime
} = time - $ctime; # the method lxcfs uses
834 $d->{mem
} = read_cgroup_value
('memory', $vmid, 'memory.usage_in_bytes');
835 $d->{swap
} = read_cgroup_value
('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem
};
837 my $blkio_bytes = read_cgroup_value
('blkio', $vmid, 'blkio.throttle.io_service_bytes', 1);
838 my @bytes = split(/\n/, $blkio_bytes);
839 foreach my $byte (@bytes) {
840 if (my ($key, $value) = $byte =~ /(Read|Write)\s+(\d+)/) {
841 $d->{diskread
} = $2 if $key eq 'Read';
842 $d->{diskwrite
} = $2 if $key eq 'Write';
846 my $pstat = &$parse_cpuacct_stat($vmid);
848 my $used = $pstat->{utime} + $pstat->{stime
};
850 my $old = $last_proc_vmid_stat->{$vmid};
852 $last_proc_vmid_stat->{$vmid} = {
860 my $dtime = ($cdtime - $old->{time}) * $cpucount * $cpuinfo->{user_hz
};
863 my $dutime = $used - $old->{used
};
865 $d->{cpu
} = (($dutime/$dtime)* $cpucount) / $d->{cpus
};
866 $last_proc_vmid_stat->{$vmid} = {
872 $d->{cpu
} = $old->{cpu
};
876 my $netdev = PVE
::ProcFSTools
::read_proc_net_dev
();
878 foreach my $dev (keys %$netdev) {
879 next if $dev !~ m/^veth([1-9]\d*)i/;
881 my $d = $list->{$vmid};
885 $d->{netout
} += $netdev->{$dev}->{receive
};
886 $d->{netin
} += $netdev->{$dev}->{transmit
};
893 sub parse_ct_mountpoint
{
894 my ($data, $noerr) = @_;
899 eval { $res = PVE
::JSONSchema
::parse_property_string
($mp_desc, $data) };
901 return undef if $noerr;
905 if (defined(my $size = $res->{size
})) {
906 $size = PVE
::JSONSchema
::parse_size
($size);
907 if (!defined($size)) {
908 return undef if $noerr;
909 die "invalid size: $size\n";
911 $res->{size
} = $size;
917 sub print_ct_mountpoint
{
918 my ($info, $nomp) = @_;
919 my $skip = $nomp ?
['mp'] : [];
920 return PVE
::JSONSchema
::print_property_string
($info, $mp_desc, $skip);
923 sub print_lxc_network
{
925 return PVE
::JSONSchema
::print_property_string
($net, $netconf_desc);
928 sub parse_lxc_network
{
933 return $res if !$data;
935 $res = PVE
::JSONSchema
::parse_property_string
($netconf_desc, $data);
937 $res->{type
} = 'veth';
938 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
() if !$res->{hwaddr
};
943 sub read_cgroup_value
{
944 my ($group, $vmid, $name, $full) = @_;
946 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
948 return PVE
::Tools
::file_get_contents
($path) if $full;
950 return PVE
::Tools
::file_read_firstline
($path);
953 sub write_cgroup_value
{
954 my ($group, $vmid, $name, $value) = @_;
956 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
957 PVE
::ProcFSTools
::write_proc_entry
($path, $value) if -e
$path;
961 sub find_lxc_console_pids
{
965 PVE
::Tools
::dir_glob_foreach
('/proc', '\d+', sub {
968 my $cmdline = PVE
::Tools
::file_read_firstline
("/proc/$pid/cmdline");
971 my @args = split(/\0/, $cmdline);
973 # serach for lxc-console -n <vmid>
974 return if scalar(@args) != 3;
975 return if $args[1] ne '-n';
976 return if $args[2] !~ m/^\d+$/;
977 return if $args[0] !~ m
|^(/usr/bin
/)?lxc-console
$|;
981 push @{$res->{$vmid}}, $pid;
993 $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
995 PVE
::Tools
::run_command
(['lxc-info', '-n', $vmid, '-p'], outfunc
=> $parser);
997 die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
1002 # Note: we cannot use Net:IP, because that only allows strict
1004 sub parse_ipv4_cidr
{
1005 my ($cidr, $noerr) = @_;
1007 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 <= 32)) {
1008 return { address
=> $1, netmask
=> $PVE::Network
::ipv4_reverse_mask-
>[$2] };
1011 return undef if $noerr;
1013 die "unable to parse ipv4 address/mask\n";
1019 die "VM is locked ($conf->{'lock'})\n" if $conf->{'lock'};
1022 sub check_protection
{
1023 my ($vm_conf, $err_msg) = @_;
1025 if ($vm_conf->{protection
}) {
1026 die "$err_msg - protection mode enabled\n";
1030 sub update_lxc_config
{
1031 my ($storage_cfg, $vmid, $conf) = @_;
1033 my $dir = "/var/lib/lxc/$vmid";
1035 if ($conf->{template
}) {
1037 unlink "$dir/config";
1044 die "missing 'arch' - internal error" if !$conf->{arch
};
1045 $raw .= "lxc.arch = $conf->{arch}\n";
1047 my $ostype = $conf->{ostype
} || die "missing 'ostype' - internal error";
1048 if ($ostype =~ /^(?:debian | ubuntu | centos | archlinux)$/x) {
1049 $raw .= "lxc.include = /usr/share/lxc/config/$ostype.common.conf\n";
1054 if (!has_dev_console
($conf)) {
1055 $raw .= "lxc.console = none\n";
1056 $raw .= "lxc.cgroup.devices.deny = c 5:1 rwm\n";
1059 my $ttycount = get_tty_count
($conf);
1060 $raw .= "lxc.tty = $ttycount\n";
1062 # some init scripts expects a linux terminal (turnkey).
1063 $raw .= "lxc.environment = TERM=linux\n";
1065 my $utsname = $conf->{hostname
} || "CT$vmid";
1066 $raw .= "lxc.utsname = $utsname\n";
1068 my $memory = $conf->{memory
} || 512;
1069 my $swap = $conf->{swap
} // 0;
1071 my $lxcmem = int($memory*1024*1024);
1072 $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n";
1074 my $lxcswap = int(($memory + $swap)*1024*1024);
1075 $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n";
1077 if (my $cpulimit = $conf->{cpulimit
}) {
1078 $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n";
1079 my $value = int(100000*$cpulimit);
1080 $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n";
1083 my $shares = $conf->{cpuunits
} || 1024;
1084 $raw .= "lxc.cgroup.cpu.shares = $shares\n";
1086 my $mountpoint = parse_ct_mountpoint
($conf->{rootfs
});
1087 $mountpoint->{mp
} = '/';
1089 my ($path, $use_loopdev) = mountpoint_mount_path
($mountpoint, $storage_cfg);
1090 $path = "loop:$path" if $use_loopdev;
1092 $raw .= "lxc.rootfs = $path\n";
1095 foreach my $k (keys %$conf) {
1096 next if $k !~ m/^net(\d+)$/;
1098 my $d = parse_lxc_network
($conf->{$k});
1100 $raw .= "lxc.network.type = veth\n";
1101 $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
1102 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr
});
1103 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name
});
1104 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu
});
1107 if (my $lxcconf = $conf->{lxc
}) {
1108 foreach my $entry (@$lxcconf) {
1109 my ($k, $v) = @$entry;
1110 $netcount++ if $k eq 'lxc.network.type';
1111 $raw .= "$k = $v\n";
1115 $raw .= "lxc.network.type = empty\n" if !$netcount;
1117 File
::Path
::mkpath
("$dir/rootfs");
1119 PVE
::Tools
::file_set_contents
("$dir/config", $raw);
1122 # verify and cleanup nameserver list (replace \0 with ' ')
1123 sub verify_nameserver_list
{
1124 my ($nameserver_list) = @_;
1127 foreach my $server (PVE
::Tools
::split_list
($nameserver_list)) {
1128 PVE
::JSONSchema
::pve_verify_ip
($server);
1129 push @list, $server;
1132 return join(' ', @list);
1135 sub verify_searchdomain_list
{
1136 my ($searchdomain_list) = @_;
1139 foreach my $server (PVE
::Tools
::split_list
($searchdomain_list)) {
1140 # todo: should we add checks for valid dns domains?
1141 push @list, $server;
1144 return join(' ', @list);
1147 sub add_unused_volume
{
1148 my ($config, $volid) = @_;
1150 # skip bind mounts and block devices
1151 return if $volid =~ m
|^/|;
1154 for (my $ind = $MAX_UNUSED_DISKS - 1; $ind >= 0; $ind--) {
1155 my $test = "unused$ind";
1156 if (my $vid = $config->{$test}) {
1157 return if $vid eq $volid; # do not add duplicates
1163 die "To many unused volume - please delete them first.\n" if !$key;
1165 $config->{$key} = $volid;
1170 sub update_pct_config
{
1171 my ($vmid, $conf, $running, $param, $delete) = @_;
1176 my @deleted_volumes;
1180 my $pid = find_lxc_pid
($vmid);
1181 $rootdir = "/proc/$pid/root";
1184 my $hotplug_error = sub {
1186 push @nohotplug, @_;
1193 if (defined($delete)) {
1194 foreach my $opt (@$delete) {
1195 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
1196 die "unable to delete required option '$opt'\n";
1197 } elsif ($opt eq 'swap') {
1198 delete $conf->{$opt};
1199 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
1200 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
1201 delete $conf->{$opt};
1202 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
1203 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1204 next if $hotplug_error->($opt);
1205 delete $conf->{$opt};
1206 } elsif ($opt =~ m/^net(\d)$/) {
1207 delete $conf->{$opt};
1210 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
1211 } elsif ($opt eq 'protection') {
1212 delete $conf->{$opt};
1213 } elsif ($opt =~ m/^unused(\d+)$/) {
1214 next if $hotplug_error->($opt);
1215 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1216 push @deleted_volumes, $conf->{$opt};
1217 delete $conf->{$opt};
1218 } elsif ($opt =~ m/^mp(\d+)$/) {
1219 next if $hotplug_error->($opt);
1220 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1221 my $mountpoint = parse_ct_mountpoint
($conf->{$opt});
1222 add_unused_volume
($conf, $mountpoint->{volume
});
1223 delete $conf->{$opt};
1227 write_config
($vmid, $conf) if $running;
1231 # There's no separate swap size to configure, there's memory and "total"
1232 # memory (iow. memory+swap). This means we have to change them together.
1233 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
1234 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
1235 if (defined($wanted_memory) || defined($wanted_swap)) {
1237 $wanted_memory //= ($conf->{memory
} || 512);
1238 $wanted_swap //= ($conf->{swap
} || 0);
1240 my $total = $wanted_memory + $wanted_swap;
1242 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1243 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1245 $conf->{memory
} = $wanted_memory;
1246 $conf->{swap
} = $wanted_swap;
1248 write_config
($vmid, $conf) if $running;
1251 foreach my $opt (keys %$param) {
1252 my $value = $param->{$opt};
1253 if ($opt eq 'hostname') {
1254 $conf->{$opt} = $value;
1255 } elsif ($opt eq 'onboot') {
1256 $conf->{$opt} = $value ?
1 : 0;
1257 } elsif ($opt eq 'startup') {
1258 $conf->{$opt} = $value;
1259 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1260 next if $hotplug_error->($opt);
1261 $conf->{$opt} = $value;
1262 } elsif ($opt eq 'nameserver') {
1263 next if $hotplug_error->($opt);
1264 my $list = verify_nameserver_list
($value);
1265 $conf->{$opt} = $list;
1266 } elsif ($opt eq 'searchdomain') {
1267 next if $hotplug_error->($opt);
1268 my $list = verify_searchdomain_list
($value);
1269 $conf->{$opt} = $list;
1270 } elsif ($opt eq 'cpulimit') {
1271 next if $hotplug_error->($opt); # FIXME: hotplug
1272 $conf->{$opt} = $value;
1273 } elsif ($opt eq 'cpuunits') {
1274 $conf->{$opt} = $value;
1275 write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
1276 } elsif ($opt eq 'description') {
1277 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
1278 } elsif ($opt =~ m/^net(\d+)$/) {
1280 my $net = parse_lxc_network
($value);
1282 $conf->{$opt} = print_lxc_network
($net);
1284 update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1286 } elsif ($opt eq 'protection') {
1287 $conf->{$opt} = $value ?
1 : 0;
1288 } elsif ($opt =~ m/^mp(\d+)$/) {
1289 next if $hotplug_error->($opt);
1290 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1291 $conf->{$opt} = $value;
1293 } elsif ($opt eq 'rootfs') {
1294 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1295 die "implement me: $opt";
1297 die "implement me: $opt";
1299 write_config
($vmid, $conf) if $running;
1302 if (@deleted_volumes) {
1303 my $storage_cfg = PVE
::Storage
::config
();
1304 foreach my $volume (@deleted_volumes) {
1305 delete_mountpoint_volume
($storage_cfg, $vmid, $volume);
1310 my $storage_cfg = PVE
::Storage
::config
();
1311 create_disks
($storage_cfg, $vmid, $conf, $conf);
1314 # This should be the last thing we do here
1315 if ($running && scalar(@nohotplug)) {
1316 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1320 sub has_dev_console
{
1323 return !(defined($conf->{console
}) && !$conf->{console
});
1329 return $conf->{tty
} // $confdesc->{tty
}->{default};
1335 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1338 sub get_console_command
{
1339 my ($vmid, $conf) = @_;
1341 my $cmode = get_cmode
($conf);
1343 if ($cmode eq 'console') {
1344 return ['lxc-console', '-n', $vmid, '-t', 0];
1345 } elsif ($cmode eq 'tty') {
1346 return ['lxc-console', '-n', $vmid];
1347 } elsif ($cmode eq 'shell') {
1348 return ['lxc-attach', '--clear-env', '-n', $vmid];
1350 die "internal error";
1354 sub get_primary_ips
{
1357 # return data from net0
1359 return undef if !defined($conf->{net0
});
1360 my $net = parse_lxc_network
($conf->{net0
});
1362 my $ipv4 = $net->{ip
};
1364 if ($ipv4 =~ /^(dhcp|manual)$/) {
1370 my $ipv6 = $net->{ip6
};
1372 if ($ipv6 =~ /^(auto|dhcp|manual)$/) {
1379 return ($ipv4, $ipv6);
1382 sub delete_mountpoint_volume
{
1383 my ($storage_cfg, $vmid, $volume) = @_;
1385 # skip bind mounts and block devices
1386 if ($volume =~ m
|^/|) {
1390 my ($vtype, $name, $owner) = PVE
::Storage
::parse_volname
($storage_cfg, $volume);
1391 PVE
::Storage
::vdisk_free
($storage_cfg, $volume) if $vmid == $owner;
1394 sub destroy_lxc_container
{
1395 my ($storage_cfg, $vmid, $conf) = @_;
1397 foreach_mountpoint
($conf, sub {
1398 my ($ms, $mountpoint) = @_;
1399 delete_mountpoint_volume
($storage_cfg, $vmid, $mountpoint->{volume
});
1402 rmdir "/var/lib/lxc/$vmid/rootfs";
1403 unlink "/var/lib/lxc/$vmid/config";
1404 rmdir "/var/lib/lxc/$vmid";
1405 destroy_config
($vmid);
1407 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1408 #PVE::Tools::run_command($cmd);
1411 sub vm_stop_cleanup
{
1412 my ($storage_cfg, $vmid, $conf, $keepActive) = @_;
1417 my $vollist = get_vm_volumes
($conf);
1418 PVE
::Storage
::deactivate_volumes
($storage_cfg, $vollist);
1421 warn $@ if $@; # avoid errors - just warn
1424 my $safe_num_ne = sub {
1427 return 0 if !defined($a) && !defined($b);
1428 return 1 if !defined($a);
1429 return 1 if !defined($b);
1434 my $safe_string_ne = sub {
1437 return 0 if !defined($a) && !defined($b);
1438 return 1 if !defined($a);
1439 return 1 if !defined($b);
1445 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1447 if ($newnet->{type
} ne 'veth') {
1448 # for when there are physical interfaces
1449 die "cannot update interface of type $newnet->{type}";
1452 my $veth = "veth${vmid}i${netid}";
1453 my $eth = $newnet->{name
};
1455 if (my $oldnetcfg = $conf->{$opt}) {
1456 my $oldnet = parse_lxc_network
($oldnetcfg);
1458 if (&$safe_string_ne($oldnet->{hwaddr
}, $newnet->{hwaddr
}) ||
1459 &$safe_string_ne($oldnet->{name
}, $newnet->{name
})) {
1461 PVE
::Network
::veth_delete
($veth);
1462 delete $conf->{$opt};
1463 write_config
($vmid, $conf);
1465 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1467 } elsif (&$safe_string_ne($oldnet->{bridge
}, $newnet->{bridge
}) ||
1468 &$safe_num_ne($oldnet->{tag
}, $newnet->{tag
}) ||
1469 &$safe_num_ne($oldnet->{firewall
}, $newnet->{firewall
})) {
1471 if ($oldnet->{bridge
}) {
1472 PVE
::Network
::tap_unplug
($veth);
1473 foreach (qw(bridge tag firewall)) {
1474 delete $oldnet->{$_};
1476 $conf->{$opt} = print_lxc_network
($oldnet);
1477 write_config
($vmid, $conf);
1480 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1481 foreach (qw(bridge tag firewall)) {
1482 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1484 $conf->{$opt} = print_lxc_network
($oldnet);
1485 write_config
($vmid, $conf);
1488 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1491 update_ipconfig
($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1495 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
1497 my $veth = "veth${vmid}i${netid}";
1498 my $vethpeer = $veth . "p";
1499 my $eth = $newnet->{name
};
1501 PVE
::Network
::veth_create
($veth, $vethpeer, $newnet->{bridge
}, $newnet->{hwaddr
});
1502 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1504 # attach peer in container
1505 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1506 PVE
::Tools
::run_command
($cmd);
1508 # link up peer in container
1509 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1510 PVE
::Tools
::run_command
($cmd);
1512 my $done = { type
=> 'veth' };
1513 foreach (qw(bridge tag firewall hwaddr name)) {
1514 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1516 $conf->{$opt} = print_lxc_network
($done);
1518 write_config
($vmid, $conf);
1521 sub update_ipconfig
{
1522 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1524 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
1526 my $optdata = parse_lxc_network
($conf->{$opt});
1530 my $cmdargs = shift;
1531 PVE
::Tools
::run_command
(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
1533 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
1535 my $change_ip_config = sub {
1536 my ($ipversion) = @_;
1538 my $family_opt = "-$ipversion";
1539 my $suffix = $ipversion == 4 ?
'' : $ipversion;
1540 my $gw= "gw$suffix";
1541 my $ip= "ip$suffix";
1543 my $newip = $newnet->{$ip};
1544 my $newgw = $newnet->{$gw};
1545 my $oldip = $optdata->{$ip};
1547 my $change_ip = &$safe_string_ne($oldip, $newip);
1548 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
1550 return if !$change_ip && !$change_gw;
1552 # step 1: add new IP, if this fails we cancel
1553 my $is_real_ip = ($newip && $newip !~ /^(?:auto|dhcp|manual)$/);
1554 if ($change_ip && $is_real_ip) {
1555 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
1562 # step 2: replace gateway
1563 # If this fails we delete the added IP and cancel.
1564 # If it succeeds we save the config and delete the old IP, ignoring
1565 # errors. The config is then saved.
1566 # Note: 'ip route replace' can add
1570 if ($is_real_ip && !PVE
::Network
::is_ip_in_cidr
($newgw, $newip, $ipversion)) {
1571 &$ipcmd($family_opt, 'route', 'add', $newgw, 'dev', $eth);
1573 &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw);
1577 # the route was not replaced, the old IP is still available
1578 # rollback (delete new IP) and cancel
1580 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
1581 warn $@ if $@; # no need to die here
1586 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
1587 # if the route was not deleted, the guest might have deleted it manually
1593 # from this point on we save the configuration
1594 # step 3: delete old IP ignoring errors
1595 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
1596 # We need to enable promote_secondaries, otherwise our newly added
1597 # address will be removed along with the old one.
1600 if ($ipversion == 4) {
1601 &$nscmd({ outfunc
=> sub { $promote = int(shift) } },
1602 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1603 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1605 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1607 warn $@ if $@; # no need to die here
1609 if ($ipversion == 4) {
1610 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1614 foreach my $property ($ip, $gw) {
1615 if ($newnet->{$property}) {
1616 $optdata->{$property} = $newnet->{$property};
1618 delete $optdata->{$property};
1621 $conf->{$opt} = print_lxc_network
($optdata);
1622 write_config
($vmid, $conf);
1623 $lxc_setup->setup_network($conf);
1626 &$change_ip_config(4);
1627 &$change_ip_config(6);
1631 # Internal snapshots
1633 # NOTE: Snapshot create/delete involves several non-atomic
1634 # action, and can take a long time.
1635 # So we try to avoid locking the file and use 'lock' variable
1636 # inside the config file instead.
1638 my $snapshot_copy_config = sub {
1639 my ($source, $dest) = @_;
1641 foreach my $k (keys %$source) {
1642 next if $k eq 'snapshots';
1643 next if $k eq 'snapstate';
1644 next if $k eq 'snaptime';
1645 next if $k eq 'vmstate';
1646 next if $k eq 'lock';
1647 next if $k eq 'digest';
1648 next if $k eq 'description';
1650 $dest->{$k} = $source->{$k};
1654 my $snapshot_prepare = sub {
1655 my ($vmid, $snapname, $comment) = @_;
1659 my $updatefn = sub {
1661 my $conf = load_config
($vmid);
1663 die "you can't take a snapshot if it's a template\n"
1664 if is_template
($conf);
1668 $conf->{lock} = 'snapshot';
1670 die "snapshot name '$snapname' already used\n"
1671 if defined($conf->{snapshots
}->{$snapname});
1673 my $storecfg = PVE
::Storage
::config
();
1674 die "snapshot feature is not available\n" if !has_feature
('snapshot', $conf, $storecfg);
1676 $snap = $conf->{snapshots
}->{$snapname} = {};
1678 &$snapshot_copy_config($conf, $snap);
1680 $snap->{'snapstate'} = "prepare";
1681 $snap->{'snaptime'} = time();
1682 $snap->{'description'} = $comment if $comment;
1683 $conf->{snapshots
}->{$snapname} = $snap;
1685 write_config
($vmid, $conf);
1688 lock_container
($vmid, 10, $updatefn);
1693 my $snapshot_commit = sub {
1694 my ($vmid, $snapname) = @_;
1696 my $updatefn = sub {
1698 my $conf = load_config
($vmid);
1700 die "missing snapshot lock\n"
1701 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
1703 die "snapshot '$snapname' does not exist\n"
1704 if !defined($conf->{snapshots
}->{$snapname});
1706 die "wrong snapshot state\n"
1707 if !($conf->{snapshots
}->{$snapname}->{'snapstate'} &&
1708 $conf->{snapshots
}->{$snapname}->{'snapstate'} eq "prepare");
1710 delete $conf->{snapshots
}->{$snapname}->{'snapstate'};
1711 delete $conf->{lock};
1712 $conf->{parent
} = $snapname;
1714 write_config
($vmid, $conf);
1717 lock_container
($vmid, 10 ,$updatefn);
1721 my ($feature, $conf, $storecfg, $snapname) = @_;
1725 foreach_mountpoint
($conf, sub {
1726 my ($ms, $mountpoint) = @_;
1728 return if $err; # skip further test
1730 $err = 1 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature, $mountpoint->{volume
}, $snapname);
1732 # TODO: implement support for mountpoints
1733 die "unable to handle mountpoint '$ms' - feature not implemented\n"
1737 return $err ?
0 : 1;
1740 sub snapshot_create
{
1741 my ($vmid, $snapname, $comment) = @_;
1743 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1745 my $conf = load_config
($vmid);
1747 my $running = check_running
($vmid);
1750 PVE
::Tools
::run_command
(['/usr/bin/lxc-freeze', '-n', $vmid]);
1751 PVE
::Tools
::run_command
(['/bin/sync']);
1754 my $storecfg = PVE
::Storage
::config
();
1755 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1756 my $volid = $rootinfo->{volume
};
1759 PVE
::Tools
::run_command
(['/usr/bin/lxc-unfreeze', '-n', $vmid]);
1762 PVE
::Storage
::volume_snapshot
($storecfg, $volid, $snapname);
1763 &$snapshot_commit($vmid, $snapname);
1766 snapshot_delete
($vmid, $snapname, 1);
1771 sub snapshot_delete
{
1772 my ($vmid, $snapname, $force) = @_;
1778 my $updatefn = sub {
1780 $conf = load_config
($vmid);
1782 die "you can't delete a snapshot if vm is a template\n"
1783 if is_template
($conf);
1785 $snap = $conf->{snapshots
}->{$snapname};
1789 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1791 $snap->{snapstate
} = 'delete';
1793 write_config
($vmid, $conf);
1796 lock_container
($vmid, 10, $updatefn);
1798 my $storecfg = PVE
::Storage
::config
();
1800 my $del_snap = sub {
1804 if ($conf->{parent
} eq $snapname) {
1805 if ($conf->{snapshots
}->{$snapname}->{snapname
}) {
1806 $conf->{parent
} = $conf->{snapshots
}->{$snapname}->{parent
};
1808 delete $conf->{parent
};
1812 delete $conf->{snapshots
}->{$snapname};
1814 write_config
($vmid, $conf);
1817 my $rootfs = $conf->{snapshots
}->{$snapname}->{rootfs
};
1818 my $rootinfo = parse_ct_mountpoint
($rootfs);
1819 my $volid = $rootinfo->{volume
};
1822 PVE
::Storage
::volume_snapshot_delete
($storecfg, $volid, $snapname);
1826 if(!$err || ($err && $force)) {
1827 lock_container
($vmid, 10, $del_snap);
1829 die "Can't delete snapshot: $vmid $snapname $err\n";
1834 sub snapshot_rollback
{
1835 my ($vmid, $snapname) = @_;
1837 my $storecfg = PVE
::Storage
::config
();
1839 my $conf = load_config
($vmid);
1841 die "you can't rollback if vm is a template\n" if is_template
($conf);
1843 my $snap = $conf->{snapshots
}->{$snapname};
1845 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1847 my $rootfs = $snap->{rootfs
};
1848 my $rootinfo = parse_ct_mountpoint
($rootfs);
1849 my $volid = $rootinfo->{volume
};
1851 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $volid, $snapname);
1853 my $updatefn = sub {
1855 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1856 if $snap->{snapstate
};
1860 system("lxc-stop -n $vmid --kill") if check_running
($vmid);
1862 die "unable to rollback vm $vmid: vm is running\n"
1863 if check_running
($vmid);
1865 $conf->{lock} = 'rollback';
1869 # copy snapshot config to current config
1871 my $tmp_conf = $conf;
1872 &$snapshot_copy_config($tmp_conf->{snapshots
}->{$snapname}, $conf);
1873 $conf->{snapshots
} = $tmp_conf->{snapshots
};
1874 delete $conf->{snaptime
};
1875 delete $conf->{snapname
};
1876 $conf->{parent
} = $snapname;
1878 write_config
($vmid, $conf);
1881 my $unlockfn = sub {
1882 delete $conf->{lock};
1883 write_config
($vmid, $conf);
1886 lock_container
($vmid, 10, $updatefn);
1888 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $volid, $snapname);
1890 lock_container
($vmid, 5, $unlockfn);
1893 sub template_create
{
1894 my ($vmid, $conf) = @_;
1896 my $storecfg = PVE
::Storage
::config
();
1898 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1899 my $volid = $rootinfo->{volume
};
1901 die "Template feature is not available for '$volid'\n"
1902 if !PVE
::Storage
::volume_has_feature
($storecfg, 'template', $volid);
1904 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
1906 my $template_volid = PVE
::Storage
::vdisk_create_base
($storecfg, $volid);
1907 $rootinfo->{volume
} = $template_volid;
1908 $conf->{rootfs
} = print_ct_mountpoint
($rootinfo, 1);
1910 write_config
($vmid, $conf);
1916 return 1 if defined $conf->{template
} && $conf->{template
} == 1;
1919 sub mountpoint_names
{
1922 my @names = ('rootfs');
1924 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1925 push @names, "mp$i";
1928 return $reverse ?
reverse @names : @names;
1931 # The container might have *different* symlinks than the host. realpath/abs_path
1932 # use the actual filesystem to resolve links.
1933 sub sanitize_mountpoint
{
1935 $mp = '/' . $mp; # we always start with a slash
1936 $mp =~ s
@/{2,}@/@g; # collapse sequences of slashes
1937 $mp =~ s
@/\./@@g; # collapse /./
1938 $mp =~ s
@/\.(/)?
$@$1@; # collapse a trailing /. or /./
1939 $mp =~ s
@(.*)/[^/]+/\.\./@$1/@g; # collapse /../ without regard for symlinks
1940 $mp =~ s
@/\.\
.(/)?
$@$1@; # collapse trailing /.. or /../ disregarding symlinks
1944 sub foreach_mountpoint_full
{
1945 my ($conf, $reverse, $func) = @_;
1947 foreach my $key (mountpoint_names
($reverse)) {
1948 my $value = $conf->{$key};
1949 next if !defined($value);
1950 my $mountpoint = parse_ct_mountpoint
($value, 1);
1951 next if !defined($mountpoint);
1953 # just to be sure: rootfs is /
1954 my $path = $key eq 'rootfs' ?
'/' : $mountpoint->{mp
};
1955 $mountpoint->{mp
} = sanitize_mountpoint
($path);
1957 $path = $mountpoint->{volume
};
1958 $mountpoint->{volume
} = sanitize_mountpoint
($path) if $path =~ m
|^/|;
1960 &$func($key, $mountpoint);
1964 sub foreach_mountpoint
{
1965 my ($conf, $func) = @_;
1967 foreach_mountpoint_full
($conf, 0, $func);
1970 sub foreach_mountpoint_reverse
{
1971 my ($conf, $func) = @_;
1973 foreach_mountpoint_full
($conf, 1, $func);
1976 sub check_ct_modify_config_perm
{
1977 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
1979 return 1 if $authuser ne 'root@pam';
1981 foreach my $opt (@$key_list) {
1983 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
1984 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
1985 } elsif ($opt eq 'rootfs' || $opt =~ /^mp\d+$/) {
1986 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
1987 } elsif ($opt eq 'memory' || $opt eq 'swap') {
1988 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
1989 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
1990 $opt eq 'searchdomain' || $opt eq 'hostname') {
1991 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
1993 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
2001 my ($vmid, $storage_cfg, $conf, $noerr) = @_;
2003 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2004 my $volid_list = get_vm_volumes
($conf);
2006 foreach_mountpoint_reverse
($conf, sub {
2007 my ($ms, $mountpoint) = @_;
2009 my $volid = $mountpoint->{volume
};
2010 my $mount = $mountpoint->{mp
};
2012 return if !$volid || !$mount;
2014 my $mount_path = "$rootdir/$mount";
2015 $mount_path =~ s!/+!/!g;
2017 return if !PVE
::ProcFSTools
::is_mounted
($mount_path);
2020 PVE
::Tools
::run_command
(['umount', '-d', $mount_path]);
2033 my ($vmid, $storage_cfg, $conf) = @_;
2035 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2036 File
::Path
::make_path
($rootdir);
2038 my $volid_list = get_vm_volumes
($conf);
2039 PVE
::Storage
::activate_volumes
($storage_cfg, $volid_list);
2042 foreach_mountpoint
($conf, sub {
2043 my ($ms, $mountpoint) = @_;
2045 my $volid = $mountpoint->{volume
};
2046 my $mount = $mountpoint->{mp
};
2048 return if !$volid || !$mount;
2050 my $image_path = PVE
::Storage
::path
($storage_cfg, $volid);
2051 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2052 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2054 die "unable to mount base volume - internal error" if $isBase;
2056 mountpoint_mount
($mountpoint, $rootdir, $storage_cfg);
2060 warn "mounting container failed - $err";
2061 umount_all
($vmid, $storage_cfg, $conf, 1);
2068 sub mountpoint_mount_path
{
2069 my ($mountpoint, $storage_cfg, $snapname) = @_;
2071 return mountpoint_mount
($mountpoint, undef, $storage_cfg, $snapname);
2074 my $check_mount_path = sub {
2076 $path = File
::Spec-
>canonpath($path);
2077 my $real = Cwd
::realpath
($path);
2078 if ($real ne $path) {
2079 die "mount path modified by symlink: $path != $real";
2083 # use $rootdir = undef to just return the corresponding mount path
2084 sub mountpoint_mount
{
2085 my ($mountpoint, $rootdir, $storage_cfg, $snapname) = @_;
2087 my $volid = $mountpoint->{volume
};
2088 my $mount = $mountpoint->{mp
};
2090 return if !$volid || !$mount;
2094 if (defined($rootdir)) {
2095 $rootdir =~ s!/+$!!;
2096 $mount_path = "$rootdir/$mount";
2097 $mount_path =~ s!/+!/!g;
2098 &$check_mount_path($mount_path);
2099 File
::Path
::mkpath
($mount_path);
2102 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2104 die "unknown snapshot path for '$volid'" if !$storage && defined($snapname);
2108 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
2109 my $path = PVE
::Storage
::path
($storage_cfg, $volid, $snapname);
2111 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2112 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2114 $format = 'iso' if $vtype eq 'iso'; # allow to handle iso files
2116 if ($format eq 'subvol') {
2119 if ($scfg->{type
} eq 'zfspool') {
2120 my $path_arg = $path;
2121 $path_arg =~ s!^/+!!;
2122 PVE
::Tools
::run_command
(['mount', '-o', 'ro', '-t', 'zfs', $path_arg, $mount_path]);
2124 die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
2127 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $path, $mount_path]);
2130 return wantarray ?
($path, 0) : $path;
2131 } elsif ($format eq 'raw' || $format eq 'iso') {
2132 my $use_loopdev = 0;
2134 if ($scfg->{path
}) {
2135 push @extra_opts, '-o', 'loop';
2137 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm' || $scfg->{type
} eq 'rbd') {
2140 die "unsupported storage type '$scfg->{type}'\n";
2143 if ($format eq 'iso') {
2144 PVE
::Tools
::run_command
(['mount', '-o', 'ro', @extra_opts, $path, $mount_path]);
2145 } elsif ($isBase || defined($snapname)) {
2146 PVE
::Tools
::run_command
(['mount', '-o', 'ro,noload', @extra_opts, $path, $mount_path]);
2148 PVE
::Tools
::run_command
(['mount', @extra_opts, $path, $mount_path]);
2151 return wantarray ?
($path, $use_loopdev) : $path;
2153 die "unsupported image format '$format'\n";
2155 } elsif ($volid =~ m
|^/dev/.+|) {
2156 PVE
::Tools
::run_command
(['mount', $volid, $mount_path]) if $mount_path;
2157 return wantarray ?
($volid, 0) : $volid;
2158 } elsif ($volid !~ m
|^/dev/.+| && $volid =~ m
|^/.+| && -d
$volid) {
2159 &$check_mount_path($volid);
2160 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $volid, $mount_path]) if $mount_path;
2161 return wantarray ?
($volid, 0) : $volid;
2164 die "unsupported storage";
2167 sub get_vm_volumes
{
2168 my ($conf, $excludes) = @_;
2172 foreach_mountpoint
($conf, sub {
2173 my ($ms, $mountpoint) = @_;
2175 return if $excludes && $ms eq $excludes;
2177 my $volid = $mountpoint->{volume
};
2179 return if !$volid || $volid =~ m
|^/|;
2181 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2184 push @$vollist, $volid;
2193 PVE
::Tools
::run_command
(['mkfs.ext4', '-O', 'mmp', $dev]);
2197 my ($storage_cfg, $volid) = @_;
2199 if ($volid =~ m!^/dev/.+!) {
2204 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2206 die "cannot format volume '$volid' with no storage\n" if !$storage;
2208 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
2210 my $path = PVE
::Storage
::path
($storage_cfg, $volid);
2212 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2213 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2215 die "cannot format volume '$volid' (format == $format)\n"
2216 if $format ne 'raw';
2222 my ($storecfg, $vollist) = @_;
2224 foreach my $volid (@$vollist) {
2225 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2231 my ($storecfg, $vmid, $settings, $conf) = @_;
2236 foreach_mountpoint
($settings, sub {
2237 my ($ms, $mountpoint) = @_;
2239 my $volid = $mountpoint->{volume
};
2240 my $mp = $mountpoint->{mp
};
2242 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2244 return if !$storage;
2246 if ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/) {
2247 my ($storeid, $size_gb) = ($1, $2);
2249 my $size_kb = int(${size_gb
}*1024) * 1024;
2251 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2252 # fixme: use better naming ct-$vmid-disk-X.raw?
2254 if ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs') {
2256 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw',
2258 format_disk
($storecfg, $volid);
2260 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2263 } elsif ($scfg->{type
} eq 'zfspool') {
2265 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2267 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm') {
2269 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2270 format_disk
($storecfg, $volid);
2272 } elsif ($scfg->{type
} eq 'rbd') {
2274 die "krbd option must be enabled on storage type '$scfg->{type}'\n" if !$scfg->{krbd
};
2275 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2276 format_disk
($storecfg, $volid);
2278 die "unable to create containers on storage type '$scfg->{type}'\n";
2280 push @$vollist, $volid;
2281 my $new_mountpoint = { volume
=> $volid, size
=> $size_kb*1024, mp
=> $mp };
2282 $conf->{$ms} = print_ct_mountpoint
($new_mountpoint, $ms eq 'rootfs');
2284 # use specified/existing volid
2288 # free allocated images on error
2290 destroy_disks
($storecfg, $vollist);
2296 # bash completion helper
2298 sub complete_os_templates
{
2299 my ($cmdname, $pname, $cvalue) = @_;
2301 my $cfg = PVE
::Storage
::config
();
2305 if ($cvalue =~ m/^([^:]+):/) {
2309 my $vtype = $cmdname eq 'restore' ?
'backup' : 'vztmpl';
2310 my $data = PVE
::Storage
::template_list
($cfg, $storeid, $vtype);
2313 foreach my $id (keys %$data) {
2314 foreach my $item (@{$data->{$id}}) {
2315 push @$res, $item->{volid
} if defined($item->{volid
});
2322 my $complete_ctid_full = sub {
2325 my $idlist = vmstatus
();
2327 my $active_hash = list_active_containers
();
2331 foreach my $id (keys %$idlist) {
2332 my $d = $idlist->{$id};
2333 if (defined($running)) {
2334 next if $d->{template
};
2335 next if $running && !$active_hash->{$id};
2336 next if !$running && $active_hash->{$id};
2345 return &$complete_ctid_full();
2348 sub complete_ctid_stopped
{
2349 return &$complete_ctid_full(0);
2352 sub complete_ctid_running
{
2353 return &$complete_ctid_full(1);