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 $raw .= "lxc.start.unshare = 1\n";
1074 # Should we read them from /etc/subuid?
1075 if ($unprivileged && !$custom_idmap) {
1076 $raw .= "lxc.id_map = u 0 100000 65536\n";
1077 $raw .= "lxc.id_map = g 0 100000 65536\n";
1080 if (!has_dev_console
($conf)) {
1081 $raw .= "lxc.console = none\n";
1082 $raw .= "lxc.cgroup.devices.deny = c 5:1 rwm\n";
1085 my $ttycount = get_tty_count
($conf);
1086 $raw .= "lxc.tty = $ttycount\n";
1088 # some init scripts expects a linux terminal (turnkey).
1089 $raw .= "lxc.environment = TERM=linux\n";
1091 my $utsname = $conf->{hostname
} || "CT$vmid";
1092 $raw .= "lxc.utsname = $utsname\n";
1094 my $memory = $conf->{memory
} || 512;
1095 my $swap = $conf->{swap
} // 0;
1097 my $lxcmem = int($memory*1024*1024);
1098 $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n";
1099 $raw .= "lxc.cgroup.memory.kmem.limit_in_bytes = $lxcmem\n";
1101 my $lxcswap = int(($memory + $swap)*1024*1024);
1102 $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n";
1104 if (my $cpulimit = $conf->{cpulimit
}) {
1105 $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n";
1106 my $value = int(100000*$cpulimit);
1107 $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n";
1110 my $shares = $conf->{cpuunits
} || 1024;
1111 $raw .= "lxc.cgroup.cpu.shares = $shares\n";
1113 my $mountpoint = parse_ct_mountpoint
($conf->{rootfs
});
1114 $mountpoint->{mp
} = '/';
1116 $raw .= "lxc.rootfs = $dir/rootfs\n";
1117 $raw .= "lxc.hook.stop = /usr/lib/x86_64-linux-gnu/lxc/hooks/unmount-namespace\n";
1120 foreach my $k (keys %$conf) {
1121 next if $k !~ m/^net(\d+)$/;
1123 my $d = parse_lxc_network
($conf->{$k});
1125 $raw .= "lxc.network.type = veth\n";
1126 $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
1127 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr
});
1128 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name
});
1129 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu
});
1132 if (my $lxcconf = $conf->{lxc
}) {
1133 foreach my $entry (@$lxcconf) {
1134 my ($k, $v) = @$entry;
1135 $netcount++ if $k eq 'lxc.network.type';
1136 $raw .= "$k = $v\n";
1140 $raw .= "lxc.network.type = empty\n" if !$netcount;
1142 File
::Path
::mkpath
("$dir/rootfs");
1144 PVE
::Tools
::file_set_contents
("$dir/config", $raw);
1147 # verify and cleanup nameserver list (replace \0 with ' ')
1148 sub verify_nameserver_list
{
1149 my ($nameserver_list) = @_;
1152 foreach my $server (PVE
::Tools
::split_list
($nameserver_list)) {
1153 PVE
::JSONSchema
::pve_verify_ip
($server);
1154 push @list, $server;
1157 return join(' ', @list);
1160 sub verify_searchdomain_list
{
1161 my ($searchdomain_list) = @_;
1164 foreach my $server (PVE
::Tools
::split_list
($searchdomain_list)) {
1165 # todo: should we add checks for valid dns domains?
1166 push @list, $server;
1169 return join(' ', @list);
1172 sub add_unused_volume
{
1173 my ($config, $volid) = @_;
1175 # skip bind mounts and block devices
1176 return if $volid =~ m
|^/|;
1179 for (my $ind = $MAX_UNUSED_DISKS - 1; $ind >= 0; $ind--) {
1180 my $test = "unused$ind";
1181 if (my $vid = $config->{$test}) {
1182 return if $vid eq $volid; # do not add duplicates
1188 die "To many unused volume - please delete them first.\n" if !$key;
1190 $config->{$key} = $volid;
1195 sub update_pct_config
{
1196 my ($vmid, $conf, $running, $param, $delete) = @_;
1201 my @deleted_volumes;
1205 my $pid = find_lxc_pid
($vmid);
1206 $rootdir = "/proc/$pid/root";
1209 my $hotplug_error = sub {
1211 push @nohotplug, @_;
1218 if (defined($delete)) {
1219 foreach my $opt (@$delete) {
1220 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
1221 die "unable to delete required option '$opt'\n";
1222 } elsif ($opt eq 'swap') {
1223 delete $conf->{$opt};
1224 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
1225 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
1226 delete $conf->{$opt};
1227 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
1228 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1229 next if $hotplug_error->($opt);
1230 delete $conf->{$opt};
1231 } elsif ($opt =~ m/^net(\d)$/) {
1232 delete $conf->{$opt};
1235 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
1236 } elsif ($opt eq 'protection') {
1237 delete $conf->{$opt};
1238 } elsif ($opt =~ m/^unused(\d+)$/) {
1239 next if $hotplug_error->($opt);
1240 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1241 push @deleted_volumes, $conf->{$opt};
1242 delete $conf->{$opt};
1243 } elsif ($opt =~ m/^mp(\d+)$/) {
1244 next if $hotplug_error->($opt);
1245 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1246 my $mountpoint = parse_ct_mountpoint
($conf->{$opt});
1247 add_unused_volume
($conf, $mountpoint->{volume
});
1248 delete $conf->{$opt};
1249 } elsif ($opt eq 'unprivileged') {
1250 die "unable to delete read-only option: '$opt'\n";
1254 write_config
($vmid, $conf) if $running;
1258 # There's no separate swap size to configure, there's memory and "total"
1259 # memory (iow. memory+swap). This means we have to change them together.
1260 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
1261 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
1262 if (defined($wanted_memory) || defined($wanted_swap)) {
1264 $wanted_memory //= ($conf->{memory
} || 512);
1265 $wanted_swap //= ($conf->{swap
} || 0);
1267 my $total = $wanted_memory + $wanted_swap;
1269 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1270 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1272 $conf->{memory
} = $wanted_memory;
1273 $conf->{swap
} = $wanted_swap;
1275 write_config
($vmid, $conf) if $running;
1278 foreach my $opt (keys %$param) {
1279 my $value = $param->{$opt};
1280 if ($opt eq 'hostname') {
1281 $conf->{$opt} = $value;
1282 } elsif ($opt eq 'onboot') {
1283 $conf->{$opt} = $value ?
1 : 0;
1284 } elsif ($opt eq 'startup') {
1285 $conf->{$opt} = $value;
1286 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1287 next if $hotplug_error->($opt);
1288 $conf->{$opt} = $value;
1289 } elsif ($opt eq 'nameserver') {
1290 next if $hotplug_error->($opt);
1291 my $list = verify_nameserver_list
($value);
1292 $conf->{$opt} = $list;
1293 } elsif ($opt eq 'searchdomain') {
1294 next if $hotplug_error->($opt);
1295 my $list = verify_searchdomain_list
($value);
1296 $conf->{$opt} = $list;
1297 } elsif ($opt eq 'cpulimit') {
1298 next if $hotplug_error->($opt); # FIXME: hotplug
1299 $conf->{$opt} = $value;
1300 } elsif ($opt eq 'cpuunits') {
1301 $conf->{$opt} = $value;
1302 write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
1303 } elsif ($opt eq 'description') {
1304 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
1305 } elsif ($opt =~ m/^net(\d+)$/) {
1307 my $net = parse_lxc_network
($value);
1309 $conf->{$opt} = print_lxc_network
($net);
1311 update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1313 } elsif ($opt eq 'protection') {
1314 $conf->{$opt} = $value ?
1 : 0;
1315 } elsif ($opt =~ m/^mp(\d+)$/) {
1316 next if $hotplug_error->($opt);
1317 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1318 $conf->{$opt} = $value;
1320 } elsif ($opt eq 'rootfs') {
1321 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1322 die "implement me: $opt";
1323 } elsif ($opt eq 'unprivileged') {
1324 die "unable to modify read-only option: '$opt'\n";
1326 die "implement me: $opt";
1328 write_config
($vmid, $conf) if $running;
1331 if (@deleted_volumes) {
1332 my $storage_cfg = PVE
::Storage
::config
();
1333 foreach my $volume (@deleted_volumes) {
1334 delete_mountpoint_volume
($storage_cfg, $vmid, $volume);
1339 my $storage_cfg = PVE
::Storage
::config
();
1340 create_disks
($storage_cfg, $vmid, $conf, $conf);
1343 # This should be the last thing we do here
1344 if ($running && scalar(@nohotplug)) {
1345 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1349 sub has_dev_console
{
1352 return !(defined($conf->{console
}) && !$conf->{console
});
1358 return $conf->{tty
} // $confdesc->{tty
}->{default};
1364 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1367 sub get_console_command
{
1368 my ($vmid, $conf) = @_;
1370 my $cmode = get_cmode
($conf);
1372 if ($cmode eq 'console') {
1373 return ['lxc-console', '-n', $vmid, '-t', 0];
1374 } elsif ($cmode eq 'tty') {
1375 return ['lxc-console', '-n', $vmid];
1376 } elsif ($cmode eq 'shell') {
1377 return ['lxc-attach', '--clear-env', '-n', $vmid];
1379 die "internal error";
1383 sub get_primary_ips
{
1386 # return data from net0
1388 return undef if !defined($conf->{net0
});
1389 my $net = parse_lxc_network
($conf->{net0
});
1391 my $ipv4 = $net->{ip
};
1393 if ($ipv4 =~ /^(dhcp|manual)$/) {
1399 my $ipv6 = $net->{ip6
};
1401 if ($ipv6 =~ /^(auto|dhcp|manual)$/) {
1408 return ($ipv4, $ipv6);
1411 sub delete_mountpoint_volume
{
1412 my ($storage_cfg, $vmid, $volume) = @_;
1414 # skip bind mounts and block devices
1415 if ($volume =~ m
|^/|) {
1419 my ($vtype, $name, $owner) = PVE
::Storage
::parse_volname
($storage_cfg, $volume);
1420 PVE
::Storage
::vdisk_free
($storage_cfg, $volume) if $vmid == $owner;
1423 sub destroy_lxc_container
{
1424 my ($storage_cfg, $vmid, $conf) = @_;
1426 foreach_mountpoint
($conf, sub {
1427 my ($ms, $mountpoint) = @_;
1428 delete_mountpoint_volume
($storage_cfg, $vmid, $mountpoint->{volume
});
1431 rmdir "/var/lib/lxc/$vmid/rootfs";
1432 unlink "/var/lib/lxc/$vmid/config";
1433 rmdir "/var/lib/lxc/$vmid";
1434 destroy_config
($vmid);
1436 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1437 #PVE::Tools::run_command($cmd);
1440 sub vm_stop_cleanup
{
1441 my ($storage_cfg, $vmid, $conf, $keepActive) = @_;
1446 my $vollist = get_vm_volumes
($conf);
1447 PVE
::Storage
::deactivate_volumes
($storage_cfg, $vollist);
1450 warn $@ if $@; # avoid errors - just warn
1453 my $safe_num_ne = sub {
1456 return 0 if !defined($a) && !defined($b);
1457 return 1 if !defined($a);
1458 return 1 if !defined($b);
1463 my $safe_string_ne = sub {
1466 return 0 if !defined($a) && !defined($b);
1467 return 1 if !defined($a);
1468 return 1 if !defined($b);
1474 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1476 if ($newnet->{type
} ne 'veth') {
1477 # for when there are physical interfaces
1478 die "cannot update interface of type $newnet->{type}";
1481 my $veth = "veth${vmid}i${netid}";
1482 my $eth = $newnet->{name
};
1484 if (my $oldnetcfg = $conf->{$opt}) {
1485 my $oldnet = parse_lxc_network
($oldnetcfg);
1487 if (&$safe_string_ne($oldnet->{hwaddr
}, $newnet->{hwaddr
}) ||
1488 &$safe_string_ne($oldnet->{name
}, $newnet->{name
})) {
1490 PVE
::Network
::veth_delete
($veth);
1491 delete $conf->{$opt};
1492 write_config
($vmid, $conf);
1494 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1496 } elsif (&$safe_string_ne($oldnet->{bridge
}, $newnet->{bridge
}) ||
1497 &$safe_num_ne($oldnet->{tag
}, $newnet->{tag
}) ||
1498 &$safe_num_ne($oldnet->{firewall
}, $newnet->{firewall
})) {
1500 if ($oldnet->{bridge
}) {
1501 PVE
::Network
::tap_unplug
($veth);
1502 foreach (qw(bridge tag firewall)) {
1503 delete $oldnet->{$_};
1505 $conf->{$opt} = print_lxc_network
($oldnet);
1506 write_config
($vmid, $conf);
1509 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1510 foreach (qw(bridge tag firewall)) {
1511 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1513 $conf->{$opt} = print_lxc_network
($oldnet);
1514 write_config
($vmid, $conf);
1517 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1520 update_ipconfig
($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1524 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
1526 my $veth = "veth${vmid}i${netid}";
1527 my $vethpeer = $veth . "p";
1528 my $eth = $newnet->{name
};
1530 PVE
::Network
::veth_create
($veth, $vethpeer, $newnet->{bridge
}, $newnet->{hwaddr
});
1531 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
});
1533 # attach peer in container
1534 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1535 PVE
::Tools
::run_command
($cmd);
1537 # link up peer in container
1538 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1539 PVE
::Tools
::run_command
($cmd);
1541 my $done = { type
=> 'veth' };
1542 foreach (qw(bridge tag firewall hwaddr name)) {
1543 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1545 $conf->{$opt} = print_lxc_network
($done);
1547 write_config
($vmid, $conf);
1550 sub update_ipconfig
{
1551 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1553 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
1555 my $optdata = parse_lxc_network
($conf->{$opt});
1559 my $cmdargs = shift;
1560 PVE
::Tools
::run_command
(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
1562 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
1564 my $change_ip_config = sub {
1565 my ($ipversion) = @_;
1567 my $family_opt = "-$ipversion";
1568 my $suffix = $ipversion == 4 ?
'' : $ipversion;
1569 my $gw= "gw$suffix";
1570 my $ip= "ip$suffix";
1572 my $newip = $newnet->{$ip};
1573 my $newgw = $newnet->{$gw};
1574 my $oldip = $optdata->{$ip};
1576 my $change_ip = &$safe_string_ne($oldip, $newip);
1577 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
1579 return if !$change_ip && !$change_gw;
1581 # step 1: add new IP, if this fails we cancel
1582 my $is_real_ip = ($newip && $newip !~ /^(?:auto|dhcp|manual)$/);
1583 if ($change_ip && $is_real_ip) {
1584 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
1591 # step 2: replace gateway
1592 # If this fails we delete the added IP and cancel.
1593 # If it succeeds we save the config and delete the old IP, ignoring
1594 # errors. The config is then saved.
1595 # Note: 'ip route replace' can add
1599 if ($is_real_ip && !PVE
::Network
::is_ip_in_cidr
($newgw, $newip, $ipversion)) {
1600 &$ipcmd($family_opt, 'route', 'add', $newgw, 'dev', $eth);
1602 &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw);
1606 # the route was not replaced, the old IP is still available
1607 # rollback (delete new IP) and cancel
1609 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
1610 warn $@ if $@; # no need to die here
1615 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
1616 # if the route was not deleted, the guest might have deleted it manually
1622 # from this point on we save the configuration
1623 # step 3: delete old IP ignoring errors
1624 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
1625 # We need to enable promote_secondaries, otherwise our newly added
1626 # address will be removed along with the old one.
1629 if ($ipversion == 4) {
1630 &$nscmd({ outfunc
=> sub { $promote = int(shift) } },
1631 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1632 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1634 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1636 warn $@ if $@; # no need to die here
1638 if ($ipversion == 4) {
1639 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1643 foreach my $property ($ip, $gw) {
1644 if ($newnet->{$property}) {
1645 $optdata->{$property} = $newnet->{$property};
1647 delete $optdata->{$property};
1650 $conf->{$opt} = print_lxc_network
($optdata);
1651 write_config
($vmid, $conf);
1652 $lxc_setup->setup_network($conf);
1655 &$change_ip_config(4);
1656 &$change_ip_config(6);
1660 # Internal snapshots
1662 # NOTE: Snapshot create/delete involves several non-atomic
1663 # action, and can take a long time.
1664 # So we try to avoid locking the file and use 'lock' variable
1665 # inside the config file instead.
1667 my $snapshot_copy_config = sub {
1668 my ($source, $dest) = @_;
1670 foreach my $k (keys %$source) {
1671 next if $k eq 'snapshots';
1672 next if $k eq 'snapstate';
1673 next if $k eq 'snaptime';
1674 next if $k eq 'vmstate';
1675 next if $k eq 'lock';
1676 next if $k eq 'digest';
1677 next if $k eq 'description';
1679 $dest->{$k} = $source->{$k};
1683 my $snapshot_prepare = sub {
1684 my ($vmid, $snapname, $comment) = @_;
1688 my $updatefn = sub {
1690 my $conf = load_config
($vmid);
1692 die "you can't take a snapshot if it's a template\n"
1693 if is_template
($conf);
1697 $conf->{lock} = 'snapshot';
1699 die "snapshot name '$snapname' already used\n"
1700 if defined($conf->{snapshots
}->{$snapname});
1702 my $storecfg = PVE
::Storage
::config
();
1703 die "snapshot feature is not available\n" if !has_feature
('snapshot', $conf, $storecfg);
1705 $snap = $conf->{snapshots
}->{$snapname} = {};
1707 &$snapshot_copy_config($conf, $snap);
1709 $snap->{'snapstate'} = "prepare";
1710 $snap->{'snaptime'} = time();
1711 $snap->{'description'} = $comment if $comment;
1712 $conf->{snapshots
}->{$snapname} = $snap;
1714 write_config
($vmid, $conf);
1717 lock_container
($vmid, 10, $updatefn);
1722 my $snapshot_commit = sub {
1723 my ($vmid, $snapname) = @_;
1725 my $updatefn = sub {
1727 my $conf = load_config
($vmid);
1729 die "missing snapshot lock\n"
1730 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
1732 die "snapshot '$snapname' does not exist\n"
1733 if !defined($conf->{snapshots
}->{$snapname});
1735 die "wrong snapshot state\n"
1736 if !($conf->{snapshots
}->{$snapname}->{'snapstate'} &&
1737 $conf->{snapshots
}->{$snapname}->{'snapstate'} eq "prepare");
1739 delete $conf->{snapshots
}->{$snapname}->{'snapstate'};
1740 delete $conf->{lock};
1741 $conf->{parent
} = $snapname;
1743 write_config
($vmid, $conf);
1746 lock_container
($vmid, 10 ,$updatefn);
1750 my ($feature, $conf, $storecfg, $snapname) = @_;
1754 foreach_mountpoint
($conf, sub {
1755 my ($ms, $mountpoint) = @_;
1757 return if $err; # skip further test
1759 $err = 1 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature, $mountpoint->{volume
}, $snapname);
1761 # TODO: implement support for mountpoints
1762 die "unable to handle mountpoint '$ms' - feature not implemented\n"
1766 return $err ?
0 : 1;
1769 sub snapshot_create
{
1770 my ($vmid, $snapname, $comment) = @_;
1772 my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
1774 my $conf = load_config
($vmid);
1776 my $running = check_running
($vmid);
1779 PVE
::Tools
::run_command
(['/usr/bin/lxc-freeze', '-n', $vmid]);
1780 PVE
::Tools
::run_command
(['/bin/sync']);
1783 my $storecfg = PVE
::Storage
::config
();
1784 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1785 my $volid = $rootinfo->{volume
};
1788 PVE
::Tools
::run_command
(['/usr/bin/lxc-unfreeze', '-n', $vmid]);
1791 PVE
::Storage
::volume_snapshot
($storecfg, $volid, $snapname);
1792 &$snapshot_commit($vmid, $snapname);
1795 snapshot_delete
($vmid, $snapname, 1);
1800 sub snapshot_delete
{
1801 my ($vmid, $snapname, $force) = @_;
1807 my $updatefn = sub {
1809 $conf = load_config
($vmid);
1811 die "you can't delete a snapshot if vm is a template\n"
1812 if is_template
($conf);
1814 $snap = $conf->{snapshots
}->{$snapname};
1818 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1820 $snap->{snapstate
} = 'delete';
1822 write_config
($vmid, $conf);
1825 lock_container
($vmid, 10, $updatefn);
1827 my $storecfg = PVE
::Storage
::config
();
1829 my $del_snap = sub {
1833 if ($conf->{parent
} eq $snapname) {
1834 if ($conf->{snapshots
}->{$snapname}->{snapname
}) {
1835 $conf->{parent
} = $conf->{snapshots
}->{$snapname}->{parent
};
1837 delete $conf->{parent
};
1841 delete $conf->{snapshots
}->{$snapname};
1843 write_config
($vmid, $conf);
1846 my $rootfs = $conf->{snapshots
}->{$snapname}->{rootfs
};
1847 my $rootinfo = parse_ct_mountpoint
($rootfs);
1848 my $volid = $rootinfo->{volume
};
1851 PVE
::Storage
::volume_snapshot_delete
($storecfg, $volid, $snapname);
1855 if(!$err || ($err && $force)) {
1856 lock_container
($vmid, 10, $del_snap);
1858 die "Can't delete snapshot: $vmid $snapname $err\n";
1863 sub snapshot_rollback
{
1864 my ($vmid, $snapname) = @_;
1866 my $storecfg = PVE
::Storage
::config
();
1868 my $conf = load_config
($vmid);
1870 die "you can't rollback if vm is a template\n" if is_template
($conf);
1872 my $snap = $conf->{snapshots
}->{$snapname};
1874 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1876 my $rootfs = $snap->{rootfs
};
1877 my $rootinfo = parse_ct_mountpoint
($rootfs);
1878 my $volid = $rootinfo->{volume
};
1880 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $volid, $snapname);
1882 my $updatefn = sub {
1884 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
1885 if $snap->{snapstate
};
1889 system("lxc-stop -n $vmid --kill") if check_running
($vmid);
1891 die "unable to rollback vm $vmid: vm is running\n"
1892 if check_running
($vmid);
1894 $conf->{lock} = 'rollback';
1898 # copy snapshot config to current config
1900 my $tmp_conf = $conf;
1901 &$snapshot_copy_config($tmp_conf->{snapshots
}->{$snapname}, $conf);
1902 $conf->{snapshots
} = $tmp_conf->{snapshots
};
1903 delete $conf->{snaptime
};
1904 delete $conf->{snapname
};
1905 $conf->{parent
} = $snapname;
1907 write_config
($vmid, $conf);
1910 my $unlockfn = sub {
1911 delete $conf->{lock};
1912 write_config
($vmid, $conf);
1915 lock_container
($vmid, 10, $updatefn);
1917 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $volid, $snapname);
1919 lock_container
($vmid, 5, $unlockfn);
1922 sub template_create
{
1923 my ($vmid, $conf) = @_;
1925 my $storecfg = PVE
::Storage
::config
();
1927 my $rootinfo = parse_ct_mountpoint
($conf->{rootfs
});
1928 my $volid = $rootinfo->{volume
};
1930 die "Template feature is not available for '$volid'\n"
1931 if !PVE
::Storage
::volume_has_feature
($storecfg, 'template', $volid);
1933 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
1935 my $template_volid = PVE
::Storage
::vdisk_create_base
($storecfg, $volid);
1936 $rootinfo->{volume
} = $template_volid;
1937 $conf->{rootfs
} = print_ct_mountpoint
($rootinfo, 1);
1939 write_config
($vmid, $conf);
1945 return 1 if defined $conf->{template
} && $conf->{template
} == 1;
1948 sub mountpoint_names
{
1951 my @names = ('rootfs');
1953 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
1954 push @names, "mp$i";
1957 return $reverse ?
reverse @names : @names;
1960 # The container might have *different* symlinks than the host. realpath/abs_path
1961 # use the actual filesystem to resolve links.
1962 sub sanitize_mountpoint
{
1964 $mp = '/' . $mp; # we always start with a slash
1965 $mp =~ s
@/{2,}@/@g; # collapse sequences of slashes
1966 $mp =~ s
@/\./@@g; # collapse /./
1967 $mp =~ s
@/\.(/)?
$@$1@; # collapse a trailing /. or /./
1968 $mp =~ s
@(.*)/[^/]+/\.\./@$1/@g; # collapse /../ without regard for symlinks
1969 $mp =~ s
@/\.\
.(/)?
$@$1@; # collapse trailing /.. or /../ disregarding symlinks
1973 sub foreach_mountpoint_full
{
1974 my ($conf, $reverse, $func) = @_;
1976 foreach my $key (mountpoint_names
($reverse)) {
1977 my $value = $conf->{$key};
1978 next if !defined($value);
1979 my $mountpoint = parse_ct_mountpoint
($value, 1);
1980 next if !defined($mountpoint);
1982 # just to be sure: rootfs is /
1983 my $path = $key eq 'rootfs' ?
'/' : $mountpoint->{mp
};
1984 $mountpoint->{mp
} = sanitize_mountpoint
($path);
1986 $path = $mountpoint->{volume
};
1987 $mountpoint->{volume
} = sanitize_mountpoint
($path) if $path =~ m
|^/|;
1989 &$func($key, $mountpoint);
1993 sub foreach_mountpoint
{
1994 my ($conf, $func) = @_;
1996 foreach_mountpoint_full
($conf, 0, $func);
1999 sub foreach_mountpoint_reverse
{
2000 my ($conf, $func) = @_;
2002 foreach_mountpoint_full
($conf, 1, $func);
2005 sub check_ct_modify_config_perm
{
2006 my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
2008 return 1 if $authuser ne 'root@pam';
2010 foreach my $opt (@$key_list) {
2012 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
2013 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
2014 } elsif ($opt eq 'rootfs' || $opt =~ /^mp\d+$/) {
2015 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
2016 } elsif ($opt eq 'memory' || $opt eq 'swap') {
2017 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
2018 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
2019 $opt eq 'searchdomain' || $opt eq 'hostname') {
2020 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
2022 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
2030 my ($vmid, $storage_cfg, $conf, $noerr) = @_;
2032 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2033 my $volid_list = get_vm_volumes
($conf);
2035 foreach_mountpoint_reverse
($conf, sub {
2036 my ($ms, $mountpoint) = @_;
2038 my $volid = $mountpoint->{volume
};
2039 my $mount = $mountpoint->{mp
};
2041 return if !$volid || !$mount;
2043 my $mount_path = "$rootdir/$mount";
2044 $mount_path =~ s!/+!/!g;
2046 return if !PVE
::ProcFSTools
::is_mounted
($mount_path);
2049 PVE
::Tools
::run_command
(['umount', '-d', $mount_path]);
2062 my ($vmid, $storage_cfg, $conf) = @_;
2064 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2065 File
::Path
::make_path
($rootdir);
2067 my $volid_list = get_vm_volumes
($conf);
2068 PVE
::Storage
::activate_volumes
($storage_cfg, $volid_list);
2071 foreach_mountpoint
($conf, sub {
2072 my ($ms, $mountpoint) = @_;
2074 my $volid = $mountpoint->{volume
};
2075 my $mount = $mountpoint->{mp
};
2077 return if !$volid || !$mount;
2079 my $image_path = PVE
::Storage
::path
($storage_cfg, $volid);
2080 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2081 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2083 die "unable to mount base volume - internal error" if $isBase;
2085 mountpoint_mount
($mountpoint, $rootdir, $storage_cfg);
2089 warn "mounting container failed - $err";
2090 umount_all
($vmid, $storage_cfg, $conf, 1);
2097 sub mountpoint_mount_path
{
2098 my ($mountpoint, $storage_cfg, $snapname) = @_;
2100 return mountpoint_mount
($mountpoint, undef, $storage_cfg, $snapname);
2103 my $check_mount_path = sub {
2105 $path = File
::Spec-
>canonpath($path);
2106 my $real = Cwd
::realpath
($path);
2107 if ($real ne $path) {
2108 die "mount path modified by symlink: $path != $real";
2112 # use $rootdir = undef to just return the corresponding mount path
2113 sub mountpoint_mount
{
2114 my ($mountpoint, $rootdir, $storage_cfg, $snapname) = @_;
2116 my $volid = $mountpoint->{volume
};
2117 my $mount = $mountpoint->{mp
};
2119 return if !$volid || !$mount;
2123 if (defined($rootdir)) {
2124 $rootdir =~ s!/+$!!;
2125 $mount_path = "$rootdir/$mount";
2126 $mount_path =~ s!/+!/!g;
2127 &$check_mount_path($mount_path);
2128 File
::Path
::mkpath
($mount_path);
2131 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2133 die "unknown snapshot path for '$volid'" if !$storage && defined($snapname);
2137 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
2138 my $path = PVE
::Storage
::path
($storage_cfg, $volid, $snapname);
2140 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2141 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2143 $format = 'iso' if $vtype eq 'iso'; # allow to handle iso files
2145 if ($format eq 'subvol') {
2148 if ($scfg->{type
} ne 'zfspool') {
2149 die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
2152 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $path, $mount_path]);
2154 return wantarray ?
($path, 0) : $path;
2155 } elsif ($format eq 'raw' || $format eq 'iso') {
2156 my $use_loopdev = 0;
2158 if ($scfg->{path
}) {
2159 push @extra_opts, '-o', 'loop';
2161 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm' || $scfg->{type
} eq 'rbd') {
2164 die "unsupported storage type '$scfg->{type}'\n";
2167 if ($format eq 'iso') {
2168 PVE
::Tools
::run_command
(['mount', '-o', 'ro', @extra_opts, $path, $mount_path]);
2169 } elsif ($isBase || defined($snapname)) {
2170 PVE
::Tools
::run_command
(['mount', '-o', 'ro,noload', @extra_opts, $path, $mount_path]);
2172 PVE
::Tools
::run_command
(['mount', @extra_opts, $path, $mount_path]);
2175 return wantarray ?
($path, $use_loopdev) : $path;
2177 die "unsupported image format '$format'\n";
2179 } elsif ($volid =~ m
|^/dev/.+|) {
2180 PVE
::Tools
::run_command
(['mount', $volid, $mount_path]) if $mount_path;
2181 return wantarray ?
($volid, 0) : $volid;
2182 } elsif ($volid !~ m
|^/dev/.+| && $volid =~ m
|^/.+| && -d
$volid) {
2183 &$check_mount_path($volid);
2184 PVE
::Tools
::run_command
(['mount', '-o', 'bind', $volid, $mount_path]) if $mount_path;
2185 return wantarray ?
($volid, 0) : $volid;
2188 die "unsupported storage";
2191 sub get_vm_volumes
{
2192 my ($conf, $excludes) = @_;
2196 foreach_mountpoint
($conf, sub {
2197 my ($ms, $mountpoint) = @_;
2199 return if $excludes && $ms eq $excludes;
2201 my $volid = $mountpoint->{volume
};
2203 return if !$volid || $volid =~ m
|^/|;
2205 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2208 push @$vollist, $volid;
2215 my ($dev, $rootuid, $rootgid) = @_;
2217 PVE
::Tools
::run_command
(['mkfs.ext4', '-O', 'mmp',
2218 '-E', "root_owner=$rootuid:$rootgid",
2223 my ($storage_cfg, $volid, $rootuid, $rootgid) = @_;
2225 if ($volid =~ m!^/dev/.+!) {
2230 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2232 die "cannot format volume '$volid' with no storage\n" if !$storage;
2234 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
2236 my $path = PVE
::Storage
::path
($storage_cfg, $volid);
2238 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2239 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2241 die "cannot format volume '$volid' (format == $format)\n"
2242 if $format ne 'raw';
2244 mkfs
($path, $rootuid, $rootgid);
2248 my ($storecfg, $vollist) = @_;
2250 foreach my $volid (@$vollist) {
2251 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2257 my ($storecfg, $vmid, $settings, $conf) = @_;
2262 my (undef, $rootuid, $rootgid) = PVE
::LXC
::parse_id_maps
($conf);
2263 my $chown_vollist = [];
2265 foreach_mountpoint
($settings, sub {
2266 my ($ms, $mountpoint) = @_;
2268 my $volid = $mountpoint->{volume
};
2269 my $mp = $mountpoint->{mp
};
2271 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2273 return if !$storage;
2275 if ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/) {
2276 my ($storeid, $size_gb) = ($1, $2);
2278 my $size_kb = int(${size_gb
}*1024) * 1024;
2280 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2281 # fixme: use better naming ct-$vmid-disk-X.raw?
2283 if ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs') {
2285 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw',
2287 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2289 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2291 push @$chown_vollist, $volid;
2293 } elsif ($scfg->{type
} eq 'zfspool') {
2295 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2297 push @$chown_vollist, $volid;
2298 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm') {
2300 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2301 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2303 } elsif ($scfg->{type
} eq 'rbd') {
2305 die "krbd option must be enabled on storage type '$scfg->{type}'\n" if !$scfg->{krbd
};
2306 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2307 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2309 die "unable to create containers on storage type '$scfg->{type}'\n";
2311 push @$vollist, $volid;
2312 my $new_mountpoint = { volume
=> $volid, size
=> $size_kb*1024, mp
=> $mp };
2313 $conf->{$ms} = print_ct_mountpoint
($new_mountpoint, $ms eq 'rootfs');
2315 # use specified/existing volid
2319 PVE
::Storage
::activate_volumes
($storecfg, $chown_vollist, undef);
2320 foreach my $volid (@$chown_vollist) {
2321 my $path = PVE
::Storage
::path
($storecfg, $volid, undef);
2322 chown($rootuid, $rootgid, $path);
2324 PVE
::Storage
::deactivate_volumes
($storecfg, $chown_vollist, undef);
2326 # free allocated images on error
2328 destroy_disks
($storecfg, $vollist);
2334 # bash completion helper
2336 sub complete_os_templates
{
2337 my ($cmdname, $pname, $cvalue) = @_;
2339 my $cfg = PVE
::Storage
::config
();
2343 if ($cvalue =~ m/^([^:]+):/) {
2347 my $vtype = $cmdname eq 'restore' ?
'backup' : 'vztmpl';
2348 my $data = PVE
::Storage
::template_list
($cfg, $storeid, $vtype);
2351 foreach my $id (keys %$data) {
2352 foreach my $item (@{$data->{$id}}) {
2353 push @$res, $item->{volid
} if defined($item->{volid
});
2360 my $complete_ctid_full = sub {
2363 my $idlist = vmstatus
();
2365 my $active_hash = list_active_containers
();
2369 foreach my $id (keys %$idlist) {
2370 my $d = $idlist->{$id};
2371 if (defined($running)) {
2372 next if $d->{template
};
2373 next if $running && !$active_hash->{$id};
2374 next if !$running && $active_hash->{$id};
2383 return &$complete_ctid_full();
2386 sub complete_ctid_stopped
{
2387 return &$complete_ctid_full(0);
2390 sub complete_ctid_running
{
2391 return &$complete_ctid_full(1);
2401 my $lxc = $conf->{lxc
};
2402 foreach my $entry (@$lxc) {
2403 my ($key, $value) = @$entry;
2404 next if $key ne 'lxc.id_map';
2405 if ($value =~ /^([ug])\s+(\d+)\s+(\d+)\s+(\d+)\s*$/) {
2406 my ($type, $ct, $host, $length) = ($1, $2, $3, $4);
2407 push @$id_map, [$type, $ct, $host, $length];
2409 $rootuid = $host if $type eq 'u';
2410 $rootgid = $host if $type eq 'g';
2413 die "failed to parse id_map: $value\n";
2417 if (!@$id_map && $conf->{unprivileged
}) {
2418 # Should we read them from /etc/subuid?
2419 $id_map = [ ['u', '0', '100000', '65536'],
2420 ['g', '0', '100000', '65536'] ];
2421 $rootuid = $rootgid = 100000;
2424 return ($id_map, $rootuid, $rootgid);
2427 sub userns_command
{
2430 return ['lxc-usernsexec', (map { ('-m', join(':', @$_)) } @$id_map), '--'];