12 use Fcntl
qw(O_RDONLY);
14 use PVE
::Cluster
qw(cfs_register_file cfs_read_file);
15 use PVE
::Exception
qw(raise_perm_exc);
19 use PVE
::JSONSchema
qw(get_standard_option);
20 use PVE
::Tools
qw($IPV6RE $IPV4RE dir_glob_foreach lock_file lock_file_full);
22 use PVE
::AccessControl
;
24 use Time
::HiRes qw
(gettimeofday
);
28 my $nodename = PVE
::INotify
::nodename
();
30 my $cpuinfo= PVE
::ProcFSTools
::read_cpuinfo
();
32 our $COMMON_TAR_FLAGS = [ '--sparse', '--numeric-owner', '--acls',
34 '--xattrs-include=user.*',
35 '--xattrs-include=security.capability',
36 '--warning=no-xattr-write' ];
38 cfs_register_file
('/lxc/', \
&parse_pct_config
, \
&write_pct_config
);
44 format
=> 'pve-lxc-mp-string',
45 format_description
=> 'volume',
46 description
=> 'Volume, device or directory to mount into the container.',
50 format_description
=> '[1|0]',
51 description
=> 'Whether to include the mountpoint in backups.',
56 format
=> 'disk-size',
57 format_description
=> 'DiskSize',
58 description
=> 'Volume size (read only value).',
63 format_description
=> 'acl',
64 description
=> 'Explicitly enable or disable ACL support.',
69 format_description
=> 'ro',
70 description
=> 'Read-only mountpoint (not supported with bind mounts)',
75 format_description
=> '[0|1]',
76 description
=> 'Enable user quotas inside the container (not supported with zfs subvolumes)',
81 PVE
::JSONSchema
::register_standard_option
('pve-ct-rootfs', {
82 type
=> 'string', format
=> $rootfs_desc,
83 description
=> "Use volume as container root.",
87 PVE
::JSONSchema
::register_standard_option
('pve-lxc-snapshot-name', {
88 description
=> "The name of the snapshot.",
89 type
=> 'string', format
=> 'pve-configid',
97 description
=> "Lock/unlock the VM.",
98 enum
=> [qw(migrate backup snapshot rollback)],
103 description
=> "Specifies whether a VM will be started during system bootup.",
106 startup
=> get_standard_option
('pve-startup-order'),
110 description
=> "Enable/disable Template.",
116 enum
=> ['amd64', 'i386'],
117 description
=> "OS architecture type.",
123 enum
=> ['debian', 'ubuntu', 'centos', 'fedora', 'opensuse', 'archlinux', 'alpine', 'unmanaged'],
124 description
=> "OS type. This is used to setup configuration inside the container, and corresponds to lxc setup scripts in /usr/share/lxc/config/<ostype>.common.conf. Value 'unmanaged' can be used to skip and OS specific setup.",
129 description
=> "Attach a console device (/dev/console) to the container.",
135 description
=> "Specify the number of tty available to the container",
143 description
=> "Limit of CPU usage. Note if the computer has 2 CPUs, it has a total of '2' CPU time. Value '0' indicates no CPU limit.",
151 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 the weights of all the other running VMs.\n\nNOTE: You can disable fair-scheduler configuration by setting this to 0.",
159 description
=> "Amount of RAM for the VM in MB.",
166 description
=> "Amount of SWAP for the VM in MB.",
172 description
=> "Set a host name for the container.",
173 type
=> 'string', format
=> 'dns-name',
179 description
=> "Container description. Only used on the configuration web interface.",
183 type
=> 'string', format
=> 'dns-name-list',
184 description
=> "Sets DNS search domains for a container. Create will automatically use the setting from the host if you neither set searchdomain nor nameserver.",
188 type
=> 'string', format
=> 'address-list',
189 description
=> "Sets DNS server IP address for a container. Create will automatically use the setting from the host if you neither set searchdomain nor nameserver.",
191 rootfs
=> get_standard_option
('pve-ct-rootfs'),
194 type
=> 'string', format
=> 'pve-configid',
196 description
=> "Parent snapshot name. This is used internally, and should not be modified.",
200 description
=> "Timestamp for snapshots.",
206 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).",
208 enum
=> ['shell', 'console', 'tty'],
214 description
=> "Sets the protection flag of the container. This will prevent the CT or CT's disk remove/update operation.",
220 description
=> "Makes the container run as unprivileged user. (Should not be modified manually.)",
225 my $valid_lxc_conf_keys = {
229 'lxc.haltsignal' => 1,
230 'lxc.rebootsignal' => 1,
231 'lxc.stopsignal' => 1,
233 'lxc.network.type' => 1,
234 'lxc.network.flags' => 1,
235 'lxc.network.link' => 1,
236 'lxc.network.mtu' => 1,
237 'lxc.network.name' => 1,
238 'lxc.network.hwaddr' => 1,
239 'lxc.network.ipv4' => 1,
240 'lxc.network.ipv4.gateway' => 1,
241 'lxc.network.ipv6' => 1,
242 'lxc.network.ipv6.gateway' => 1,
243 'lxc.network.script.up' => 1,
244 'lxc.network.script.down' => 1,
246 'lxc.console.logfile' => 1,
249 'lxc.devttydir' => 1,
250 'lxc.hook.autodev' => 1,
254 'lxc.mount.entry' => 1,
255 'lxc.mount.auto' => 1,
256 'lxc.rootfs' => 'lxc.rootfs is auto generated from rootfs',
257 'lxc.rootfs.mount' => 1,
258 'lxc.rootfs.options' => 'lxc.rootfs.options is not supported' .
259 ', please use mountpoint options in the "rootfs" key',
263 'lxc.aa_profile' => 1,
264 'lxc.aa_allow_incomplete' => 1,
265 'lxc.se_context' => 1,
268 'lxc.hook.pre-start' => 1,
269 'lxc.hook.pre-mount' => 1,
270 'lxc.hook.mount' => 1,
271 'lxc.hook.start' => 1,
272 'lxc.hook.stop' => 1,
273 'lxc.hook.post-stop' => 1,
274 'lxc.hook.clone' => 1,
275 'lxc.hook.destroy' => 1,
278 'lxc.start.auto' => 1,
279 'lxc.start.delay' => 1,
280 'lxc.start.order' => 1,
282 'lxc.environment' => 1,
289 description
=> "Network interface type.",
294 format_description
=> 'String',
295 description
=> 'Name of the network device as seen from inside the container. (lxc.network.name)',
296 pattern
=> '[-_.\w\d]+',
300 format_description
=> 'vmbr<Number>',
301 description
=> 'Bridge to attach the network device to.',
302 pattern
=> '[-_.\w\d]+',
307 format_description
=> 'MAC',
308 description
=> 'Bridge to attach the network device to. (lxc.network.hwaddr)',
309 pattern
=> qr/(?:[a-f0-9]{2}:){5}[a-f0-9]{2}/i,
314 format_description
=> 'Number',
315 description
=> 'Maximum transfer unit of the interface. (lxc.network.mtu)',
316 minimum
=> 64, # minimum ethernet frame is 64 bytes
321 format
=> 'pve-ipv4-config',
322 format_description
=> 'IPv4Format/CIDR',
323 description
=> 'IPv4 address in CIDR format.',
329 format_description
=> 'GatewayIPv4',
330 description
=> 'Default gateway for IPv4 traffic.',
335 format
=> 'pve-ipv6-config',
336 format_description
=> 'IPv6Format/CIDR',
337 description
=> 'IPv6 address in CIDR format.',
343 format_description
=> 'GatewayIPv6',
344 description
=> 'Default gateway for IPv6 traffic.',
349 format_description
=> '[1|0]',
350 description
=> "Controls whether this interface's firewall rules should be used.",
355 format_description
=> 'VlanNo',
358 description
=> "VLAN tag for this interface.",
363 pattern
=> qr/\d+(?:;\d+)*/,
364 format_description
=> 'vlanid[;vlanid...]',
365 description
=> "VLAN ids to pass through the interface",
369 PVE
::JSONSchema
::register_format
('pve-lxc-network', $netconf_desc);
371 my $MAX_LXC_NETWORKS = 10;
372 for (my $i = 0; $i < $MAX_LXC_NETWORKS; $i++) {
373 $confdesc->{"net$i"} = {
375 type
=> 'string', format
=> $netconf_desc,
376 description
=> "Specifies network interfaces for the container.",
380 PVE
::JSONSchema
::register_format
('pve-lxc-mp-string', \
&verify_lxc_mp_string
);
381 sub verify_lxc_mp_string
{
382 my ($mp, $noerr) = @_;
386 # /. or /.. at the end
387 # ../ at the beginning
389 if($mp =~ m
@/\.\
.?
/@ ||
392 return undef if $noerr;
393 die "$mp contains illegal character sequences\n";
402 format
=> 'pve-lxc-mp-string',
403 format_description
=> 'Path',
404 description
=> 'Path to the mountpoint as seen from inside the container.',
407 PVE
::JSONSchema
::register_format
('pve-ct-mountpoint', $mp_desc);
411 type
=> 'string', format
=> 'pve-volume-id',
412 description
=> "Reference to unused volumes.",
415 my $MAX_MOUNT_POINTS = 10;
416 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
417 $confdesc->{"mp$i"} = {
419 type
=> 'string', format
=> $mp_desc,
420 description
=> "Use volume as container mount point (experimental feature).",
425 my $MAX_UNUSED_DISKS = $MAX_MOUNT_POINTS;
426 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
427 $confdesc->{"unused$i"} = $unuseddesc;
430 sub write_pct_config
{
431 my ($filename, $conf) = @_;
433 delete $conf->{snapstate
}; # just to be sure
434 my $volidlist = get_vm_volumes
($conf);
435 my $used_volids = {};
436 foreach my $vid (@$volidlist) {
437 $used_volids->{$vid} = 1;
440 # remove 'unusedX' settings if the volume is still used
441 foreach my $key (keys %$conf) {
442 my $value = $conf->{$key};
443 if ($key =~ m/^unused/ && $used_volids->{$value}) {
444 delete $conf->{$key};
448 my $generate_raw_config = sub {
453 # add description as comment to top of file
454 my $descr = $conf->{description
} || '';
455 foreach my $cl (split(/\n/, $descr)) {
456 $raw .= '#' . PVE
::Tools
::encode_text
($cl) . "\n";
459 foreach my $key (sort keys %$conf) {
460 next if $key eq 'digest' || $key eq 'description' || $key eq 'pending' ||
461 $key eq 'snapshots' || $key eq 'snapname' || $key eq 'lxc';
462 my $value = $conf->{$key};
463 die "detected invalid newline inside property '$key'\n" if $value =~ m/\n/;
464 $raw .= "$key: $value\n";
467 if (my $lxcconf = $conf->{lxc
}) {
468 foreach my $entry (@$lxcconf) {
469 my ($k, $v) = @$entry;
477 my $raw = &$generate_raw_config($conf);
479 foreach my $snapname (sort keys %{$conf->{snapshots
}}) {
480 $raw .= "\n[$snapname]\n";
481 $raw .= &$generate_raw_config($conf->{snapshots
}->{$snapname});
488 my ($key, $value) = @_;
490 die "unknown setting '$key'\n" if !$confdesc->{$key};
492 my $type = $confdesc->{$key}->{type
};
494 if (!defined($value)) {
495 die "got undefined value\n";
498 if ($value =~ m/[\n\r]/) {
499 die "property contains a line feed\n";
502 if ($type eq 'boolean') {
503 return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i);
504 return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i);
505 die "type check ('boolean') failed - got '$value'\n";
506 } elsif ($type eq 'integer') {
507 return int($1) if $value =~ m/^(\d+)$/;
508 die "type check ('integer') failed - got '$value'\n";
509 } elsif ($type eq 'number') {
510 return $value if $value =~ m/^(\d+)(\.\d+)?$/;
511 die "type check ('number') failed - got '$value'\n";
512 } elsif ($type eq 'string') {
513 if (my $fmt = $confdesc->{$key}->{format
}) {
514 PVE
::JSONSchema
::check_format
($fmt, $value);
523 sub parse_pct_config
{
524 my ($filename, $raw) = @_;
526 return undef if !defined($raw);
529 digest
=> Digest
::SHA
::sha1_hex
($raw),
533 $filename =~ m
|/lxc/(\d
+).conf
$|
534 || die "got strange filename '$filename'";
542 my @lines = split(/\n/, $raw);
543 foreach my $line (@lines) {
544 next if $line =~ m/^\s*$/;
546 if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
548 $conf->{description
} = $descr if $descr;
550 $conf = $res->{snapshots
}->{$section} = {};
554 if ($line =~ m/^\#(.*)\s*$/) {
555 $descr .= PVE
::Tools
::decode_text
($1) . "\n";
559 if ($line =~ m/^(lxc\.[a-z0-9_\-\.]+)(:|\s*=)\s*(.*?)\s*$/) {
562 my $validity = $valid_lxc_conf_keys->{$key} || 0;
563 if ($validity eq 1 || $key =~ m/^lxc\.cgroup\./) {
564 push @{$conf->{lxc
}}, [$key, $value];
565 } elsif (my $errmsg = $validity) {
566 warn "vm $vmid - $key: $errmsg\n";
568 warn "vm $vmid - unable to parse config: $line\n";
570 } elsif ($line =~ m/^(description):\s*(.*\S)\s*$/) {
571 $descr .= PVE
::Tools
::decode_text
($2);
572 } elsif ($line =~ m/snapstate:\s*(prepare|delete)\s*$/) {
573 $conf->{snapstate
} = $1;
574 } elsif ($line =~ m/^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) {
577 eval { $value = check_type
($key, $value); };
578 warn "vm $vmid - unable to parse value of '$key' - $@" if $@;
579 $conf->{$key} = $value;
581 warn "vm $vmid - unable to parse config: $line\n";
585 $conf->{description
} = $descr if $descr;
587 delete $res->{snapstate
}; # just to be sure
593 my $vmlist = PVE
::Cluster
::get_vmlist
();
595 return $res if !$vmlist || !$vmlist->{ids
};
596 my $ids = $vmlist->{ids
};
598 foreach my $vmid (keys %$ids) {
599 next if !$vmid; # skip CT0
600 my $d = $ids->{$vmid};
601 next if !$d->{node
} || $d->{node
} ne $nodename;
602 next if !$d->{type
} || $d->{type
} ne 'lxc';
603 $res->{$vmid}->{type
} = 'lxc';
608 sub cfs_config_path
{
609 my ($vmid, $node) = @_;
611 $node = $nodename if !$node;
612 return "nodes/$node/lxc/$vmid.conf";
616 my ($vmid, $node) = @_;
618 my $cfspath = cfs_config_path
($vmid, $node);
619 return "/etc/pve/$cfspath";
623 my ($vmid, $node) = @_;
625 $node = $nodename if !$node;
626 my $cfspath = cfs_config_path
($vmid, $node);
628 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath);
629 die "container $vmid does not exist\n" if !defined($conf);
635 my ($vmid, $conf) = @_;
637 my $dir = "/etc/pve/nodes/$nodename/lxc";
640 write_config
($vmid, $conf);
646 unlink config_file
($vmid, $nodename);
650 my ($vmid, $conf) = @_;
652 my $cfspath = cfs_config_path
($vmid);
654 PVE
::Cluster
::cfs_write_file
($cfspath, $conf);
657 # flock: we use one file handle per process, so lock file
658 # can be called multiple times and will succeed for the same process.
660 my $lock_handles = {};
661 my $lockdir = "/run/lock/lxc";
663 sub config_file_lock
{
666 return "$lockdir/pve-config-${vmid}.lock";
669 sub lock_config_full
{
670 my ($vmid, $timeout, $code, @param) = @_;
672 my $filename = config_file_lock
($vmid);
674 mkdir $lockdir if !-d
$lockdir;
676 my $res = lock_file
($filename, $timeout, $code, @param);
683 sub lock_config_mode
{
684 my ($vmid, $timeout, $shared, $code, @param) = @_;
686 my $filename = config_file_lock
($vmid);
688 mkdir $lockdir if !-d
$lockdir;
690 my $res = lock_file_full
($filename, $timeout, $shared, $code, @param);
698 my ($vmid, $code, @param) = @_;
700 return lock_config_full
($vmid, 10, $code, @param);
706 return defined($confdesc->{$name});
709 # add JSON properties for create and set function
710 sub json_config_properties
{
713 foreach my $opt (keys %$confdesc) {
714 next if $opt eq 'parent' || $opt eq 'snaptime';
715 next if $prop->{$opt};
716 $prop->{$opt} = $confdesc->{$opt};
722 # container status helpers
724 sub list_active_containers
{
726 my $filename = "/proc/net/unix";
728 # similar test is used by lcxcontainers.c: list_active_containers
731 my $fh = IO
::File-
>new ($filename, "r");
734 while (defined(my $line = <$fh>)) {
735 if ($line =~ m/^[a-f0-9]+:\s\S+\s\S+\s\S+\s\S+\s\S+\s\d+\s(\S+)$/) {
737 if ($path =~ m!^@/var/lib/lxc/(\d+)/command$!) {
748 # warning: this is slow
752 my $active_hash = list_active_containers
();
754 return 1 if defined($active_hash->{$vmid});
759 sub get_container_disk_usage
{
760 my ($vmid, $pid) = @_;
762 return PVE
::Tools
::df
("/proc/$pid/root/", 1);
765 my $last_proc_vmid_stat;
767 my $parse_cpuacct_stat = sub {
770 my $raw = read_cgroup_value
('cpuacct', $vmid, 'cpuacct.stat', 1);
774 if ($raw =~ m/^user (\d+)\nsystem (\d+)\n/) {
787 my $list = $opt_vmid ?
{ $opt_vmid => { type
=> 'lxc' }} : config_list
();
789 my $active_hash = list_active_containers
();
791 my $cpucount = $cpuinfo->{cpus
} || 1;
793 my $cdtime = gettimeofday
;
795 my $uptime = (PVE
::ProcFSTools
::read_proc_uptime
(1))[0];
797 foreach my $vmid (keys %$list) {
798 my $d = $list->{$vmid};
800 eval { $d->{pid
} = find_lxc_pid
($vmid) if defined($active_hash->{$vmid}); };
801 warn $@ if $@; # ignore errors (consider them stopped)
803 $d->{status
} = $d->{pid
} ?
'running' : 'stopped';
805 my $cfspath = cfs_config_path
($vmid);
806 my $conf = PVE
::Cluster
::cfs_read_file
($cfspath) || {};
808 $d->{name
} = $conf->{'hostname'} || "CT$vmid";
809 $d->{name
} =~ s/[\s]//g;
811 $d->{cpus
} = $conf->{cpulimit
} || $cpucount;
814 my $res = get_container_disk_usage
($vmid, $d->{pid
});
815 $d->{disk
} = $res->{used
};
816 $d->{maxdisk
} = $res->{total
};
819 # use 4GB by default ??
820 if (my $rootfs = $conf->{rootfs
}) {
821 my $rootinfo = parse_ct_rootfs
($rootfs);
822 $d->{maxdisk
} = int(($rootinfo->{size
} || 4)*1024*1024)*1024;
824 $d->{maxdisk
} = 4*1024*1024*1024;
830 $d->{maxmem
} = ($conf->{memory
}||512)*1024*1024;
831 $d->{maxswap
} = ($conf->{swap
}//0)*1024*1024;
842 $d->{template
} = is_template
($conf);
845 foreach my $vmid (keys %$list) {
846 my $d = $list->{$vmid};
849 next if !$pid; # skip stopped CTs
851 my $ctime = (stat("/proc/$pid"))[10]; # 10 = ctime
852 $d->{uptime
} = time - $ctime; # the method lxcfs uses
854 $d->{mem
} = read_cgroup_value
('memory', $vmid, 'memory.usage_in_bytes');
855 $d->{swap
} = read_cgroup_value
('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem
};
857 my $blkio_bytes = read_cgroup_value
('blkio', $vmid, 'blkio.throttle.io_service_bytes', 1);
858 my @bytes = split(/\n/, $blkio_bytes);
859 foreach my $byte (@bytes) {
860 if (my ($key, $value) = $byte =~ /(Read|Write)\s+(\d+)/) {
861 $d->{diskread
} = $2 if $key eq 'Read';
862 $d->{diskwrite
} = $2 if $key eq 'Write';
866 my $pstat = &$parse_cpuacct_stat($vmid);
868 my $used = $pstat->{utime} + $pstat->{stime
};
870 my $old = $last_proc_vmid_stat->{$vmid};
872 $last_proc_vmid_stat->{$vmid} = {
880 my $dtime = ($cdtime - $old->{time}) * $cpucount * $cpuinfo->{user_hz
};
883 my $dutime = $used - $old->{used
};
885 $d->{cpu
} = (($dutime/$dtime)* $cpucount) / $d->{cpus
};
886 $last_proc_vmid_stat->{$vmid} = {
892 $d->{cpu
} = $old->{cpu
};
896 my $netdev = PVE
::ProcFSTools
::read_proc_net_dev
();
898 foreach my $dev (keys %$netdev) {
899 next if $dev !~ m/^veth([1-9]\d*)i/;
901 my $d = $list->{$vmid};
905 $d->{netout
} += $netdev->{$dev}->{receive
};
906 $d->{netin
} += $netdev->{$dev}->{transmit
};
913 sub classify_mountpoint
{
916 return 'device' if $vol =~ m!^/dev/!;
922 my $parse_ct_mountpoint_full = sub {
923 my ($desc, $data, $noerr) = @_;
928 eval { $res = PVE
::JSONSchema
::parse_property_string
($desc, $data) };
930 return undef if $noerr;
934 if (defined(my $size = $res->{size
})) {
935 $size = PVE
::JSONSchema
::parse_size
($size);
936 if (!defined($size)) {
937 return undef if $noerr;
938 die "invalid size: $size\n";
940 $res->{size
} = $size;
943 $res->{type
} = classify_mountpoint
($res->{volume
});
948 sub parse_ct_rootfs
{
949 my ($data, $noerr) = @_;
951 my $res = &$parse_ct_mountpoint_full($rootfs_desc, $data, $noerr);
953 $res->{mp
} = '/' if defined($res);
958 sub parse_ct_mountpoint
{
959 my ($data, $noerr) = @_;
961 return &$parse_ct_mountpoint_full($mp_desc, $data, $noerr);
964 sub print_ct_mountpoint
{
965 my ($info, $nomp) = @_;
966 my $skip = [ 'type' ];
967 push @$skip, 'mp' if $nomp;
968 return PVE
::JSONSchema
::print_property_string
($info, $mp_desc, $skip);
971 sub print_lxc_network
{
973 return PVE
::JSONSchema
::print_property_string
($net, $netconf_desc);
976 sub parse_lxc_network
{
981 return $res if !$data;
983 $res = PVE
::JSONSchema
::parse_property_string
($netconf_desc, $data);
985 $res->{type
} = 'veth';
986 $res->{hwaddr
} = PVE
::Tools
::random_ether_addr
() if !$res->{hwaddr
};
991 sub read_cgroup_value
{
992 my ($group, $vmid, $name, $full) = @_;
994 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
996 return PVE
::Tools
::file_get_contents
($path) if $full;
998 return PVE
::Tools
::file_read_firstline
($path);
1001 sub write_cgroup_value
{
1002 my ($group, $vmid, $name, $value) = @_;
1004 my $path = "/sys/fs/cgroup/$group/lxc/$vmid/$name";
1005 PVE
::ProcFSTools
::write_proc_entry
($path, $value) if -e
$path;
1009 sub find_lxc_console_pids
{
1013 PVE
::Tools
::dir_glob_foreach
('/proc', '\d+', sub {
1016 my $cmdline = PVE
::Tools
::file_read_firstline
("/proc/$pid/cmdline");
1017 return if !$cmdline;
1019 my @args = split(/\0/, $cmdline);
1021 # search for lxc-console -n <vmid>
1022 return if scalar(@args) != 3;
1023 return if $args[1] ne '-n';
1024 return if $args[2] !~ m/^\d+$/;
1025 return if $args[0] !~ m
|^(/usr/bin
/)?lxc-console
$|;
1027 my $vmid = $args[2];
1029 push @{$res->{$vmid}}, $pid;
1041 $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
1043 PVE
::Tools
::run_command
(['lxc-info', '-n', $vmid, '-p'], outfunc
=> $parser);
1045 die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
1050 # Note: we cannot use Net:IP, because that only allows strict
1052 sub parse_ipv4_cidr
{
1053 my ($cidr, $noerr) = @_;
1055 if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) && ($2 <= 32)) {
1056 return { address
=> $1, netmask
=> $PVE::Network
::ipv4_reverse_mask-
>[$2] };
1059 return undef if $noerr;
1061 die "unable to parse ipv4 address/mask\n";
1067 die "VM is locked ($conf->{'lock'})\n" if $conf->{'lock'};
1071 my ($conf, $lock) = @_;
1072 return $conf->{lock} && (!defined($lock) || $lock eq $conf->{lock});
1075 sub check_protection
{
1076 my ($vm_conf, $err_msg) = @_;
1078 if ($vm_conf->{protection
}) {
1079 die "$err_msg - protection mode enabled\n";
1083 sub update_lxc_config
{
1084 my ($storage_cfg, $vmid, $conf) = @_;
1086 my $dir = "/var/lib/lxc/$vmid";
1088 if ($conf->{template
}) {
1090 unlink "$dir/config";
1097 die "missing 'arch' - internal error" if !$conf->{arch
};
1098 $raw .= "lxc.arch = $conf->{arch}\n";
1100 my $unprivileged = $conf->{unprivileged
};
1101 my $custom_idmap = grep { $_->[0] eq 'lxc.id_map' } @{$conf->{lxc
}};
1103 my $ostype = $conf->{ostype
} || die "missing 'ostype' - internal error";
1104 if ($ostype =~ /^(?:debian | ubuntu | centos | fedora | opensuse | archlinux | alpine | unmanaged)$/x) {
1105 my $inc ="/usr/share/lxc/config/$ostype.common.conf";
1106 $inc ="/usr/share/lxc/config/common.conf" if !-f
$inc;
1107 $raw .= "lxc.include = $inc\n";
1108 if ($unprivileged || $custom_idmap) {
1109 $inc = "/usr/share/lxc/config/$ostype.userns.conf";
1110 $inc = "/usr/share/lxc/config/userns.conf" if !-f
$inc;
1111 $raw .= "lxc.include = $inc\n"
1114 die "implement me (ostype $ostype)";
1117 # WARNING: DO NOT REMOVE this without making sure that loop device nodes
1118 # cannot be exposed to the container with r/w access (cgroup perms).
1119 # When this is enabled mounts will still remain in the monitor's namespace
1120 # after the container unmounted them and thus will not detach from their
1121 # files while the container is running!
1122 $raw .= "lxc.monitor.unshare = 1\n";
1124 # Should we read them from /etc/subuid?
1125 if ($unprivileged && !$custom_idmap) {
1126 $raw .= "lxc.id_map = u 0 100000 65536\n";
1127 $raw .= "lxc.id_map = g 0 100000 65536\n";
1130 if (!has_dev_console
($conf)) {
1131 $raw .= "lxc.console = none\n";
1132 $raw .= "lxc.cgroup.devices.deny = c 5:1 rwm\n";
1135 my $ttycount = get_tty_count
($conf);
1136 $raw .= "lxc.tty = $ttycount\n";
1138 # some init scripts expect a linux terminal (turnkey).
1139 $raw .= "lxc.environment = TERM=linux\n";
1141 my $utsname = $conf->{hostname
} || "CT$vmid";
1142 $raw .= "lxc.utsname = $utsname\n";
1144 my $memory = $conf->{memory
} || 512;
1145 my $swap = $conf->{swap
} // 0;
1147 my $lxcmem = int($memory*1024*1024);
1148 $raw .= "lxc.cgroup.memory.limit_in_bytes = $lxcmem\n";
1150 my $lxcswap = int(($memory + $swap)*1024*1024);
1151 $raw .= "lxc.cgroup.memory.memsw.limit_in_bytes = $lxcswap\n";
1153 if (my $cpulimit = $conf->{cpulimit
}) {
1154 $raw .= "lxc.cgroup.cpu.cfs_period_us = 100000\n";
1155 my $value = int(100000*$cpulimit);
1156 $raw .= "lxc.cgroup.cpu.cfs_quota_us = $value\n";
1159 my $shares = $conf->{cpuunits
} || 1024;
1160 $raw .= "lxc.cgroup.cpu.shares = $shares\n";
1162 my $mountpoint = parse_ct_rootfs
($conf->{rootfs
});
1164 $raw .= "lxc.rootfs = $dir/rootfs\n";
1167 foreach my $k (keys %$conf) {
1168 next if $k !~ m/^net(\d+)$/;
1170 my $d = parse_lxc_network
($conf->{$k});
1172 $raw .= "lxc.network.type = veth\n";
1173 $raw .= "lxc.network.veth.pair = veth${vmid}i${ind}\n";
1174 $raw .= "lxc.network.hwaddr = $d->{hwaddr}\n" if defined($d->{hwaddr
});
1175 $raw .= "lxc.network.name = $d->{name}\n" if defined($d->{name
});
1176 $raw .= "lxc.network.mtu = $d->{mtu}\n" if defined($d->{mtu
});
1179 if (my $lxcconf = $conf->{lxc
}) {
1180 foreach my $entry (@$lxcconf) {
1181 my ($k, $v) = @$entry;
1182 $netcount++ if $k eq 'lxc.network.type';
1183 $raw .= "$k = $v\n";
1187 $raw .= "lxc.network.type = empty\n" if !$netcount;
1189 File
::Path
::mkpath
("$dir/rootfs");
1191 PVE
::Tools
::file_set_contents
("$dir/config", $raw);
1194 # verify and cleanup nameserver list (replace \0 with ' ')
1195 sub verify_nameserver_list
{
1196 my ($nameserver_list) = @_;
1199 foreach my $server (PVE
::Tools
::split_list
($nameserver_list)) {
1200 PVE
::JSONSchema
::pve_verify_ip
($server);
1201 push @list, $server;
1204 return join(' ', @list);
1207 sub verify_searchdomain_list
{
1208 my ($searchdomain_list) = @_;
1211 foreach my $server (PVE
::Tools
::split_list
($searchdomain_list)) {
1212 # todo: should we add checks for valid dns domains?
1213 push @list, $server;
1216 return join(' ', @list);
1219 sub is_volume_in_use
{
1220 my ($config, $volid, $include_snapshots) = @_;
1223 foreach_mountpoint
($config, sub {
1224 my ($ms, $mountpoint) = @_;
1226 if ($mountpoint->{type
} eq 'volume' && $mountpoint->{volume
} eq $volid) {
1231 my $snapshots = $config->{snapshots
};
1232 if ($include_snapshots && $snapshots) {
1233 foreach my $snap (keys %$snapshots) {
1234 $used ||= is_volume_in_use
($snapshots->{$snap}, $volid);
1241 sub add_unused_volume
{
1242 my ($config, $volid) = @_;
1245 for (my $ind = $MAX_UNUSED_DISKS - 1; $ind >= 0; $ind--) {
1246 my $test = "unused$ind";
1247 if (my $vid = $config->{$test}) {
1248 return if $vid eq $volid; # do not add duplicates
1254 die "Too many unused volumes - please delete them first.\n" if !$key;
1256 $config->{$key} = $volid;
1261 sub update_pct_config
{
1262 my ($vmid, $conf, $running, $param, $delete) = @_;
1267 my @deleted_volumes;
1271 my $pid = find_lxc_pid
($vmid);
1272 $rootdir = "/proc/$pid/root";
1275 my $hotplug_error = sub {
1277 push @nohotplug, @_;
1284 if (defined($delete)) {
1285 foreach my $opt (@$delete) {
1286 if (!exists($conf->{$opt})) {
1287 warn "no such option: $opt\n";
1291 if ($opt eq 'hostname' || $opt eq 'memory' || $opt eq 'rootfs') {
1292 die "unable to delete required option '$opt'\n";
1293 } elsif ($opt eq 'swap') {
1294 delete $conf->{$opt};
1295 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", -1);
1296 } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup') {
1297 delete $conf->{$opt};
1298 } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
1299 $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1300 next if $hotplug_error->($opt);
1301 delete $conf->{$opt};
1302 } elsif ($opt =~ m/^net(\d)$/) {
1303 delete $conf->{$opt};
1306 PVE
::Network
::veth_delete
("veth${vmid}i$netid");
1307 } elsif ($opt eq 'protection') {
1308 delete $conf->{$opt};
1309 } elsif ($opt =~ m/^unused(\d+)$/) {
1310 next if $hotplug_error->($opt);
1311 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1312 push @deleted_volumes, $conf->{$opt};
1313 delete $conf->{$opt};
1314 } elsif ($opt =~ m/^mp(\d+)$/) {
1315 next if $hotplug_error->($opt);
1316 check_protection
($conf, "can't remove CT $vmid drive '$opt'");
1317 my $mp = parse_ct_mountpoint
($conf->{$opt});
1318 delete $conf->{$opt};
1319 if ($mp->{type
} eq 'volume') {
1320 add_unused_volume
($conf, $mp->{volume
});
1322 } elsif ($opt eq 'unprivileged') {
1323 die "unable to delete read-only option: '$opt'\n";
1325 die "implement me (delete: $opt)"
1327 write_config
($vmid, $conf) if $running;
1331 # There's no separate swap size to configure, there's memory and "total"
1332 # memory (iow. memory+swap). This means we have to change them together.
1333 my $wanted_memory = PVE
::Tools
::extract_param
($param, 'memory');
1334 my $wanted_swap = PVE
::Tools
::extract_param
($param, 'swap');
1335 if (defined($wanted_memory) || defined($wanted_swap)) {
1337 my $old_memory = ($conf->{memory
} || 512);
1338 my $old_swap = ($conf->{swap
} || 0);
1340 $wanted_memory //= $old_memory;
1341 $wanted_swap //= $old_swap;
1343 my $total = $wanted_memory + $wanted_swap;
1345 my $old_total = $old_memory + $old_swap;
1346 if ($total > $old_total) {
1347 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1348 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1350 write_cgroup_value
("memory", $vmid, "memory.limit_in_bytes", int($wanted_memory*1024*1024));
1351 write_cgroup_value
("memory", $vmid, "memory.memsw.limit_in_bytes", int($total*1024*1024));
1354 $conf->{memory
} = $wanted_memory;
1355 $conf->{swap
} = $wanted_swap;
1357 write_config
($vmid, $conf) if $running;
1360 my $used_volids = {};
1362 foreach my $opt (keys %$param) {
1363 my $value = $param->{$opt};
1364 if ($opt eq 'hostname') {
1365 $conf->{$opt} = $value;
1366 } elsif ($opt eq 'onboot') {
1367 $conf->{$opt} = $value ?
1 : 0;
1368 } elsif ($opt eq 'startup') {
1369 $conf->{$opt} = $value;
1370 } elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
1371 next if $hotplug_error->($opt);
1372 $conf->{$opt} = $value;
1373 } elsif ($opt eq 'nameserver') {
1374 next if $hotplug_error->($opt);
1375 my $list = verify_nameserver_list
($value);
1376 $conf->{$opt} = $list;
1377 } elsif ($opt eq 'searchdomain') {
1378 next if $hotplug_error->($opt);
1379 my $list = verify_searchdomain_list
($value);
1380 $conf->{$opt} = $list;
1381 } elsif ($opt eq 'cpulimit') {
1382 next if $hotplug_error->($opt); # FIXME: hotplug
1383 $conf->{$opt} = $value;
1384 } elsif ($opt eq 'cpuunits') {
1385 $conf->{$opt} = $value;
1386 write_cgroup_value
("cpu", $vmid, "cpu.shares", $value);
1387 } elsif ($opt eq 'description') {
1388 $conf->{$opt} = PVE
::Tools
::encode_text
($value);
1389 } elsif ($opt =~ m/^net(\d+)$/) {
1391 my $net = parse_lxc_network
($value);
1393 $conf->{$opt} = print_lxc_network
($net);
1395 update_net
($vmid, $conf, $opt, $net, $netid, $rootdir);
1397 } elsif ($opt eq 'protection') {
1398 $conf->{$opt} = $value ?
1 : 0;
1399 } elsif ($opt =~ m/^mp(\d+)$/) {
1400 next if $hotplug_error->($opt);
1401 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1402 my $old = $conf->{$opt};
1403 $conf->{$opt} = $value;
1404 if (defined($old)) {
1405 my $mp = parse_ct_mountpoint
($old);
1406 if ($mp->{type
} eq 'volume') {
1407 add_unused_volume
($conf, $mp->{volume
});
1411 my $mp = parse_ct_mountpoint
($value);
1412 $used_volids->{$mp->{volume
}} = 1;
1413 } elsif ($opt eq 'rootfs') {
1414 next if $hotplug_error->($opt);
1415 check_protection
($conf, "can't update CT $vmid drive '$opt'");
1416 my $old = $conf->{$opt};
1417 $conf->{$opt} = $value;
1418 if (defined($old)) {
1419 my $mp = parse_ct_rootfs
($old);
1420 if ($mp->{type
} eq 'volume') {
1421 add_unused_volume
($conf, $mp->{volume
});
1424 my $mp = parse_ct_rootfs
($value);
1425 $used_volids->{$mp->{volume
}} = 1;
1426 } elsif ($opt eq 'unprivileged') {
1427 die "unable to modify read-only option: '$opt'\n";
1428 } elsif ($opt eq 'ostype') {
1429 next if $hotplug_error->($opt);
1430 $conf->{$opt} = $value;
1432 die "implement me: $opt";
1434 write_config
($vmid, $conf) if $running;
1437 # Apply deletions and creations of new volumes
1438 if (@deleted_volumes) {
1439 my $storage_cfg = PVE
::Storage
::config
();
1440 foreach my $volume (@deleted_volumes) {
1441 next if $used_volids->{$volume}; # could have been re-added, too
1442 # also check for references in snapshots
1443 next if is_volume_in_use
($conf, $volume, 1);
1444 delete_mountpoint_volume
($storage_cfg, $vmid, $volume);
1449 my $storage_cfg = PVE
::Storage
::config
();
1450 create_disks
($storage_cfg, $vmid, $conf, $conf);
1453 # This should be the last thing we do here
1454 if ($running && scalar(@nohotplug)) {
1455 die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
1459 sub has_dev_console
{
1462 return !(defined($conf->{console
}) && !$conf->{console
});
1468 return $conf->{tty
} // $confdesc->{tty
}->{default};
1474 return $conf->{cmode
} // $confdesc->{cmode
}->{default};
1477 sub get_console_command
{
1478 my ($vmid, $conf) = @_;
1480 my $cmode = get_cmode
($conf);
1482 if ($cmode eq 'console') {
1483 return ['lxc-console', '-n', $vmid, '-t', 0];
1484 } elsif ($cmode eq 'tty') {
1485 return ['lxc-console', '-n', $vmid];
1486 } elsif ($cmode eq 'shell') {
1487 return ['lxc-attach', '--clear-env', '-n', $vmid];
1489 die "internal error";
1493 sub get_primary_ips
{
1496 # return data from net0
1498 return undef if !defined($conf->{net0
});
1499 my $net = parse_lxc_network
($conf->{net0
});
1501 my $ipv4 = $net->{ip
};
1503 if ($ipv4 =~ /^(dhcp|manual)$/) {
1509 my $ipv6 = $net->{ip6
};
1511 if ($ipv6 =~ /^(auto|dhcp|manual)$/) {
1518 return ($ipv4, $ipv6);
1521 sub delete_mountpoint_volume
{
1522 my ($storage_cfg, $vmid, $volume) = @_;
1524 return if classify_mountpoint
($volume) ne 'volume';
1526 my ($vtype, $name, $owner) = PVE
::Storage
::parse_volname
($storage_cfg, $volume);
1527 PVE
::Storage
::vdisk_free
($storage_cfg, $volume) if $vmid == $owner;
1530 sub destroy_lxc_container
{
1531 my ($storage_cfg, $vmid, $conf) = @_;
1533 foreach_mountpoint
($conf, sub {
1534 my ($ms, $mountpoint) = @_;
1535 delete_mountpoint_volume
($storage_cfg, $vmid, $mountpoint->{volume
});
1538 rmdir "/var/lib/lxc/$vmid/rootfs";
1539 unlink "/var/lib/lxc/$vmid/config";
1540 rmdir "/var/lib/lxc/$vmid";
1541 destroy_config
($vmid);
1543 #my $cmd = ['lxc-destroy', '-n', $vmid ];
1544 #PVE::Tools::run_command($cmd);
1547 sub vm_stop_cleanup
{
1548 my ($storage_cfg, $vmid, $conf, $keepActive) = @_;
1553 my $vollist = get_vm_volumes
($conf);
1554 PVE
::Storage
::deactivate_volumes
($storage_cfg, $vollist);
1557 warn $@ if $@; # avoid errors - just warn
1560 my $safe_num_ne = sub {
1563 return 0 if !defined($a) && !defined($b);
1564 return 1 if !defined($a);
1565 return 1 if !defined($b);
1570 my $safe_string_ne = sub {
1573 return 0 if !defined($a) && !defined($b);
1574 return 1 if !defined($a);
1575 return 1 if !defined($b);
1581 my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
1583 if ($newnet->{type
} ne 'veth') {
1584 # for when there are physical interfaces
1585 die "cannot update interface of type $newnet->{type}";
1588 my $veth = "veth${vmid}i${netid}";
1589 my $eth = $newnet->{name
};
1591 if (my $oldnetcfg = $conf->{$opt}) {
1592 my $oldnet = parse_lxc_network
($oldnetcfg);
1594 if (&$safe_string_ne($oldnet->{hwaddr
}, $newnet->{hwaddr
}) ||
1595 &$safe_string_ne($oldnet->{name
}, $newnet->{name
})) {
1597 PVE
::Network
::veth_delete
($veth);
1598 delete $conf->{$opt};
1599 write_config
($vmid, $conf);
1601 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1603 } elsif (&$safe_string_ne($oldnet->{bridge
}, $newnet->{bridge
}) ||
1604 &$safe_num_ne($oldnet->{tag
}, $newnet->{tag
}) ||
1605 &$safe_num_ne($oldnet->{firewall
}, $newnet->{firewall
})) {
1607 if ($oldnet->{bridge
}) {
1608 PVE
::Network
::tap_unplug
($veth);
1609 foreach (qw(bridge tag firewall)) {
1610 delete $oldnet->{$_};
1612 $conf->{$opt} = print_lxc_network
($oldnet);
1613 write_config
($vmid, $conf);
1616 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
}, $newnet->{trunks
});
1617 foreach (qw(bridge tag firewall)) {
1618 $oldnet->{$_} = $newnet->{$_} if $newnet->{$_};
1620 $conf->{$opt} = print_lxc_network
($oldnet);
1621 write_config
($vmid, $conf);
1624 hotplug_net
($vmid, $conf, $opt, $newnet, $netid);
1627 update_ipconfig
($vmid, $conf, $opt, $eth, $newnet, $rootdir);
1631 my ($vmid, $conf, $opt, $newnet, $netid) = @_;
1633 my $veth = "veth${vmid}i${netid}";
1634 my $vethpeer = $veth . "p";
1635 my $eth = $newnet->{name
};
1637 PVE
::Network
::veth_create
($veth, $vethpeer, $newnet->{bridge
}, $newnet->{hwaddr
});
1638 PVE
::Network
::tap_plug
($veth, $newnet->{bridge
}, $newnet->{tag
}, $newnet->{firewall
}, $newnet->{trunks
});
1640 # attach peer in container
1641 my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
1642 PVE
::Tools
::run_command
($cmd);
1644 # link up peer in container
1645 $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up' ];
1646 PVE
::Tools
::run_command
($cmd);
1648 my $done = { type
=> 'veth' };
1649 foreach (qw(bridge tag firewall hwaddr name)) {
1650 $done->{$_} = $newnet->{$_} if $newnet->{$_};
1652 $conf->{$opt} = print_lxc_network
($done);
1654 write_config
($vmid, $conf);
1657 sub update_ipconfig
{
1658 my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
1660 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
1662 my $optdata = parse_lxc_network
($conf->{$opt});
1666 my $cmdargs = shift;
1667 PVE
::Tools
::run_command
(['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', @_], %$cmdargs);
1669 my $ipcmd = sub { &$nscmd({}, '/sbin/ip', @_) };
1671 my $change_ip_config = sub {
1672 my ($ipversion) = @_;
1674 my $family_opt = "-$ipversion";
1675 my $suffix = $ipversion == 4 ?
'' : $ipversion;
1676 my $gw= "gw$suffix";
1677 my $ip= "ip$suffix";
1679 my $newip = $newnet->{$ip};
1680 my $newgw = $newnet->{$gw};
1681 my $oldip = $optdata->{$ip};
1683 my $change_ip = &$safe_string_ne($oldip, $newip);
1684 my $change_gw = &$safe_string_ne($optdata->{$gw}, $newgw);
1686 return if !$change_ip && !$change_gw;
1688 # step 1: add new IP, if this fails we cancel
1689 my $is_real_ip = ($newip && $newip !~ /^(?:auto|dhcp|manual)$/);
1690 if ($change_ip && $is_real_ip) {
1691 eval { &$ipcmd($family_opt, 'addr', 'add', $newip, 'dev', $eth); };
1698 # step 2: replace gateway
1699 # If this fails we delete the added IP and cancel.
1700 # If it succeeds we save the config and delete the old IP, ignoring
1701 # errors. The config is then saved.
1702 # Note: 'ip route replace' can add
1706 if ($is_real_ip && !PVE
::Network
::is_ip_in_cidr
($newgw, $newip, $ipversion)) {
1707 &$ipcmd($family_opt, 'route', 'add', $newgw, 'dev', $eth);
1709 &$ipcmd($family_opt, 'route', 'replace', 'default', 'via', $newgw);
1713 # the route was not replaced, the old IP is still available
1714 # rollback (delete new IP) and cancel
1716 eval { &$ipcmd($family_opt, 'addr', 'del', $newip, 'dev', $eth); };
1717 warn $@ if $@; # no need to die here
1722 eval { &$ipcmd($family_opt, 'route', 'del', 'default'); };
1723 # if the route was not deleted, the guest might have deleted it manually
1729 # from this point on we save the configuration
1730 # step 3: delete old IP ignoring errors
1731 if ($change_ip && $oldip && $oldip !~ /^(?:auto|dhcp)$/) {
1732 # We need to enable promote_secondaries, otherwise our newly added
1733 # address will be removed along with the old one.
1736 if ($ipversion == 4) {
1737 &$nscmd({ outfunc
=> sub { $promote = int(shift) } },
1738 'cat', "/proc/sys/net/ipv4/conf/$eth/promote_secondaries");
1739 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=1");
1741 &$ipcmd($family_opt, 'addr', 'del', $oldip, 'dev', $eth);
1743 warn $@ if $@; # no need to die here
1745 if ($ipversion == 4) {
1746 &$nscmd({}, 'sysctl', "net.ipv4.conf.$eth.promote_secondaries=$promote");
1750 foreach my $property ($ip, $gw) {
1751 if ($newnet->{$property}) {
1752 $optdata->{$property} = $newnet->{$property};
1754 delete $optdata->{$property};
1757 $conf->{$opt} = print_lxc_network
($optdata);
1758 write_config
($vmid, $conf);
1759 $lxc_setup->setup_network($conf);
1762 &$change_ip_config(4);
1763 &$change_ip_config(6);
1767 # Internal snapshots
1769 # NOTE: Snapshot create/delete involves several non-atomic
1770 # actions, and can take a long time.
1771 # So we try to avoid locking the file and use the 'lock' variable
1772 # inside the config file instead.
1774 my $snapshot_copy_config = sub {
1775 my ($source, $dest) = @_;
1777 foreach my $k (keys %$source) {
1778 next if $k eq 'snapshots';
1779 next if $k eq 'snapstate';
1780 next if $k eq 'snaptime';
1781 next if $k eq 'vmstate';
1782 next if $k eq 'lock';
1783 next if $k eq 'digest';
1784 next if $k eq 'description';
1785 next if $k =~ m/^unused\d+$/;
1787 $dest->{$k} = $source->{$k};
1791 my $snapshot_apply_config = sub {
1792 my ($conf, $snap) = @_;
1794 # copy snapshot list
1796 snapshots
=> $conf->{snapshots
},
1799 # keep description and list of unused disks
1800 foreach my $k (keys %$conf) {
1801 next if !($k =~ m/^unused\d+$/ || $k eq 'description');
1802 $newconf->{$k} = $conf->{$k};
1805 &$snapshot_copy_config($snap, $newconf);
1810 sub snapshot_save_vmstate
{
1811 die "implement me - snapshot_save_vmstate\n";
1814 sub snapshot_prepare
{
1815 my ($vmid, $snapname, $save_vmstate, $comment) = @_;
1819 my $updatefn = sub {
1821 my $conf = load_config
($vmid);
1823 die "you can't take a snapshot if it's a template\n"
1824 if is_template
($conf);
1828 $conf->{lock} = 'snapshot';
1830 die "snapshot name '$snapname' already used\n"
1831 if defined($conf->{snapshots
}->{$snapname});
1833 my $storecfg = PVE
::Storage
::config
();
1834 die "snapshot feature is not available\n"
1835 if !has_feature
('snapshot', $conf, $storecfg, undef, undef, $snapname eq 'vzdump');
1837 $snap = $conf->{snapshots
}->{$snapname} = {};
1839 if ($save_vmstate && check_running
($vmid)) {
1840 snapshot_save_vmstate
($vmid, $conf, $snapname, $storecfg);
1843 &$snapshot_copy_config($conf, $snap);
1845 $snap->{snapstate
} = "prepare";
1846 $snap->{snaptime
} = time();
1847 $snap->{description
} = $comment if $comment;
1849 write_config
($vmid, $conf);
1852 lock_config
($vmid, $updatefn);
1857 sub snapshot_commit
{
1858 my ($vmid, $snapname) = @_;
1860 my $updatefn = sub {
1862 my $conf = load_config
($vmid);
1864 die "missing snapshot lock\n"
1865 if !($conf->{lock} && $conf->{lock} eq 'snapshot');
1867 my $snap = $conf->{snapshots
}->{$snapname};
1868 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1870 die "wrong snapshot state\n"
1871 if !($snap->{snapstate
} && $snap->{snapstate
} eq "prepare");
1873 delete $snap->{snapstate
};
1874 delete $conf->{lock};
1876 $conf->{parent
} = $snapname;
1878 write_config
($vmid, $conf);
1881 lock_config
($vmid, $updatefn);
1885 my ($feature, $conf, $storecfg, $snapname, $running, $backup_only) = @_;
1889 foreach_mountpoint
($conf, sub {
1890 my ($ms, $mountpoint) = @_;
1892 return if $err; # skip further test
1893 return if $backup_only && $ms ne 'rootfs' && !$mountpoint->{backup
};
1895 $err = 1 if !PVE
::Storage
::volume_has_feature
($storecfg, $feature, $mountpoint->{volume
}, $snapname, $running);
1898 return $err ?
0 : 1;
1901 my $enter_namespace = sub {
1902 my ($vmid, $pid, $which, $type) = @_;
1903 sysopen my $fd, "/proc/$pid/ns/$which", O_RDONLY
1904 or die "failed to open $which namespace of container $vmid: $!\n";
1905 PVE
::Tools
::setns
(fileno($fd), $type)
1906 or die "failed to enter $which namespace of container $vmid: $!\n";
1910 my $do_syncfs = sub {
1911 my ($vmid, $pid, $socket) = @_;
1913 &$enter_namespace($vmid, $pid, 'mnt', PVE
::Tools
::CLONE_NEWNS
);
1915 # Tell the parent process to start reading our /proc/mounts
1916 print {$socket} "go\n";
1919 # Receive /proc/self/mounts
1920 my $mountdata = do { local $/ = undef; <$socket> };
1923 # Now sync all mountpoints...
1924 my $mounts = PVE
::ProcFSTools
::parse_mounts
($mountdata);
1925 foreach my $mp (@$mounts) {
1926 my ($what, $dir, $fs) = @$mp;
1927 next if $fs eq 'fuse.lxcfs';
1928 eval { PVE
::Tools
::sync_mountpoint
($dir); };
1933 sub sync_container_namespace
{
1935 my $pid = find_lxc_pid
($vmid);
1937 # SOCK_DGRAM is nicer for barriers but cannot be slurped
1938 socketpair my $pfd, my $cfd, AF_UNIX
, SOCK_STREAM
, PF_UNSPEC
1939 or die "failed to create socketpair: $!\n";
1942 die "fork failed: $!\n" if !defined($child);
1947 &$do_syncfs($vmid, $pid, $cfd);
1957 die "failed to enter container namespace\n" if $go ne "go\n";
1959 open my $mounts, '<', "/proc/$child/mounts"
1960 or die "failed to open container's /proc/mounts: $!\n";
1961 my $mountdata = do { local $/ = undef; <$mounts> };
1963 print {$pfd} $mountdata;
1966 while (waitpid($child, 0) != $child) {}
1967 die "failed to sync container namespace\n" if $? != 0;
1970 sub check_freeze_needed
{
1971 my ($vmid, $config, $save_vmstate) = @_;
1973 my $ret = check_running
($vmid);
1974 return ($ret, $ret);
1977 sub snapshot_create
{
1978 my ($vmid, $snapname, $save_vmstate, $comment) = @_;
1980 my $snap = snapshot_prepare
($vmid, $snapname, $save_vmstate, $comment);
1982 $save_vmstate = 0 if !$snap->{vmstate
};
1984 my $conf = load_config
($vmid);
1986 my ($running, $freezefs) = check_freeze_needed
($vmid, $conf, $snap->{vmstate
});
1992 PVE
::Tools
::run_command
(['/usr/bin/lxc-freeze', '-n', $vmid]);
1993 sync_container_namespace
($vmid);
1996 my $storecfg = PVE
::Storage
::config
();
1997 foreach_mountpoint
($conf, sub {
1998 my ($ms, $mountpoint) = @_;
2000 return if $snapname eq 'vzdump' && $ms ne 'rootfs' && !$mountpoint->{backup
};
2001 PVE
::Storage
::volume_snapshot
($storecfg, $mountpoint->{volume
}, $snapname);
2002 $drivehash->{$ms} = 1;
2009 eval { PVE
::Tools
::run_command
(['/usr/bin/lxc-unfreeze', '-n', $vmid]); };
2015 warn "snapshot create failed: starting cleanup\n";
2016 eval { snapshot_delete
($vmid, $snapname, 1, $drivehash); };
2021 snapshot_commit
($vmid, $snapname);
2024 # Note: $drivehash is only set when called from snapshot_create.
2025 sub snapshot_delete
{
2026 my ($vmid, $snapname, $force, $drivehash) = @_;
2033 my $unlink_parent = sub {
2034 my ($confref, $new_parent) = @_;
2036 if ($confref->{parent
} && $confref->{parent
} eq $snapname) {
2038 $confref->{parent
} = $new_parent;
2040 delete $confref->{parent
};
2045 my $updatefn = sub {
2046 my ($remove_drive) = @_;
2048 my $conf = load_config
($vmid);
2052 die "you can't delete a snapshot if vm is a template\n"
2053 if is_template
($conf);
2056 $snap = $conf->{snapshots
}->{$snapname};
2058 die "snapshot '$snapname' does not exist\n" if !defined($snap);
2060 # remove parent refs
2062 &$unlink_parent($conf, $snap->{parent
});
2063 foreach my $sn (keys %{$conf->{snapshots
}}) {
2064 next if $sn eq $snapname;
2065 &$unlink_parent($conf->{snapshots
}->{$sn}, $snap->{parent
});
2069 if ($remove_drive) {
2070 if ($remove_drive eq 'vmstate') {
2071 die "implement me - saving vmstate\n";
2073 my $value = $snap->{$remove_drive};
2074 my $mountpoint = $remove_drive eq 'rootfs' ? parse_ct_rootfs
($value, 1) : parse_ct_mountpoint
($value, 1);
2075 delete $snap->{$remove_drive};
2076 add_unused_volume
($snap, $mountpoint->{volume
});
2081 $snap->{snapstate
} = 'delete';
2083 delete $conf->{snapshots
}->{$snapname};
2084 delete $conf->{lock} if $drivehash;
2085 foreach my $volid (@$unused) {
2086 add_unused_volume
($conf, $volid);
2090 write_config
($vmid, $conf);
2093 lock_config
($vmid, $updatefn);
2095 # now remove vmstate file
2096 # never set for LXC!
2097 my $storecfg = PVE
::Storage
::config
();
2099 if ($snap->{vmstate
}) {
2100 die "implement me - saving vmstate\n";
2103 # now remove all volume snapshots
2104 foreach_mountpoint
($snap, sub {
2105 my ($ms, $mountpoint) = @_;
2107 return if $snapname eq 'vzdump' && $ms ne 'rootfs' && !$mountpoint->{backup
};
2108 if (!$drivehash || $drivehash->{$ms}) {
2109 eval { PVE
::Storage
::volume_snapshot_delete
($storecfg, $mountpoint->{volume
}, $snapname); };
2111 die $err if !$force;
2116 # save changes (remove mp from snapshot)
2117 lock_config
($vmid, $updatefn, $ms) if !$force;
2118 push @$unused, $mountpoint->{volume
};
2121 # now cleanup config
2123 lock_config
($vmid, $updatefn);
2126 sub snapshot_rollback
{
2127 my ($vmid, $snapname) = @_;
2131 my $storecfg = PVE
::Storage
::config
();
2133 my $conf = load_config
($vmid);
2135 my $get_snapshot_config = sub {
2137 die "you can't rollback if vm is a template\n" if is_template
($conf);
2139 my $res = $conf->{snapshots
}->{$snapname};
2141 die "snapshot '$snapname' does not exist\n" if !defined($res);
2146 my $snap = &$get_snapshot_config();
2148 foreach_mountpoint
($snap, sub {
2149 my ($ms, $mountpoint) = @_;
2151 PVE
::Storage
::volume_rollback_is_possible
($storecfg, $mountpoint->{volume
}, $snapname);
2154 my $updatefn = sub {
2156 $conf = load_config
($vmid);
2158 $snap = &$get_snapshot_config();
2160 die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n"
2161 if $snap->{snapstate
};
2165 PVE
::Tools
::run_command
(['/usr/bin/lxc-stop', '-n', $vmid, '--kill'])
2166 if check_running
($vmid);
2169 die "unable to rollback vm $vmid: vm is running\n"
2170 if check_running
($vmid);
2173 $conf->{lock} = 'rollback';
2175 die "got wrong lock\n" if !($conf->{lock} && $conf->{lock} eq 'rollback');
2176 delete $conf->{lock};
2182 # copy snapshot config to current config
2183 $conf = &$snapshot_apply_config($conf, $snap);
2184 $conf->{parent
} = $snapname;
2187 write_config
($vmid, $conf);
2189 if (!$prepare && $snap->{vmstate
}) {
2190 die "implement me - save vmstate\n";
2194 lock_config
($vmid, $updatefn);
2196 foreach_mountpoint
($snap, sub {
2197 my ($ms, $mountpoint) = @_;
2199 PVE
::Storage
::volume_snapshot_rollback
($storecfg, $mountpoint->{volume
}, $snapname);
2203 lock_config
($vmid, $updatefn);
2206 sub template_create
{
2207 my ($vmid, $conf) = @_;
2209 my $storecfg = PVE
::Storage
::config
();
2211 my $rootinfo = parse_ct_rootfs
($conf->{rootfs
});
2212 my $volid = $rootinfo->{volume
};
2214 die "Template feature is not available for '$volid'\n"
2215 if !PVE
::Storage
::volume_has_feature
($storecfg, 'template', $volid);
2217 PVE
::Storage
::activate_volumes
($storecfg, [$volid]);
2219 my $template_volid = PVE
::Storage
::vdisk_create_base
($storecfg, $volid);
2220 $rootinfo->{volume
} = $template_volid;
2221 $conf->{rootfs
} = print_ct_mountpoint
($rootinfo, 1);
2223 write_config
($vmid, $conf);
2229 return 1 if defined $conf->{template
} && $conf->{template
} == 1;
2232 sub mountpoint_names
{
2235 my @names = ('rootfs');
2237 for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) {
2238 push @names, "mp$i";
2241 return $reverse ?
reverse @names : @names;
2245 sub foreach_mountpoint_full
{
2246 my ($conf, $reverse, $func) = @_;
2248 foreach my $key (mountpoint_names
($reverse)) {
2249 my $value = $conf->{$key};
2250 next if !defined($value);
2251 my $mountpoint = $key eq 'rootfs' ? parse_ct_rootfs
($value, 1) : parse_ct_mountpoint
($value, 1);
2252 next if !defined($mountpoint);
2254 &$func($key, $mountpoint);
2258 sub foreach_mountpoint
{
2259 my ($conf, $func) = @_;
2261 foreach_mountpoint_full
($conf, 0, $func);
2264 sub foreach_mountpoint_reverse
{
2265 my ($conf, $func) = @_;
2267 foreach_mountpoint_full
($conf, 1, $func);
2270 sub check_ct_modify_config_perm
{
2271 my ($rpcenv, $authuser, $vmid, $pool, $newconf, $delete) = @_;
2273 return 1 if $authuser eq 'root@pam';
2276 my ($opt, $delete) = @_;
2277 if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') {
2278 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']);
2279 } elsif ($opt eq 'rootfs' || $opt =~ /^mp\d+$/) {
2280 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
2282 my $data = $opt eq 'rootfs' ? parse_ct_rootfs
($newconf->{$opt})
2283 : parse_ct_mountpoint
($newconf->{$opt});
2284 raise_perm_exc
("mountpoint type $data->{type}") if $data->{type
} ne 'volume';
2285 } elsif ($opt eq 'memory' || $opt eq 'swap') {
2286 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
2287 } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' ||
2288 $opt eq 'searchdomain' || $opt eq 'hostname') {
2289 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
2291 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']);
2295 foreach my $opt (keys %$newconf) {
2298 foreach my $opt (@$delete) {
2306 my ($vmid, $storage_cfg, $conf, $noerr) = @_;
2308 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2309 my $volid_list = get_vm_volumes
($conf);
2311 foreach_mountpoint_reverse
($conf, sub {
2312 my ($ms, $mountpoint) = @_;
2314 my $volid = $mountpoint->{volume
};
2315 my $mount = $mountpoint->{mp
};
2317 return if !$volid || !$mount;
2319 my $mount_path = "$rootdir/$mount";
2320 $mount_path =~ s!/+!/!g;
2322 return if !PVE
::ProcFSTools
::is_mounted
($mount_path);
2325 PVE
::Tools
::run_command
(['umount', '-d', $mount_path]);
2338 my ($vmid, $storage_cfg, $conf) = @_;
2340 my $rootdir = "/var/lib/lxc/$vmid/rootfs";
2341 File
::Path
::make_path
($rootdir);
2343 my $volid_list = get_vm_volumes
($conf);
2344 PVE
::Storage
::activate_volumes
($storage_cfg, $volid_list);
2347 foreach_mountpoint
($conf, sub {
2348 my ($ms, $mountpoint) = @_;
2350 mountpoint_mount
($mountpoint, $rootdir, $storage_cfg);
2354 warn "mounting container failed\n";
2355 umount_all
($vmid, $storage_cfg, $conf, 1);
2363 sub mountpoint_mount_path
{
2364 my ($mountpoint, $storage_cfg, $snapname) = @_;
2366 return mountpoint_mount
($mountpoint, undef, $storage_cfg, $snapname);
2369 my $check_mount_path = sub {
2371 $path = File
::Spec-
>canonpath($path);
2372 my $real = Cwd
::realpath
($path);
2373 if ($real ne $path) {
2374 die "mount path modified by symlink: $path != $real";
2383 if ($line =~ m
@^(/dev/loop\d
+):@) {
2387 my $cmd = ['losetup', '--associated', $path];
2388 PVE
::Tools
::run_command
($cmd, outfunc
=> $parser);
2392 # Run a function with a file attached to a loop device.
2393 # The loop device is always detached afterwards (or set to autoclear).
2394 # Returns the loop device.
2395 sub run_with_loopdev
{
2396 my ($func, $file) = @_;
2397 my $device = query_loopdev
($file);
2398 # Try to reuse an existing device
2400 # We assume that whoever setup the loop device is responsible for
2408 if ($line =~ m
@^(/dev/loop\d
+)$@) {
2412 PVE
::Tools
::run_command
(['losetup', '--show', '-f', $file], outfunc
=> $parser);
2413 die "failed to setup loop device for $file\n" if !$device;
2414 eval { &$func($device); };
2416 PVE
::Tools
::run_command
(['losetup', '-d', $device]);
2422 my ($dir, $dest, $ro, @extra_opts) = @_;
2423 PVE
::Tools
::run_command
(['mount', '-o', 'bind', @extra_opts, $dir, $dest]);
2425 eval { PVE
::Tools
::run_command
(['mount', '-o', 'bind,remount,ro', $dest]); };
2427 warn "bindmount error\n";
2428 # don't leave writable bind-mounts behind...
2429 PVE
::Tools
::run_command
(['umount', $dest]);
2435 # use $rootdir = undef to just return the corresponding mount path
2436 sub mountpoint_mount
{
2437 my ($mountpoint, $rootdir, $storage_cfg, $snapname) = @_;
2439 my $volid = $mountpoint->{volume
};
2440 my $mount = $mountpoint->{mp
};
2441 my $type = $mountpoint->{type
};
2442 my $quota = !$snapname && !$mountpoint->{ro
} && $mountpoint->{quota
};
2445 return if !$volid || !$mount;
2449 if (defined($rootdir)) {
2450 $rootdir =~ s!/+$!!;
2451 $mount_path = "$rootdir/$mount";
2452 $mount_path =~ s!/+!/!g;
2453 &$check_mount_path($mount_path);
2454 File
::Path
::mkpath
($mount_path);
2457 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2459 die "unknown snapshot path for '$volid'" if !$storage && defined($snapname);
2462 if (defined($mountpoint->{acl
})) {
2463 $optstring .= ($mountpoint->{acl
} ?
'acl' : 'noacl');
2465 my $readonly = $mountpoint->{ro
};
2467 my @extra_opts = ('-o', $optstring);
2471 my $scfg = PVE
::Storage
::storage_config
($storage_cfg, $storage);
2472 my $path = PVE
::Storage
::path
($storage_cfg, $volid, $snapname);
2474 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2475 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2477 $format = 'iso' if $vtype eq 'iso'; # allow to handle iso files
2479 if ($format eq 'subvol') {
2482 if ($scfg->{type
} eq 'zfspool') {
2483 my $path_arg = $path;
2484 $path_arg =~ s!^/+!!;
2485 PVE
::Tools
::run_command
(['mount', '-o', 'ro', @extra_opts, '-t', 'zfs', $path_arg, $mount_path]);
2487 die "cannot mount subvol snapshots for storage type '$scfg->{type}'\n";
2490 bindmount
($path, $mount_path, $readonly, @extra_opts);
2491 warn "cannot enable quota control for bind mounted subvolumes\n" if $quota;
2494 return wantarray ?
($path, 0, $mounted_dev) : $path;
2495 } elsif ($format eq 'raw' || $format eq 'iso') {
2499 if ($format eq 'iso') {
2500 PVE
::Tools
::run_command
(['mount', '-o', 'ro', @extra_opts, $path, $mount_path]);
2501 } elsif ($isBase || defined($snapname)) {
2502 PVE
::Tools
::run_command
(['mount', '-o', 'ro,noload', @extra_opts, $path, $mount_path]);
2505 push @extra_opts, '-o', 'usrjquota=aquota.user,grpjquota=aquota.group,jqfmt=vfsv0';
2507 push @extra_opts, '-o', 'ro' if $readonly;
2508 PVE
::Tools
::run_command
(['mount', @extra_opts, $path, $mount_path]);
2512 my $use_loopdev = 0;
2513 if ($scfg->{path
}) {
2514 $mounted_dev = run_with_loopdev
($domount, $path);
2516 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm' ||
2517 $scfg->{type
} eq 'rbd' || $scfg->{type
} eq 'lvmthin') {
2518 $mounted_dev = $path;
2521 die "unsupported storage type '$scfg->{type}'\n";
2523 return wantarray ?
($path, $use_loopdev, $mounted_dev) : $path;
2525 die "unsupported image format '$format'\n";
2527 } elsif ($type eq 'device') {
2528 push @extra_opts, '-o', 'ro' if $readonly;
2529 PVE
::Tools
::run_command
(['mount', @extra_opts, $volid, $mount_path]) if $mount_path;
2530 return wantarray ?
($volid, 0, $volid) : $volid;
2531 } elsif ($type eq 'bind') {
2532 die "directory '$volid' does not exist\n" if ! -d
$volid;
2533 &$check_mount_path($volid);
2534 bindmount
($volid, $mount_path, $readonly, @extra_opts) if $mount_path;
2535 warn "cannot enable quota control for bind mounts\n" if $quota;
2536 return wantarray ?
($volid, 0, undef) : $volid;
2539 die "unsupported storage";
2542 sub get_vm_volumes
{
2543 my ($conf, $excludes) = @_;
2547 foreach_mountpoint
($conf, sub {
2548 my ($ms, $mountpoint) = @_;
2550 return if $excludes && $ms eq $excludes;
2552 my $volid = $mountpoint->{volume
};
2554 return if !$volid || $mountpoint->{type
} ne 'volume';
2556 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2559 push @$vollist, $volid;
2566 my ($dev, $rootuid, $rootgid) = @_;
2568 PVE
::Tools
::run_command
(['mkfs.ext4', '-O', 'mmp',
2569 '-E', "root_owner=$rootuid:$rootgid",
2574 my ($storage_cfg, $volid, $rootuid, $rootgid) = @_;
2576 if ($volid =~ m!^/dev/.+!) {
2581 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2583 die "cannot format volume '$volid' with no storage\n" if !$storage;
2585 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
2587 my $path = PVE
::Storage
::path
($storage_cfg, $volid);
2589 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
2590 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
2592 die "cannot format volume '$volid' (format == $format)\n"
2593 if $format ne 'raw';
2595 mkfs
($path, $rootuid, $rootgid);
2599 my ($storecfg, $vollist) = @_;
2601 foreach my $volid (@$vollist) {
2602 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
2608 my ($storecfg, $vmid, $settings, $conf) = @_;
2613 my (undef, $rootuid, $rootgid) = PVE
::LXC
::parse_id_maps
($conf);
2614 my $chown_vollist = [];
2616 foreach_mountpoint
($settings, sub {
2617 my ($ms, $mountpoint) = @_;
2619 my $volid = $mountpoint->{volume
};
2620 my $mp = $mountpoint->{mp
};
2622 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
2624 if ($storage && ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/)) {
2625 my ($storeid, $size_gb) = ($1, $2);
2627 my $size_kb = int(${size_gb
}*1024) * 1024;
2629 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
2630 # fixme: use better naming ct-$vmid-disk-X.raw?
2632 if ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs') {
2634 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw',
2636 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2638 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2640 push @$chown_vollist, $volid;
2642 } elsif ($scfg->{type
} eq 'zfspool') {
2644 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
2646 push @$chown_vollist, $volid;
2647 } elsif ($scfg->{type
} eq 'drbd' || $scfg->{type
} eq 'lvm' || $scfg->{type
} eq 'lvmthin') {
2649 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2650 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2652 } elsif ($scfg->{type
} eq 'rbd') {
2654 die "krbd option must be enabled on storage type '$scfg->{type}'\n" if !$scfg->{krbd
};
2655 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size_kb);
2656 format_disk
($storecfg, $volid, $rootuid, $rootgid);
2658 die "unable to create containers on storage type '$scfg->{type}'\n";
2660 push @$vollist, $volid;
2661 $mountpoint->{volume
} = $volid;
2662 $mountpoint->{size
} = $size_kb * 1024;
2663 $conf->{$ms} = print_ct_mountpoint
($mountpoint, $ms eq 'rootfs');
2665 # use specified/existing volid/dir/device
2666 $conf->{$ms} = print_ct_mountpoint
($mountpoint, $ms eq 'rootfs');
2670 PVE
::Storage
::activate_volumes
($storecfg, $chown_vollist, undef);
2671 foreach my $volid (@$chown_vollist) {
2672 my $path = PVE
::Storage
::path
($storecfg, $volid, undef);
2673 chown($rootuid, $rootgid, $path);
2675 PVE
::Storage
::deactivate_volumes
($storecfg, $chown_vollist, undef);
2677 # free allocated images on error
2679 destroy_disks
($storecfg, $vollist);
2685 # bash completion helper
2687 sub complete_os_templates
{
2688 my ($cmdname, $pname, $cvalue) = @_;
2690 my $cfg = PVE
::Storage
::config
();
2694 if ($cvalue =~ m/^([^:]+):/) {
2698 my $vtype = $cmdname eq 'restore' ?
'backup' : 'vztmpl';
2699 my $data = PVE
::Storage
::template_list
($cfg, $storeid, $vtype);
2702 foreach my $id (keys %$data) {
2703 foreach my $item (@{$data->{$id}}) {
2704 push @$res, $item->{volid
} if defined($item->{volid
});
2711 my $complete_ctid_full = sub {
2714 my $idlist = vmstatus
();
2716 my $active_hash = list_active_containers
();
2720 foreach my $id (keys %$idlist) {
2721 my $d = $idlist->{$id};
2722 if (defined($running)) {
2723 next if $d->{template
};
2724 next if $running && !$active_hash->{$id};
2725 next if !$running && $active_hash->{$id};
2734 return &$complete_ctid_full();
2737 sub complete_ctid_stopped
{
2738 return &$complete_ctid_full(0);
2741 sub complete_ctid_running
{
2742 return &$complete_ctid_full(1);
2752 my $lxc = $conf->{lxc
};
2753 foreach my $entry (@$lxc) {
2754 my ($key, $value) = @$entry;
2755 next if $key ne 'lxc.id_map';
2756 if ($value =~ /^([ug])\s+(\d+)\s+(\d+)\s+(\d+)\s*$/) {
2757 my ($type, $ct, $host, $length) = ($1, $2, $3, $4);
2758 push @$id_map, [$type, $ct, $host, $length];
2760 $rootuid = $host if $type eq 'u';
2761 $rootgid = $host if $type eq 'g';
2764 die "failed to parse id_map: $value\n";
2768 if (!@$id_map && $conf->{unprivileged
}) {
2769 # Should we read them from /etc/subuid?
2770 $id_map = [ ['u', '0', '100000', '65536'],
2771 ['g', '0', '100000', '65536'] ];
2772 $rootuid = $rootgid = 100000;
2775 return ($id_map, $rootuid, $rootgid);
2778 sub userns_command
{
2781 return ['lxc-usernsexec', (map { ('-m', join(':', @$_)) } @$id_map), '--'];
2787 my ($vmid, $lock) = @_;
2789 lock_config
($vmid, sub {
2790 $conf = load_config
($vmid);
2792 $conf->{lock} = $lock;
2793 write_config
($vmid, $conf);
2799 my ($vmid, $lock) = @_;
2800 lock_config
($vmid, sub {
2801 my $conf = load_config
($vmid);
2802 if (!$conf->{lock}) {
2803 die "no lock found trying to remove lock '$lock'\n";
2804 } elsif (defined($lock) && $conf->{lock} ne $lock) {
2805 die "found lock '$conf->{lock}' trying to remove lock '$lock'\n";
2807 delete $conf->{lock};
2808 write_config
($vmid, $conf);