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 our $COMMON_TAR_FLAGS = [ '--sparse', '--numeric-owner', '--acls',
31 '--xattrs-include=user.*',
32 '--xattrs-include=security.capability',
33 '--warning=no-xattr-write' ];
35 cfs_register_file
('/lxc/', \
&parse_pct_config
, \
&write_pct_config
);
41 format_description
=> 'volume',
42 description
=> 'Volume, device or directory to mount into the container.',
46 format_description
=> '[1|0]',
47 description
=> 'Whether to include the mountpoint in backups.',
52 format
=> 'disk-size',
53 format_description
=> 'DiskSize',
54 description
=> 'Volume size (read only value).',
59 PVE
::JSONSchema
::register_standard_option
('pve-ct-rootfs', {
60 type
=> 'string', format
=> $rootfs_desc,
61 description
=> "Use volume as container root.",
65 PVE
::JSONSchema
::register_standard_option
('pve-lxc-snapshot-name', {
66 description
=> "The name of the snapshot.",
67 type
=> 'string', format
=> 'pve-configid',
75 description
=> "Lock/unlock the VM.",
76 enum
=> [qw(migrate backup snapshot rollback)],
81 description
=> "Specifies whether a VM will be started during system bootup.",
84 startup
=> get_standard_option
('pve-startup-order'),
88 description
=> "Enable/disable Template.",
94 enum
=> ['amd64', 'i386'],
95 description
=> "OS architecture type.",
101 enum
=> ['debian', 'ubuntu', 'centos', 'archlinux'],
102 description
=> "OS type. Corresponds to lxc setup scripts in /usr/share/lxc/config/<ostype>.common.conf.",
107 description
=> "Attach a console device (/dev/console) to the container.",
113 description
=> "Specify the number of tty available to the container",
121 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.",
129 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.",
137 description
=> "Amount of RAM for the VM in MB.",
144 description
=> "Amount of SWAP for the VM in MB.",
150 description
=> "Set a host name for the container.",
151 type
=> 'string', format
=> 'dns-name',
157 description
=> "Container description. Only used on the configuration web interface.",
161 type
=> 'string', format
=> 'dns-name-list',
162 description
=> "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain or nameserver.",
166 type
=> 'string', format
=> 'address-list',
167 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.",
169 rootfs
=> get_standard_option
('pve-ct-rootfs'),
172 type
=> 'string', format
=> 'pve-configid',
174 description
=> "Parent snapshot name. This is used internally, and should not be modified.",
178 description
=> "Timestamp for snapshots.",
184 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).",
186 enum
=> ['shell', 'console', 'tty'],
192 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.",
198 description
=> "Makes the container run as unprivileged user. (Should not be modified manually.)",
203 my $valid_lxc_conf_keys = {
207 'lxc.haltsignal' => 1,
208 'lxc.rebootsignal' => 1,
209 'lxc.stopsignal' => 1,
211 'lxc.network.type' => 1,
212 'lxc.network.flags' => 1,
213 'lxc.network.link' => 1,
214 'lxc.network.mtu' => 1,
215 'lxc.network.name' => 1,
216 'lxc.network.hwaddr' => 1,
217 'lxc.network.ipv4' => 1,
218 'lxc.network.ipv4.gateway' => 1,
219 'lxc.network.ipv6' => 1,
220 'lxc.network.ipv6.gateway' => 1,
221 'lxc.network.script.up' => 1,
222 'lxc.network.script.down' => 1,
224 'lxc.console.logfile' => 1,
227 'lxc.devttydir' => 1,
228 'lxc.hook.autodev' => 1,
232 'lxc.mount.entry' => 1,
233 'lxc.mount.auto' => 1,
235 'lxc.rootfs.mount' => 1,
236 'lxc.rootfs.options' => 1,
240 'lxc.aa_profile' => 1,
241 'lxc.aa_allow_incomplete' => 1,
242 'lxc.se_context' => 1,
245 'lxc.hook.pre-start' => 1,
246 'lxc.hook.pre-mount' => 1,
247 'lxc.hook.mount' => 1,
248 'lxc.hook.start' => 1,
249 'lxc.hook.stop' => 1,
250 'lxc.hook.post-stop' => 1,
251 'lxc.hook.clone' => 1,
252 'lxc.hook.destroy' => 1,
255 'lxc.start.auto' => 1,
256 'lxc.start.delay' => 1,
257 'lxc.start.order' => 1,
259 'lxc.environment' => 1,
270 description
=> "Network interface type.",
275 format_description
=> 'String',
276 description
=> 'Name of the network device as seen from inside the container. (lxc.network.name)',
277 pattern
=> '[-_.\w\d]+',
281 format_description
=> 'vmbr<Number>',
282 description
=> 'Bridge to attach the network device to.',
283 pattern
=> '[-_.\w\d]+',
288 format_description
=> 'MAC',
289 description
=> 'Bridge to attach the network device to. (lxc.network.hwaddr)',
290 pattern
=> qr/(?:[a-f0-9]{2}:){5}[a-f0-9]{2}/i,
295 format_description
=> 'Number',
296 description
=> 'Maximum transfer unit of the interface. (lxc.network.mtu)',
297 minimum
=> 64, # minimum ethernet frame is 64 bytes
302 format
=> 'pve-ipv4-config',
303 format_description
=> 'IPv4Format/CIDR',
304 description
=> 'IPv4 address in CIDR format.',
310 format_description
=> 'GatewayIPv4',
311 description
=> 'Default gateway for IPv4 traffic.',
316 format
=> 'pve-ipv6-config',
317 format_description
=> 'IPv6Format/CIDR',
318 description
=> 'IPv6 address in CIDR format.',
324 format_description
=> 'GatewayIPv6',
325 description
=> 'Default gateway for IPv6 traffic.',
330 format_description
=> '[1|0]',
331 description
=> "Controls whether this interface's firewall rules should be used.",
336 format_description
=> 'VlanNo',
339 description
=> "VLAN tag foro this interface.",
343 PVE
::JSONSchema
::register_format
('pve-lxc-network', $netconf_desc);
345 my $MAX_LXC_NETWORKS = 10;
346 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
347 $confdesc->{"net$i"} = {
349 type
=> 'string', format
=> $netconf_desc,
350 description
=> "Specifies network interfaces for the container.",
358 format_description
=> 'Path',
359 description
=> 'Path to the mountpoint as seen from inside the container.',
363 PVE
::JSONSchema
::register_format
('pve-ct-mountpoint', $mp_desc);
367 type
=> 'string', format
=> 'pve-volume-id',
368 description
=> "Reference to unused volumes.",
371 my $MAX_MOUNT_POINTS = 10;
372 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
373 $confdesc->{"mp$i"} = {
375 type
=> 'string', format
=> $mp_desc,
376 description
=> "Use volume as container mount point (experimental feature).",
381 my $MAX_UNUSED_DISKS = $MAX_MOUNT_POINTS;
382 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
383 $confdesc->{"unused$i"} = $unuseddesc;
386 sub write_pct_config
{
387 my ($filename, $conf) = @_;
389 delete $conf->{snapstate
}; # just to be sure
391 my $generate_raw_config = sub {
396 # add description as comment to top of file
397 my $descr = $conf->{description
} || '';
398 foreach my $cl (split(/\n/, $descr)) {
399 $raw .= '#' . PVE
::Tools
::encode_text
($cl) . "\n";
402 foreach my $key (sort keys %$conf) {
403 next if $key eq 'digest' || $key eq 'description' || $key eq 'pending' ||
404 $key eq 'snapshots' || $key eq 'snapname' || $key eq 'lxc';
405 my $value = $conf->{$key};
406 die "detected invalid newline inside property '$key'\n" if $value =~ m/\n/;
407 $raw .= "$key: $value\n";
410 if (my $lxcconf = $conf->{lxc
}) {
411 foreach my $entry (@$lxcconf) {
412 my ($k, $v) = @$entry;
420 my $raw = &$generate_raw_config($conf);
422 foreach my $snapname (sort keys %{$conf->{snapshots
}}) {
423 $raw .= "\n[$snapname]\n";
424 $raw .= &$generate_raw_config($conf->{snapshots
}->{$snapname});
431 my ($key, $value) = @_;
433 die "unknown setting '$key'\n" if !$confdesc->{$key};
435 my $type = $confdesc->{$key}->{type
};
437 if (!defined($value)) {
438 die "got undefined value\n";
441 if ($value =~ m/[\n\r]/) {
442 die "property contains a line feed\n";
445 if ($type eq 'boolean') {
446 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
447 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
448 die "type check ('boolean') failed - got '$value'\n";
449 } elsif ($type eq 'integer') {
450 return int($1) if $value =~ m/^(\d+)$/;
451 die "type check ('integer') failed - got '$value'\n";
452 } elsif ($type eq 'number') {
453 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
454 die "type check ('number') failed - got '$value'\n";
455 } elsif ($type eq 'string') {
456 if (my $fmt = $confdesc->{$key}->{format
}) {
457 PVE
::JSONSchema
::check_format
($fmt, $value);
466 sub parse_pct_config
{
467 my ($filename, $raw) = @_;
469 return undef if !defined($raw);
472 digest
=> Digest
::SHA
::sha1_hex
($raw),
476 $filename =~ m
|/lxc/(\d
+).conf
$|
477 || die "got strange filename '$filename'";
485 my @lines = split(/\n/, $raw);
486 foreach my $line (@lines) {
487 next if $line =~ m/^\s*$/;
489 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
491 $conf->{description
} = $descr if $descr;
493 $conf = $res->{snapshots
}->{$section} = {};
497 if ($line =~ m/^\#(.*)\s*$/) {
498 $descr .= PVE
::Tools
::decode_text
($1) . "\n";
502 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
505 if ($valid_lxc_conf_keys->{$key} || $key =~ m/^lxc\.cgroup\./) {
506 push @{$conf->{lxc
}}, [$key, $value];
508 warn "vm $vmid - unable to parse config: $line\n";
510 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
511 $descr .= PVE
::Tools
::decode_text
($2);
512 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
513 $conf->{snapstate
} = $1;
514 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
517 eval { $value = check_type
($key, $value); };
518 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
519 $conf->{$key} = $value;
521 warn "vm $vmid - unable to parse config: $line\n";
525 $conf->{description
} = $descr if $descr;
527 delete $res->{snapstate
}; # just to be sure
533 my $vmlist = PVE
::Cluster
::get_vmlist
();
535 return $res if !$vmlist || !$vmlist->{ids
};
536 my $ids = $vmlist->{ids
};
538 foreach my $vmid (keys %$ids) {
539 next if !$vmid; # skip CT0
540 my $d = $ids->{$vmid};
541 next if !$d->{node
} || $d->{node
} ne $nodename;
542 next if !$d->{type
} || $d->{type
} ne 'lxc';
543 $res->{$vmid}->{type
} = 'lxc';
548 sub cfs_config_path
{
549 my ($vmid, $node) = @_;
551 $node = $nodename if !$node;
552 return "nodes/$node/lxc/$vmid.conf";
556 my ($vmid, $node) = @_;
558 my $cfspath = cfs_config_path
($vmid, $node);
559 return "/etc/pve/$cfspath";
563 my ($vmid, $node) = @_;
565 $node = $nodename if !$node;
566 my $cfspath = cfs_config_path
($vmid, $node);
568 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath);
569 die "container $vmid does not exists\n" if !defined($conf);
575 my ($vmid, $conf) = @_;
577 my $dir = "/etc/pve/nodes/$nodename/lxc";
580 write_config
($vmid, $conf);
586 unlink config_file
($vmid, $nodename);
590 my ($vmid, $conf) = @_;
592 my $cfspath = cfs_config_path
($vmid);
594 PVE
::Cluster
::cfs_write_file
($cfspath, $conf);
597 # flock: we use one file handle per process, so lock file
598 # can be called multiple times and succeeds for the same process.
600 my $lock_handles = {};
601 my $lockdir = "/run/lock/lxc";
606 return "$lockdir/pve-config-${vmid}.lock";
610 my ($vmid, $timeout) = @_;
612 $timeout = 10 if !$timeout;
615 my $filename = lock_filename
($vmid);
617 mkdir $lockdir if !-d
$lockdir;
619 my $lock_func = sub {
620 if (!$lock_handles->{$$}->{$filename}) {
621 my $fh = new IO
::File
(">>$filename") ||
622 die "can't open file - $!\n";
623 $lock_handles->{$$}->{$filename} = { fh
=> $fh, refcount
=> 0};
626 if (!flock($lock_handles->{$$}->{$filename}->{fh
}, $mode |LOCK_NB
)) {
627 print STDERR
"trying to aquire lock...";
630 $success = flock($lock_handles->{$$}->{$filename}->{fh
}, $mode);
631 # try again on EINTR (see bug #273)
632 if ($success || ($! != EINTR
)) {
637 print STDERR
" failed\n";
638 die "can't aquire lock - $!\n";
641 print STDERR
" OK\n";
644 $lock_handles->{$$}->{$filename}->{refcount
}++;
647 eval { PVE
::Tools
::run_with_timeout
($timeout, $lock_func); };
650 die "can't lock file '$filename' - $err";
657 my $filename = lock_filename
($vmid);
659 if (my $fh = $lock_handles->{$$}->{$filename}->{fh
}) {
660 my $refcount = --$lock_handles->{$$}->{$filename}->{refcount
};
661 if ($refcount <= 0) {
662 $lock_handles->{$$}->{$filename} = undef;
669 my ($vmid, $timeout, $code, @param) = @_;
673 lock_aquire
($vmid, $timeout);
674 eval { $res = &$code(@param) };
686 return defined($confdesc->{$name});
689 # add JSON properties for create and set function
690 sub json_config_properties
{
693 foreach my $opt (keys %$confdesc) {
694 next if $opt eq 'parent' || $opt eq 'snaptime';
695 next if $prop->{$opt};
696 $prop->{$opt} = $confdesc->{$opt};
702 sub json_config_properties_no_rootfs
{
705 foreach my $opt (keys %$confdesc) {
706 next if $prop->{$opt};
707 next if $opt eq 'parent' || $opt eq 'snaptime' || $opt eq 'rootfs';
708 $prop->{$opt} = $confdesc->{$opt};
714 # container status helpers
716 sub list_active_containers
{
718 my $filename = "/proc/net/unix";
720 # similar test is used by lcxcontainers.c: list_active_containers
723 my $fh = IO
::File-
>new ($filename, "r");
726 while (defined(my $line = <$fh>)) {
727 if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) {
729 if ($path =~ m!^@/var/lib/lxc/(\d+)/command$!) {
740 # warning: this is slow
744 my $active_hash = list_active_containers
();
746 return 1 if defined($active_hash->{$vmid});
751 sub get_container_disk_usage
{
752 my ($vmid, $pid) = @_;
754 return PVE
::Tools
::df
("/proc/$pid/root/", 1);
757 my $last_proc_vmid_stat;
759 my $parse_cpuacct_stat = sub {
762 my $raw = read_cgroup_value
('cpuacct', $vmid, 'cpuacct.stat', 1);
766 if ($raw =~ m/^user (\d+)\nsystem (\d+)\n/) {
779 my $list = $opt_vmid ?
{ $opt_vmid => { type
=> 'lxc' }} : config_list
();
781 my $active_hash = list_active_containers
();
783 my $cpucount = $cpuinfo->{cpus
} || 1;
785 my $cdtime = gettimeofday
;
787 my $uptime = (PVE
::ProcFSTools
::read_proc_uptime
(1))[0];
789 foreach my $vmid (keys %$list) {
790 my $d = $list->{$vmid};
792 eval { $d->{pid
} = find_lxc_pid
($vmid) if defined($active_hash->{$vmid}); };
793 warn $@ if $@; # ignore errors (consider them stopped)
795 $d->{status
} = $d->{pid
} ?
'running' : 'stopped';
797 my $cfspath = cfs_config_path
($vmid);
798 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath) || {};
800 $d->{name
} = $conf->{'hostname'} || "CT$vmid";
801 $d->{name
} =~ s/[\s]//g;
803 $d->{cpus
} = $conf->{cpulimit
} || $cpucount;
806 my $res = get_container_disk_usage
($vmid, $d->{pid
});
807 $d->{disk
} = $res->{used
};
808 $d->{maxdisk
} = $res->{total
};
811 # use 4GB by default ??
812 if (my $rootfs = $conf->{rootfs
}) {
813 my $rootinfo = parse_ct_mountpoint
($rootfs);
814 $d->{maxdisk
} = int(($rootinfo->{size
} || 4)*1024*1024)*1024;
816 $d->{maxdisk
} = 4*1024*1024*1024;
822 $d->{maxmem
} = ($conf->{memory
}||512)*1024*1024;
823 $d->{maxswap
} = ($conf->{swap
}//0)*1024*1024;
834 $d->{template
} = is_template
($conf);
837 foreach my $vmid (keys %$list) {
838 my $d = $list->{$vmid};
841 next if !$pid; # skip stopped CTs
843 my $ctime = (stat("/proc/$pid"))[10]; # 10 = ctime
844 $d->{uptime
} = time - $ctime; # the method lxcfs uses
846 $d->{mem
} = read_cgroup_value
('memory', $vmid, 'memory.usage_in_bytes');
847 $d->{swap
} = read_cgroup_value
('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem
};
849 my $blkio_bytes = read_cgroup_value
('blkio', $vmid, 'blkio.throttle.io_service_bytes', 1);
850 my @bytes = split(/\n/, $blkio_bytes);
851 foreach my $byte (@bytes) {
852 if (my ($key, $value) = $byte =~ /(Read|Write)\s+(\d+)/) {
853 $d->{diskread
} = $2 if $key eq 'Read';
854 $d->{diskwrite
} = $2 if $key eq 'Write';
858 my $pstat = &$parse_cpuacct_stat($vmid);
860 my $used = $pstat->{utime} + $pstat->{stime
};
862 my $old = $last_proc_vmid_stat->{$vmid};
864 $last_proc_vmid_stat->{$vmid} = {
872 my $dtime = ($cdtime - $old->{time}) * $cpucount * $cpuinfo->{user_hz
};
875 my $dutime = $used - $old->{used
};
877 $d->{cpu
} = (($dutime/$dtime)* $cpucount) / $d->{cpus
};
878 $last_proc_vmid_stat->{$vmid} = {
884 $d->{cpu
} = $old->{cpu
};
888 my $netdev = PVE
::ProcFSTools
::read_proc_net_dev
();
890 foreach my $dev (keys %$netdev) {
891 next if $dev !~ m/^veth([1-9]\d*)i/;
893 my $d = $list->{$vmid};
897 $d->{netout
} += $netdev->{$dev}->{receive
};
898 $d->{netin
} += $netdev->{$dev}->{transmit
};
905 sub parse_ct_mountpoint
{
906 my ($data, $noerr) = @_;
911 eval { $res = PVE
::JSONSchema
::parse_property_string
($mp_desc, $data) };
913 return undef if $noerr;
917 if (defined(my $size = $res->{size
})) {
918 $size = PVE
::JSONSchema
::parse_size
($size);
919 if (!defined($size)) {
920 return undef if $noerr;
921 die "invalid size: $size\n";
923 $res->{size
} = $size;
929 sub print_ct_mountpoint
{
930 my ($info, $nomp) = @_;
931 my $skip = $nomp ?
['mp'] : [];
932 return PVE
::JSONSchema
::print_property_string
($info, $mp_desc, $skip);
935 sub print_lxc_network
{
937 return PVE
::JSONSchema
::print_property_string
($net, $netconf_desc);
940 sub parse_lxc_network
{
945 return $res if !$data;
947 $res = PVE
::JSONSchema
::parse_property_string
($netconf_desc, $data);
949 $res->{type
} = 'veth';
950 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
() if !$res->{hwaddr
};
955 sub read_cgroup_value
{
956 my ($group, $vmid, $name, $full) = @_;
958 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
960 return PVE
::Tools
::file_get_contents
($path) if $full;
962 return PVE
::Tools
::file_read_firstline
($path);
965 sub write_cgroup_value
{
966 my ($group, $vmid, $name, $value) = @_;
968 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
969 PVE
::ProcFSTools
::write_proc_entry
($path, $value) if -e
$path;
973 sub find_lxc_console_pids
{
977 PVE
::Tools
::dir_glob_foreach
('/proc', '\d+', sub {
980 my $cmdline = PVE
::Tools
::file_read_firstline
("/proc/$pid/cmdline");
983 my @args = split(/\0/, $cmdline);
985 # serach for lxc-console -n <vmid>
986 return if scalar(@args) != 3;
987 return if $args[1] ne '-n';
988 return if $args[2] !~ m/^\d+$/;
989 return if $args[0] !~ m
|^(/usr/bin
/)?lxc-console
$|;
993 push @{$res->{$vmid}}, $pid;
1005 $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
1007 PVE
::Tools
::run_command
(['lxc-info', '-n', $vmid, '-p'], outfunc
=> $parser);
1009 die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
1014 # Note: we cannot use Net:IP, because that only allows strict
1016 sub parse_ipv4_cidr
{
1017 my ($cidr, $noerr) = @_;
1019 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 <= 32)) {
1020 return { address
=> $1, netmask
=> $PVE::Network
::ipv4_reverse_mask-
>[$2] };
1023 return undef if $noerr;
1025 die "unable to parse ipv4 address/mask\n";
1031 die "VM is locked ($conf->{'lock'})\n" if $conf->{'lock'};
1034 sub check_protection
{
1035 my ($vm_conf, $err_msg) = @_;
1037 if ($vm_conf->{protection
}) {
1038 die "$err_msg - protection mode enabled\n";
1042 sub update_lxc_config
{
1043 my ($storage_cfg, $vmid, $conf) = @_;
1045 my $dir = "/var/lib/lxc/$vmid";
1047 if ($conf->{template
}) {
1049 unlink "$dir/config";
1056 die "missing 'arch' - internal error" if !$conf->{arch
};
1057 $raw .= "lxc.arch = $conf->{arch}\n";
1059 my $unprivileged = $conf->{unprivileged
};
1060 my $custom_idmap = grep { $_->[0] eq 'lxc.id_map' } @{$conf->{lxc
}};
1062 my $ostype = $conf->{ostype
} || die "missing 'ostype' - internal error";
1063 if ($ostype =~ /^(?:debian | ubuntu | centos | archlinux)$/x) {
1064 $raw .= "lxc.include = /usr/share/lxc/config/$ostype.common.conf\n";
1065 if ($unprivileged || $custom_idmap) {
1066 $raw .= "lxc.include = /usr/share/lxc/config/$ostype.userns.conf\n"
1072 # Should we read them from /etc/subuid?
1073 if ($unprivileged && !$custom_idmap) {
1074 $raw .= "lxc.id_map = u 0 100000 65536\n";
1075 $raw .= "lxc.id_map = g 0 100000 65536\n";
1078 if (!has_dev_console
($conf)) {
1079 $raw .= "lxc.console = none\n";
1080 $raw .= "lxc.cgroup.devices.deny = c 5:1 rwm\n";
1083 my $ttycount = get_tty_count
($conf);
1084 $raw .= "lxc.tty = $ttycount\n";
1086 # some init scripts expects a linux terminal (turnkey).
1087 $raw .= "lxc.environment = TERM=linux\n";
1089 my $utsname = $conf->{hostname
} || "CT$vmid";
1090 $raw .= "lxc.utsname = $utsname\n";
1092 my $memory = $conf->{memory
} || 512;
1093 my $swap = $conf->{swap
} // 0;
1095 my $lxcmem = int($memory*1024*1024);
1096 $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n";
1098 my $lxcswap = int(($memory + $swap)*1024*1024);
1099 $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n";
1101 if (my $cpulimit = $conf->{cpulimit
}) {
1102 $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n";
1103 my $value = int(100000*$cpulimit);
1104 $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n";
1107 my $shares = $conf->{cpuunits
} || 1024;
1108 $raw .= "lxc.cgroup.cpu.shares = $shares\n";
1110 my $mountpoint = parse_ct_mountpoint
($conf->{rootfs
});
1111 $mountpoint->{mp
} = '/';
1113 $raw .= "lxc.rootfs = $dir/rootfs\n";
1116 foreach my $k (keys %$conf) {
1117 next if $k !~ m/^net(\d+)$/;
1119 my $d = parse_lxc_network
($conf->{$k});
1121 $raw .= "lxc.network.type = veth\n";
1122 $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
1123 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr
});
1124 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name
});
1125 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu
});
1128 if (my $lxcconf = $conf->{lxc
}) {
1129 foreach my $entry (@$lxcconf) {
1130 my ($k, $v) = @$entry;
1131 $netcount++ if $k eq 'lxc.network.type';
1132 $raw .= "$k = $v\n";
1136 $raw .= "lxc.network.type = empty\n" if !$netcount;
1138 File
::Path
::mkpath
("$dir/rootfs");
1140 PVE
::Tools
::file_set_contents
("$dir/config", $raw);
1143 # verify and cleanup nameserver list (replace \0 with ' ')
1144 sub verify_nameserver_list
{
1145 my ($nameserver_list) = @_;
1148 foreach my $server (PVE
::Tools
::split_list
($nameserver_list)) {
1149 PVE
::JSONSchema
::pve_verify_ip
($server);
1150 push @list, $server;
1153 return join(' ', @list);
1156 sub verify_searchdomain_list
{
1157 my ($searchdomain_list) = @_;
1160 foreach my $server (PVE
::Tools
::split_list
($searchdomain_list)) {
1161 # todo: should we add checks for valid dns domains?
1162 push @list, $server;
1165 return join(' ', @list);
1168 sub add_unused_volume
{
1169 my ($config, $volid) = @_;
1171 # skip bind mounts and block devices
1172 return if $volid =~ m
|^/|;
1175 for (my $ind = $MAX_UNUSED_DISKS - 1; $ind >= 0; $ind--) {
1176 my $test = "unused$ind";
1177 if (my $vid = $config->{$test}) {
1178 return if $vid eq $volid; # do not add duplicates
1184 die "To many unused volume - please delete them first.\n" if !$key;
1186 $config->{$key} = $volid;
1191 sub update_pct_config
{
1192 my ($vmid, $conf, $running, $param, $delete) = @_;
1197 my @deleted_volumes;
1201 my $pid = find_lxc_pid
($vmid);
1202 $rootdir = "/proc/$pid/root";
1205 my $hotplug_error = sub {
1207 push @nohotplug, @_;
1214 if (defined($delete)) {
1215 foreach my $opt (@$delete) {
1216 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
1217 die "unable to delete required option '$opt'\n";
1218 } elsif ($opt eq 'swap') {
1219 delete $conf->{$opt};
1220 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
1221 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
1222 delete $conf->{$opt};
1223 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
1224 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1225 next if $hotplug_error->($opt);
1226 delete $conf->{$opt};
1227 } elsif ($opt =~ m/^net(\d)$/) {
1228 delete $conf->{$opt};
1231 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
1232 } elsif ($opt eq 'protection') {
1233 delete $conf->{$opt};
1234 } elsif ($opt =~ m/^unused(\d+)$/) {
1235 next if $hotplug_error->($opt);
1236 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1237 push @deleted_volumes, $conf->{$opt};
1238 delete $conf->{$opt};
1239 } elsif ($opt =~ m/^mp(\d+)$/) {
1240 next if $hotplug_error->($opt);
1241 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1242 my $mountpoint = parse_ct_mountpoint
($conf->{$opt});
1243 add_unused_volume
($conf, $mountpoint->{volume
});
1244 delete $conf->{$opt};
1245 } elsif ($opt eq 'unprivileged') {
1246 die "unable to delete read-only option: '$opt'\n";
1250 write_config
($vmid, $conf) if $running;
1254 # There's no separate swap size to configure, there's memory and "total"
1255 # memory (iow. memory+swap). This means we have to change them together.
1256 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
1257 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
1258 if (defined($wanted_memory) || defined($wanted_swap)) {
1260 $wanted_memory //= ($conf->{memory
} || 512);
1261 $wanted_swap //= ($conf->{swap
} || 0);
1263 my $total = $wanted_memory + $wanted_swap;
1265 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1266 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1268 $conf->{memory
} = $wanted_memory;
1269 $conf->{swap
} = $wanted_swap;
1271 write_config
($vmid, $conf) if $running;
1274 foreach my $opt (keys %$param) {
1275 my $value = $param->{$opt};
1276 if ($opt eq 'hostname') {
1277 $conf->{$opt} = $value;
1278 } elsif ($opt eq 'onboot') {
1279 $conf->{$opt} = $value ?
1 : 0;
1280 } elsif ($opt eq 'startup') {
1281 $conf->{$opt} = $value;
1282 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1283 next if $hotplug_error->($opt);
1284 $conf->{$opt} = $value;
1285 } elsif ($opt eq 'nameserver') {
1286 next if $hotplug_error->($opt);
1287 my $list = verify_nameserver_list
($value);
1288 $conf->{$opt} = $list;
1289 } elsif ($opt eq 'searchdomain') {
1290 next if $hotplug_error->($opt);
1291 my $list = verify_searchdomain_list
($value);
1292 $conf->{$opt} = $list;
1293 } elsif ($opt eq 'cpulimit') {
1294 next if $hotplug_error->($opt); # FIXME: hotplug
1295 $conf->{$opt} = $value;
1296 } elsif ($opt eq 'cpuunits') {
1297 $conf->{$opt} = $value;
1298 write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
1299 } elsif ($opt eq 'description') {
1300 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
1301 } elsif ($opt =~ m/^net(\d+)$/) {
1303 my $net = parse_lxc_network
($value);
1305 $conf->{$opt} = print_lxc_network
($net);
1307 update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1309 } elsif ($opt eq 'protection') {
1310 $conf->{$opt} = $value ?
1 : 0;
1311 } elsif ($opt =~ m/^mp(\d+)$/) {
1312 next if $hotplug_error->($opt);
1313 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1314 $conf->{$opt} = $value;
1316 } elsif ($opt eq 'rootfs') {
1317 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1318 die "implement me: $opt";
1319 } elsif ($opt eq 'unprivileged') {
1320 die "unable to modify read-only option: '$opt'\n";
1322 die "implement me: $opt";
1324 write_config
($vmid, $conf) if $running;
1327 if (@deleted_volumes) {
1328 my $storage_cfg = PVE
::Storage
::config
();
1329 foreach my $volume (@deleted_volumes) {
1330 delete_mountpoint_volume
($storage_cfg, $vmid, $volume);
1335 my $storage_cfg = PVE
::Storage
::config
();
1336 create_disks
($storage_cfg, $vmid, $conf, $conf);
1339 # This should be the last thing we do here
1340 if ($running && scalar(@nohotplug)) {
1341 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1345 sub has_dev_console
{
1348 return !(defined($conf->{console
}) && !$conf->{console
});
1354 return $conf->{tty
} // $confdesc->{tty
}->{default};
1360 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1363 sub get_console_command
{
1364 my ($vmid, $conf) = @_;
1366 my $cmode = get_cmode
($conf);
1368 if ($cmode eq 'console') {
1369 return ['lxc-console', '-n', $vmid, '-t', 0];
1370 } elsif ($cmode eq 'tty') {
1371 return ['lxc-console', '-n', $vmid];
1372 } elsif ($cmode eq 'shell') {
1373 return ['lxc-attach', '--clear-env', '-n', $vmid];
1375 die "internal error";
1379 sub get_primary_ips
{
1382 # return data from net0
1384 return undef if !defined($conf->{net0
});
1385 my $net = parse_lxc_network
($conf->{net0
});
1387 my $ipv4 = $net->{ip
};
1389 if ($ipv4 =~ /^(dhcp|manual)$/) {
1395 my $ipv6 = $net->{ip6
};
1397 if ($ipv6 =~ /^(auto|dhcp|manual)$/) {
1404 return ($ipv4, $ipv6);
1407 sub delete_mountpoint_volume
{
1408 my ($storage_cfg, $vmid, $volume) = @_;
1410 # skip bind mounts and block devices
1411 if ($volume =~ m
|^/|) {
1415 my ($vtype, $name, $owner) = PVE
::Storage
::parse_volname
($storage_cfg, $volume);
1416 PVE
::Storage
::vdisk_free
($storage_cfg, $volume) if $vmid == $owner;
1419 sub destroy_lxc_container
{
1420 my ($storage_cfg, $vmid, $conf) = @_;
1422 foreach_mountpoint
($conf, sub {
1423 my ($ms, $mountpoint) = @_;
1424 delete_mountpoint_volume
($storage_cfg, $vmid, $mountpoint->{volume
});
1427 rmdir "/var/lib/lxc/$vmid/rootfs";
1428 unlink "/var/lib/lxc/$vmid/config";
1429 rmdir "/var/lib/lxc/$vmid";
1430 destroy_config
($vmid);
1432 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1433 #PVE::Tools::run_command($cmd);
1436 sub vm_stop_cleanup
{
1437 my ($storage_cfg, $vmid, $conf, $keepActive) = @_;
1442 my $vollist = get_vm_volumes
($conf);
1443 PVE
::Storage
::deactivate_volumes
($storage_cfg, $vollist);
1446 warn $@ if $@; # avoid errors - just warn
1449 my $safe_num_ne = sub {
1452 return 0 if !defined($a) && !defined($b);
1453 return 1 if !defined($a);
1454 return 1 if !defined($b);
1459 my $safe_string_ne = sub {
1462 return 0 if !defined($a) && !defined($b);
1463 return 1 if !defined($a);
1464 return 1 if !defined($b);
1470 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1472 if ($newnet->{type
} ne 'veth') {
1473 # for when there are physical interfaces
1474 die "cannot update interface of type $newnet->{type}";
1477 my $veth = "veth${vmid}i${netid}";
1478 my $eth = $newnet->{name
};
1480 if (my $oldnetcfg = $conf->{$opt}) {
1481 my $oldnet = parse_lxc_network
($oldnetcfg);
1483 if (&$safe_string_ne($oldnet->{hwaddr
}, $newnet->{hwaddr
}) ||
1484 &$safe_string_ne($oldnet->{name
}, $newnet->{name
})) {
1486 PVE
::Network
::veth_delete
($veth);
1487 delete $conf->{$opt};
1488 write_config
($vmid, $conf);
1490 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1492 } elsif (&$safe_string_ne($oldnet->{bridge
}, $newnet->{bridge
}) ||
1493 &$safe_num_ne($oldnet->{tag
}, $newnet->{tag
}) ||
1494 &$safe_num_ne($oldnet->{firewall
}, $newnet->{firewall
})) {
1496 if ($oldnet->{bridge
}) {
1497 PVE
::Network
::tap_unplug
($veth);
1498 foreach (qw(bridge tag firewall)) {
1499 delete $oldnet->{$_};
1501 $conf->{$opt} = print_lxc_network
($oldnet);
1502 write_config
($vmid, $conf);
1505 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1506 foreach (qw(bridge tag firewall)) {
1507 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1509 $conf->{$opt} = print_lxc_network
($oldnet);
1510 write_config
($vmid, $conf);
1513 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1516 update_ipconfig
($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1520 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
1522 my $veth = "veth${vmid}i${netid}";
1523 my $vethpeer = $veth . "p";
1524 my $eth = $newnet->{name
};
1526 PVE
::Network
::veth_create
($veth, $vethpeer, $newnet->{bridge
}, $newnet->{hwaddr
});
1527 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1529 # attach peer in container
1530 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1531 PVE
::Tools
::run_command
($cmd);
1533 # link up peer in container
1534 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1535 PVE
::Tools
::run_command
($cmd);
1537 my $done = { type
=> 'veth' };
1538 foreach (qw(bridge tag firewall hwaddr name)) {
1539 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1541 $conf->{$opt} = print_lxc_network
($done);
1543 write_config
($vmid, $conf);
1546 sub update_ipconfig
{
1547 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1549 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
1551 my $optdata = parse_lxc_network
($conf->{$opt});
1555 my $cmdargs = shift;
1556 PVE
::Tools
::run_command
(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
1558 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
1560 my $change_ip_config = sub {
1561 my ($ipversion) = @_;
1563 my $family_opt = "-$ipversion";
1564 my $suffix = $ipversion == 4 ?
'' : $ipversion;
1565 my $gw= "gw$suffix";
1566 my $ip= "ip$suffix";
1568 my $newip = $newnet->{$ip};
1569 my $newgw = $newnet->{$gw};
1570 my $oldip = $optdata->{$ip};
1572 my $change_ip = &$safe_string_ne($oldip, $newip);
1573 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
1575 return if !$change_ip && !$change_gw;
1577 # step 1: add new IP, if this fails we cancel
1578 my $is_real_ip = ($newip && $newip !~ /^(?:auto|dhcp|manual)$/);
1579 if ($change_ip && $is_real_ip) {
1580 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
1587 # step 2: replace gateway
1588 # If this fails we delete the added IP and cancel.
1589 # If it succeeds we save the config and delete the old IP, ignoring
1590 # errors. The config is then saved.
1591 # Note: 'ip route replace' can add
1595 if ($is_real_ip && !PVE
::Network
::is_ip_in_cidr
($newgw, $newip, $ipversion)) {
1596 &$ipcmd($family_opt, 'route', 'add', $newgw, 'dev', $eth);
1598 &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw);
1602 # the route was not replaced, the old IP is still available
1603 # rollback (delete new IP) and cancel
1605 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
1606 warn $@ if $@; # no need to die here
1611 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
1612 # if the route was not deleted, the guest might have deleted it manually
1618 # from this point on we save the configuration
1619 # step 3: delete old IP ignoring errors
1620 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
1621 # We need to enable promote_secondaries, otherwise our newly added
1622 # address will be removed along with the old one.
1625 if ($ipversion == 4) {
1626 &$nscmd({ outfunc
=> sub { $promote = int(shift) } },
1627 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1628 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1630 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1632 warn $@ if $@; # no need to die here
1634 if ($ipversion == 4) {
1635 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1639 foreach my $property ($ip, $gw) {
1640 if ($newnet->{$property}) {
1641 $optdata->{$property} = $newnet->{$property};
1643 delete $optdata->{$property};
1646 $conf->{$opt} = print_lxc_network
($optdata);
1647 write_config
($vmid, $conf);
1648 $lxc_setup->setup_network($conf);
1651 &$change_ip_config(4);
1652 &$change_ip_config(6);
1656 # Internal snapshots
1658 # NOTE: Snapshot create/delete involves several non-atomic
1659 # action, and can take a long time.
1660 # So we try to avoid locking the file and use 'lock' variable
1661 # inside the config file instead.
1663 my $snapshot_copy_config = sub {
1664 my ($source, $dest) = @_;
1666 foreach my $k (keys %$source) {
1667 next if $k eq 'snapshots';
1668 next if $k eq 'snapstate';
1669 next if $k eq 'snaptime';
1670 next if $k eq 'vmstate';
1671 next if $k eq 'lock';
1672 next if $k eq 'digest';
1673 next if $k eq 'description';
1675 $dest->{$k} = $source->{$k};
1679 my $snapshot_prepare = sub {
1680 my ($vmid, $snapname, $comment) = @_;
1684 my $updatefn = sub {
1686 my $conf = load_config
($vmid);
1688 die "you can't take a snapshot if it's a template\n"
1689 if is_template
($conf);
1693 $conf->{lock} = 'snapshot';
1695 die "snapshot name '$snapname' already used\n"
1696 if defined($conf->{snapshots
}->{$snapname});
1698 my $storecfg = PVE
::Storage
::config
();
1699 die "snapshot feature is not available\n" if !has_feature
('snapshot', $conf, $storecfg);
1701 $snap = $conf->{snapshots
}->{$snapname} = {};
1703 &$snapshot_copy_config($conf, $snap);
1705 $snap->{'snapstate'} = "prepare";
1706 $snap->{'snaptime'} = time();
1707 $snap->{'description'} = $comment if $comment;
1708 $conf->{snapshots
}->{$snapname} = $snap;
1710 write_config
($vmid, $conf);
1713 lock_container
($vmid, 10, $updatefn);
1718 my $snapshot_commit = sub {
1719 my ($vmid, $snapname) = @_;
1721 my $updatefn = sub {
1723 my $conf = load_config
($vmid);
1725 die "missing snapshot lock\n"
1726 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
1728 die "snapshot '$snapname' does not exist\n"
1729 if !defined($conf->{snapshots
}->{$snapname});
1731 die "wrong snapshot state\n"
1732 if !($conf->{snapshots
}->{$snapname}->{'snapstate'} &&
1733 $conf->{snapshots
}->{$snapname}->{'snapstate'} eq "prepare");
1735 delete $conf->{snapshots
}->{$snapname}->{'snapstate'};
1736 delete $conf->{lock};
1737 $conf->{parent
} = $snapname;
1739 write_config
($vmid, $conf);
1742 lock_container
($vmid, 10 ,$updatefn);
1746 my ($feature, $conf, $storecfg, $snapname) = @_;
1750 foreach_mountpoint
($conf, sub {
1751 my ($ms, $mountpoint) = @_;
1753 return if $err; # skip further test
1755 $err = 1 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature, $mountpoint->{volume
}, $snapname);
1757 # TODO: implement support for mountpoints
1758 die "unable to handle mountpoint '$ms' - feature not implemented\n"
1762 return $err ?
0 : 1;
1765 sub snapshot_create
{
1766 my ($vmid, $snapname, $comment) = @_;
1768 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1770 my $conf = load_config
($vmid);
1772 my $running = check_running
($vmid);
1775 PVE
::Tools
::run_command
(['/usr/bin/lxc-freeze', '-n', $vmid]);
1776 PVE
::Tools
::run_command
(['/bin/sync']);
1779 my $storecfg = PVE
::Storage
::config
();
1780 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1781 my $volid = $rootinfo->{volume
};
1784 PVE
::Tools
::run_command
(['/usr/bin/lxc-unfreeze', '-n', $vmid]);
1787 PVE
::Storage
::volume_snapshot
($storecfg, $volid, $snapname);
1788 &$snapshot_commit($vmid, $snapname);
1791 snapshot_delete
($vmid, $snapname, 1);
1796 sub snapshot_delete
{
1797 my ($vmid, $snapname, $force) = @_;
1803 my $updatefn = sub {
1805 $conf = load_config
($vmid);
1807 die "you can't delete a snapshot if vm is a template\n"
1808 if is_template
($conf);
1810 $snap = $conf->{snapshots
}->{$snapname};
1814 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1816 $snap->{snapstate
} = 'delete';
1818 write_config
($vmid, $conf);
1821 lock_container
($vmid, 10, $updatefn);
1823 my $storecfg = PVE
::Storage
::config
();
1825 my $del_snap = sub {
1829 if ($conf->{parent
} eq $snapname) {
1830 if ($conf->{snapshots
}->{$snapname}->{snapname
}) {
1831 $conf->{parent
} = $conf->{snapshots
}->{$snapname}->{parent
};
1833 delete $conf->{parent
};
1837 delete $conf->{snapshots
}->{$snapname};
1839 write_config
($vmid, $conf);
1842 my $rootfs = $conf->{snapshots
}->{$snapname}->{rootfs
};
1843 my $rootinfo = parse_ct_mountpoint
($rootfs);
1844 my $volid = $rootinfo->{volume
};
1847 PVE
::Storage
::volume_snapshot_delete
($storecfg, $volid, $snapname);
1851 if(!$err || ($err && $force)) {
1852 lock_container
($vmid, 10, $del_snap);
1854 die "Can't delete snapshot: $vmid $snapname $err\n";
1859 sub snapshot_rollback
{
1860 my ($vmid, $snapname) = @_;
1862 my $storecfg = PVE
::Storage
::config
();
1864 my $conf = load_config
($vmid);
1866 die "you can't rollback if vm is a template\n" if is_template
($conf);
1868 my $snap = $conf->{snapshots
}->{$snapname};
1870 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1872 my $rootfs = $snap->{rootfs
};
1873 my $rootinfo = parse_ct_mountpoint
($rootfs);
1874 my $volid = $rootinfo->{volume
};
1876 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $volid, $snapname);
1878 my $updatefn = sub {
1880 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1881 if $snap->{snapstate
};
1885 system("lxc-stop -n $vmid --kill") if check_running
($vmid);
1887 die "unable to rollback vm $vmid: vm is running\n"
1888 if check_running
($vmid);
1890 $conf->{lock} = 'rollback';
1894 # copy snapshot config to current config
1896 my $tmp_conf = $conf;
1897 &$snapshot_copy_config($tmp_conf->{snapshots
}->{$snapname}, $conf);
1898 $conf->{snapshots
} = $tmp_conf->{snapshots
};
1899 delete $conf->{snaptime
};
1900 delete $conf->{snapname
};
1901 $conf->{parent
} = $snapname;
1903 write_config
($vmid, $conf);
1906 my $unlockfn = sub {
1907 delete $conf->{lock};
1908 write_config
($vmid, $conf);
1911 lock_container
($vmid, 10, $updatefn);
1913 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $volid, $snapname);
1915 lock_container
($vmid, 5, $unlockfn);
1918 sub template_create
{
1919 my ($vmid, $conf) = @_;
1921 my $storecfg = PVE
::Storage
::config
();
1923 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1924 my $volid = $rootinfo->{volume
};
1926 die "Template feature is not available for '$volid'\n"
1927 if !PVE
::Storage
::volume_has_feature
($storecfg, 'template', $volid);
1929 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
1931 my $template_volid = PVE
::Storage
::vdisk_create_base
($storecfg, $volid);
1932 $rootinfo->{volume
} = $template_volid;
1933 $conf->{rootfs
} = print_ct_mountpoint
($rootinfo, 1);
1935 write_config
($vmid, $conf);
1941 return 1 if defined $conf->{template
} && $conf->{template
} == 1;
1944 sub mountpoint_names
{
1947 my @names = ('rootfs');
1949 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1950 push @names, "mp$i";
1953 return $reverse ?
reverse @names : @names;
1956 # The container might have *different* symlinks than the host. realpath/abs_path
1957 # use the actual filesystem to resolve links.
1958 sub sanitize_mountpoint
{
1960 $mp = '/' . $mp; # we always start with a slash
1961 $mp =~ s
@/{2,}@/@g; # collapse sequences of slashes
1962 $mp =~ s
@/\./@@g; # collapse /./
1963 $mp =~ s
@/\.(/)?
$@$1@; # collapse a trailing /. or /./
1964 $mp =~ s
@(.*)/[^/]+/\.\./@$1/@g; # collapse /../ without regard for symlinks
1965 $mp =~ s
@/\.\
.(/)?
$@$1@; # collapse trailing /.. or /../ disregarding symlinks
1969 sub foreach_mountpoint_full
{
1970 my ($conf, $reverse, $func) = @_;
1972 foreach my $key (mountpoint_names
($reverse)) {
1973 my $value = $conf->{$key};
1974 next if !defined($value);
1975 my $mountpoint = parse_ct_mountpoint
($value, 1);
1976 next if !defined($mountpoint);
1978 # just to be sure: rootfs is /
1979 my $path = $key eq 'rootfs' ?
'/' : $mountpoint->{mp
};
1980 $mountpoint->{mp
} = sanitize_mountpoint
($path);
1982 $path = $mountpoint->{volume
};
1983 $mountpoint->{volume
} = sanitize_mountpoint
($path) if $path =~ m
|^/|;
1985 &$func($key, $mountpoint);
1989 sub foreach_mountpoint
{
1990 my ($conf, $func) = @_;
1992 foreach_mountpoint_full
($conf, 0, $func);
1995 sub foreach_mountpoint_reverse
{
1996 my ($conf, $func) = @_;
1998 foreach_mountpoint_full
($conf, 1, $func);
2001 sub check_ct_modify_config_perm
{
2002 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
2004 return 1 if $authuser ne 'root@pam';
2006 foreach my $opt (@$key_list) {
2008 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
2009 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
2010 } elsif ($opt eq 'rootfs' || $opt =~ /^mp\d+$/) {
2011 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
2012 } elsif ($opt eq 'memory' || $opt eq 'swap') {
2013 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
2014 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
2015 $opt eq 'searchdomain' || $opt eq 'hostname') {
2016 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
2018 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
2026 my ($vmid, $storage_cfg, $conf, $noerr) = @_;
2028 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2029 my $volid_list = get_vm_volumes
($conf);
2031 foreach_mountpoint_reverse
($conf, sub {
2032 my ($ms, $mountpoint) = @_;
2034 my $volid = $mountpoint->{volume
};
2035 my $mount = $mountpoint->{mp
};
2037 return if !$volid || !$mount;
2039 my $mount_path = "$rootdir/$mount";
2040 $mount_path =~ s!/+!/!g;
2042 return if !PVE
::ProcFSTools
::is_mounted
($mount_path);
2045 PVE
::Tools
::run_command
(['umount', '-d', $mount_path]);
2058 my ($vmid, $storage_cfg, $conf) = @_;
2060 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2061 File
::Path
::make_path
($rootdir);
2063 my $volid_list = get_vm_volumes
($conf);
2064 PVE
::Storage
::activate_volumes
($storage_cfg, $volid_list);
2067 foreach_mountpoint
($conf, sub {
2068 my ($ms, $mountpoint) = @_;
2070 my $volid = $mountpoint->{volume
};
2071 my $mount = $mountpoint->{mp
};
2073 return if !$volid || !$mount;
2075 my $image_path = PVE
::Storage
::path
($storage_cfg, $volid);
2076 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2077 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2079 die "unable to mount base volume - internal error" if $isBase;
2081 mountpoint_mount
($mountpoint, $rootdir, $storage_cfg);
2085 warn "mounting container failed - $err";
2086 umount_all
($vmid, $storage_cfg, $conf, 1);
2093 sub mountpoint_mount_path
{
2094 my ($mountpoint, $storage_cfg, $snapname) = @_;
2096 return mountpoint_mount
($mountpoint, undef, $storage_cfg, $snapname);
2099 my $check_mount_path = sub {
2101 $path = File
::Spec-
>canonpath($path);
2102 my $real = Cwd
::realpath
($path);
2103 if ($real ne $path) {
2104 die "mount path modified by symlink: $path != $real";
2108 # use $rootdir = undef to just return the corresponding mount path
2109 sub mountpoint_mount
{
2110 my ($mountpoint, $rootdir, $storage_cfg, $snapname) = @_;
2112 my $volid = $mountpoint->{volume
};
2113 my $mount = $mountpoint->{mp
};
2115 return if !$volid || !$mount;
2119 if (defined($rootdir)) {
2120 $rootdir =~ s!/+$!!;
2121 $mount_path = "$rootdir/$mount";
2122 $mount_path =~ s!/+!/!g;
2123 &$check_mount_path($mount_path);
2124 File
::Path
::mkpath
($mount_path);
2127 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2129 die "unknown snapshot path for '$volid'" if !$storage && defined($snapname);
2133 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
2134 my $path = PVE
::Storage
::path
($storage_cfg, $volid, $snapname);
2136 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2137 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2139 $format = 'iso' if $vtype eq 'iso'; # allow to handle iso files
2141 if ($format eq 'subvol') {
2144 if ($scfg->{type
} ne 'zfspool') {
2145 die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
2148 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $path, $mount_path]);
2150 return wantarray ?
($path, 0) : $path;
2151 } elsif ($format eq 'raw' || $format eq 'iso') {
2152 my $use_loopdev = 0;
2154 if ($scfg->{path
}) {
2155 push @extra_opts, '-o', 'loop';
2157 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm' || $scfg->{type
} eq 'rbd') {
2160 die "unsupported storage type '$scfg->{type}'\n";
2163 if ($format eq 'iso') {
2164 PVE
::Tools
::run_command
(['mount', '-o', 'ro', @extra_opts, $path, $mount_path]);
2165 } elsif ($isBase || defined($snapname)) {
2166 PVE
::Tools
::run_command
(['mount', '-o', 'ro,noload', @extra_opts, $path, $mount_path]);
2168 PVE
::Tools
::run_command
(['mount', @extra_opts, $path, $mount_path]);
2171 return wantarray ?
($path, $use_loopdev) : $path;
2173 die "unsupported image format '$format'\n";
2175 } elsif ($volid =~ m
|^/dev/.+|) {
2176 PVE
::Tools
::run_command
(['mount', $volid, $mount_path]) if $mount_path;
2177 return wantarray ?
($volid, 0) : $volid;
2178 } elsif ($volid !~ m
|^/dev/.+| && $volid =~ m
|^/.+| && -d
$volid) {
2179 &$check_mount_path($volid);
2180 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $volid, $mount_path]) if $mount_path;
2181 return wantarray ?
($volid, 0) : $volid;
2184 die "unsupported storage";
2187 sub get_vm_volumes
{
2188 my ($conf, $excludes) = @_;
2192 foreach_mountpoint
($conf, sub {
2193 my ($ms, $mountpoint) = @_;
2195 return if $excludes && $ms eq $excludes;
2197 my $volid = $mountpoint->{volume
};
2199 return if !$volid || $volid =~ m
|^/|;
2201 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2204 push @$vollist, $volid;
2211 my ($dev, $rootuid, $rootgid) = @_;
2213 PVE
::Tools
::run_command
(['mkfs.ext4', '-O', 'mmp',
2214 '-E', "root_owner=$rootuid:$rootgid",
2219 my ($storage_cfg, $volid, $rootuid, $rootgid) = @_;
2221 if ($volid =~ m!^/dev/.+!) {
2226 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2228 die "cannot format volume '$volid' with no storage\n" if !$storage;
2230 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
2232 my $path = PVE
::Storage
::path
($storage_cfg, $volid);
2234 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2235 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2237 die "cannot format volume '$volid' (format == $format)\n"
2238 if $format ne 'raw';
2240 mkfs
($path, $rootuid, $rootgid);
2244 my ($storecfg, $vollist) = @_;
2246 foreach my $volid (@$vollist) {
2247 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2253 my ($storecfg, $vmid, $settings, $conf) = @_;
2258 my (undef, $rootuid, $rootgid) = PVE
::LXC
::parse_id_maps
($conf);
2259 my $chown_vollist = [];
2261 foreach_mountpoint
($settings, sub {
2262 my ($ms, $mountpoint) = @_;
2264 my $volid = $mountpoint->{volume
};
2265 my $mp = $mountpoint->{mp
};
2267 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2269 return if !$storage;
2271 if ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/) {
2272 my ($storeid, $size_gb) = ($1, $2);
2274 my $size_kb = int(${size_gb
}*1024) * 1024;
2276 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2277 # fixme: use better naming ct-$vmid-disk-X.raw?
2279 if ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs') {
2281 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw',
2283 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2285 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2287 push @$chown_vollist, $volid;
2289 } elsif ($scfg->{type
} eq 'zfspool') {
2291 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2293 push @$chown_vollist, $volid;
2294 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm') {
2296 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2297 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2299 } elsif ($scfg->{type
} eq 'rbd') {
2301 die "krbd option must be enabled on storage type '$scfg->{type}'\n" if !$scfg->{krbd
};
2302 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2303 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2305 die "unable to create containers on storage type '$scfg->{type}'\n";
2307 push @$vollist, $volid;
2308 my $new_mountpoint = { volume
=> $volid, size
=> $size_kb*1024, mp
=> $mp };
2309 $conf->{$ms} = print_ct_mountpoint
($new_mountpoint, $ms eq 'rootfs');
2311 # use specified/existing volid
2315 PVE
::Storage
::activate_volumes
($storecfg, $chown_vollist, undef);
2316 foreach my $volid (@$chown_vollist) {
2317 my $path = PVE
::Storage
::path
($storecfg, $volid, undef);
2318 chown($rootuid, $rootgid, $path);
2320 PVE
::Storage
::deactivate_volumes
($storecfg, $chown_vollist, undef);
2322 # free allocated images on error
2324 destroy_disks
($storecfg, $vollist);
2330 # bash completion helper
2332 sub complete_os_templates
{
2333 my ($cmdname, $pname, $cvalue) = @_;
2335 my $cfg = PVE
::Storage
::config
();
2339 if ($cvalue =~ m/^([^:]+):/) {
2343 my $vtype = $cmdname eq 'restore' ?
'backup' : 'vztmpl';
2344 my $data = PVE
::Storage
::template_list
($cfg, $storeid, $vtype);
2347 foreach my $id (keys %$data) {
2348 foreach my $item (@{$data->{$id}}) {
2349 push @$res, $item->{volid
} if defined($item->{volid
});
2356 my $complete_ctid_full = sub {
2359 my $idlist = vmstatus
();
2361 my $active_hash = list_active_containers
();
2365 foreach my $id (keys %$idlist) {
2366 my $d = $idlist->{$id};
2367 if (defined($running)) {
2368 next if $d->{template
};
2369 next if $running && !$active_hash->{$id};
2370 next if !$running && $active_hash->{$id};
2379 return &$complete_ctid_full();
2382 sub complete_ctid_stopped
{
2383 return &$complete_ctid_full(0);
2386 sub complete_ctid_running
{
2387 return &$complete_ctid_full(1);
2397 my $lxc = $conf->{lxc
};
2398 foreach my $entry (@$lxc) {
2399 my ($key, $value) = @$entry;
2400 next if $key ne 'lxc.id_map';
2401 if ($value =~ /^([ug])\s+(\d+)\s+(\d+)\s+(\d+)\s*$/) {
2402 my ($type, $ct, $host, $length) = ($1, $2, $3, $4);
2403 push @$id_map, [$type, $ct, $host, $length];
2405 $rootuid = $host if $type eq 'u';
2406 $rootgid = $host if $type eq 'g';
2409 die "failed to parse id_map: $value\n";
2413 if (!@$id_map && $conf->{unprivileged
}) {
2414 # Should we read them from /etc/subuid?
2415 $id_map = [ ['u', '0', '100000', '65536'],
2416 ['g', '0', '100000', '65536'] ];
2417 $rootuid = $rootgid = 100000;
2420 return ($id_map, $rootuid, $rootgid);
2423 sub userns_command
{
2426 return ['lxc-usernsexec', (map { ('-m', join(':', @$_)) } @$id_map), '--'];