summaryrefslogtreecommitdiffstats
path: root/perl-install/any.pm
diff options
context:
space:
mode:
Diffstat (limited to 'perl-install/any.pm')
-rw-r--r--perl-install/any.pm260
1 files changed, 175 insertions, 85 deletions
diff --git a/perl-install/any.pm b/perl-install/any.pm
index fa335a9af..9d6a24d1e 100644
--- a/perl-install/any.pm
+++ b/perl-install/any.pm
@@ -8,15 +8,15 @@ use strict;
#-######################################################################################
use common;
use detect_devices;
-use partition_table qw(:types);
-use fsedit;
-use fs;
+use partition_table;
+use fs::type;
use lang;
use run_program;
use keyboard;
use devices;
use modules;
use log;
+use fs;
use c;
sub drakx_version() {
@@ -43,7 +43,7 @@ sub addKdmIcon {
eval { cp_af(facesdir() . $icon . ".png", $dest) } if $icon;
}
-sub allocUsers {
+sub alloc_user_faces {
my ($users) = @_;
my @m = my @l = facesnames();
foreach (grep { !$_->{icon} || $_->{icon} eq "automagic" } @$users) {
@@ -53,34 +53,77 @@ sub allocUsers {
}
}
-sub addUsers {
- my ($users) = @_;
+sub create_user {
+ my ($u, $isMD5) = @_;
+
+ my @existing = stat("$::prefix/home/$u->{name}");
+
+ if (!getpwnam($u->{name})) {
+ my $uid = $u->{uid} || $existing[4];
+ if ($uid && getpwuid($uid)) {
+ undef $uid; #- suggested uid already in use
+ }
+ my $gid = $u->{gid} || $existing[5] || int getgrnam($u->{name});
+ if ($gid) {
+ if (getgrgid($gid)) {
+ undef $gid if getgrgid($gid) ne $u->{name};
+ } else {
+ run_program::rooted($::prefix, 'groupadd', '-g', $gid, $u->{name});
+ }
+ }
+ require authentication;
+ run_program::rooted($::prefix,
+ 'adduser',
+ '-p', authentication::user_crypted_passwd($u, $isMD5),
+ if_($uid, '-u', $uid), if_($gid, '-g', $gid),
+ if_($u->{shell}, '-s', $u->{shell}),
+ $u->{name});
+ }
+
+ my (undef, undef, $uid, $gid, undef, undef, undef, $home) = getpwnam($u->{name});
- allocUsers($users);
- foreach my $u (@$users) {
- run_program::rooted($::prefix, "usermod", "-G", join(",", @{$u->{groups}}), $u->{name}) if !is_empty_array_ref($u->{groups});
- addKdmIcon($u->{name}, delete $u->{auto_icon} || $u->{icon});
+ if (@existing && $::isInstall && ($uid != $existing[4] || $gid != $existing[5])) {
+ log::l("chown'ing $home from $existing[4].$existing[5] to $uid.$gid");
+ require commands;
+ eval { commands::chown_("-r", "$uid.$gid", "$::prefix$home") };
}
}
-sub crypt {
- my ($password, $md5) = @_;
- crypt($password, $md5 ? '$1$' . salt(8) : salt(2));
+sub add_users {
+ my ($users, $authentication) = @_;
+
+ alloc_user_faces($users);
+
+ foreach (@$users) {
+ create_user($_, $authentication->{md5});
+ run_program::rooted($::prefix, "usermod", "-G", join(",", @{$_->{groups}}), $_->{name}) if !is_empty_array_ref($_->{groups});
+ addKdmIcon($_->{name}, delete $_->{auto_icon} || $_->{icon});
+ }
}
+
sub enableShadow() {
run_program::rooted($::prefix, "pwconv") or log::l("pwconv failed");
run_program::rooted($::prefix, "grpconv") or log::l("grpconv failed");
}
sub hdInstallPath() {
- my $tail = first(readlink("/tmp/image") =~ m|^/tmp/hdimage/?(.*)|);
+ my $tail = first(readlink("/tmp/image") =~ m|^(?:/tmp/)?hdimage/*(.*)|);
my $head = first(readlink("/tmp/hdimage") =~ m|$::prefix(.*)|);
+ log::l("search HD install path, tail=$tail, head=$head, tail defined=" . to_bool(defined $tail));
defined $tail && ($head ? "$head/$tail" : "/mnt/hd/$tail");
}
+sub install_acpi_pkgs {
+ my ($do_pkgs, $b) = @_;
+
+ my $acpi = bootloader::get_append($b, 'acpi') or return;
+ if (!member($acpi, 'off', 'ht')) {
+ $do_pkgs->install('acpi', 'acpid') if !(-x "$::prefix/usr/bin/acpi" && -x "$::prefix/usr/sbin/acpid")
+ }
+}
+
sub setupBootloader {
my ($in, $b, $all_hds, $fstab, $security) = @_;
- my $hds = $all_hds->{hds};
require bootloader;
general:
@@ -88,18 +131,53 @@ sub setupBootloader {
local $::Wizard_no_previous = 1 if $::isStandalone;
setupBootloader__general($in, $b, $all_hds, $fstab, $security) or return 0;
}
- setupBootloader__boot_bios_drive($in, $b, $hds) or goto general;
+ setupBootloader__boot_bios_drive($in, $b, $all_hds->{hds}) or goto general;
{
local $::Wizard_finished = 1 if $::isStandalone;
setupBootloader__entries($in, $b, $all_hds, $fstab) or goto general;
}
+}
+
+sub installBootloader {
+ my ($in, $b, $all_hds) = @_;
- #- somewhere should bootloader really installed ?
- $::isStandalone and my $_w = $in->wait_message(N("Please wait"), N("Bootloader installation in progress"));
+ install_acpi_pkgs($in->do_pkgs, $b);
- eval { run_program::rooted($::prefix, 'lilo', '-u') } if $::isInstall && !$::o->{isUpgrade} && -e "$::prefix/etc/lilo.conf" && glob("$::prefix/boot/boot.*");
+ eval { run_program::rooted($::prefix, 'echo | lilo -u') } if $::isInstall && !$::o->{isUpgrade} && -e "$::prefix/etc/lilo.conf" && glob("$::prefix/boot/boot.*");
+
+ retry:
+ eval {
+ my $_w = $in->wait_message(N("Please wait"), N("Bootloader installation in progress"));
+ bootloader::install($b, $all_hds);
+ };
- bootloader::install($b, $hds);
+ if (my $err = $@) {
+ $err =~ /wizcancel/ and return;
+ $err =~ s/^\w+ failed// or die;
+ $err = formatError($err);
+ while ($err =~ s/^Warning:.*//m) {}
+ if (my ($dev) = $err =~ /^Reference:\s+disk\s+"(.*?)".*^Is the above disk an NT boot disk?/ms) {
+ if ($in->ask_yesorno('',
+formatAlaTeX(N("LILO wants to assign a new Volume ID to drive %s. However, changing
+the Volume ID of a Windows NT, 2000, or XP boot disk is a fatal Windows error.
+This caution does not apply to Windows 95 or 98, or to NT data disks.
+
+Assign a new Volume ID?", $dev)))) {
+ $b->{force_lilo_answer} = 'n';
+ } else {
+ $b->{'static-bios-codes'} = 1;
+ }
+ goto retry;
+ } else {
+ $in->ask_warn('', [ N("Installation of bootloader failed. The following error occurred:"), $err ]);
+ return;
+ }
+ } elsif (arch() =~ /ppc/) {
+ my $of_boot = cat_("$::prefix/tmp/of_boot_dev") || die "Can't open $::prefix/tmp/of_boot_dev";
+ chop($of_boot);
+ $in->ask_warn('', N("You may need to change your Open Firmware boot-device to\n enable the bootloader. If you don't see the bootloader prompt at\n reboot, hold down Command-Option-O-F at reboot and enter:\n setenv boot-device %s,\\\\:tbxi\n Then type: shut-down\nAt your next boot you should see the bootloader prompt.", $of_boot));
+ }
+ 1;
}
@@ -158,7 +236,7 @@ sub setupBootloader__mbr_or_not {
my @l = (
[ N("First sector of drive (MBR)") => '/dev/' . $hds->[0]{device} ],
- [ N("First sector of the root partition") => '/dev/' . fsedit::get_root($fstab, 'boot')->{device} ],
+ [ N("First sector of the root partition") => '/dev/' . fs::get::root($fstab, 'boot')->{device} ],
if_($floppy,
[ N("On Floppy") => "/dev/$floppy" ],
),
@@ -184,7 +262,6 @@ sub setupBootloader__general {
my ($in, $b, $all_hds, $fstab, $security) = @_;
my @method_choices = bootloader::method_choices($fstab);
- my $profiles = bootloader::has_profiles($b);
my $prev_force_acpi = my $force_acpi = bootloader::get_append($b, 'acpi') !~ /off|ht/;
my $prev_force_noapic = my $force_noapic = bootloader::get_append($b, 'noapic');
my $prev_force_nolapic = my $force_nolapic = bootloader::get_append($b, 'nolapic');
@@ -209,7 +286,7 @@ sub setupBootloader__general {
}, [
{ label => N("Bootloader to use"), val => \$b->{method}, list => \@method_choices, format => \&bootloader::method2text },
if_(arch() !~ /ia64/,
- { label => N("Boot device"), val => \$b->{boot}, list => [ map { "/dev/$_" } (map { $_->{device} } (@{$all_hds->{hds}}, grep { !isFat_or_NTFS($_) } @$fstab)), detect_devices::floppies_dev() ], not_edit => !$::expert },
+ { label => N("Boot device"), val => \$b->{boot}, list => [ map { "/dev/$_->{device}" } bootloader::allowed_boot_parts($b, $all_hds) ], not_edit => !$::expert },
),
{ label => N("Delay before booting default image"), val => \$b->{timeout} },
{ text => N("Enable ACPI"), val => \$force_acpi, type => 'bool' },
@@ -225,7 +302,6 @@ sub setupBootloader__general {
{ text => N("Clean /tmp at each boot"), val => \$clean_tmp, type => 'bool', advanced => 1 },
{ label => N("Precise RAM size if needed (found %d MB)", availableRamMB()), val => \$memsize, advanced => 1 },
if_(detect_devices::isLaptop(),
- { text => N("Enable multiple profiles"), val => \$profiles, type => 'bool', advanced => 1 },
),
]) or return 0;
} else {
@@ -247,11 +323,16 @@ sub setupBootloader__general {
#- remove bios mapping if the user changed the boot device
delete $b->{bios} if $b->{boot} ne $prev_boot;
+ if ($b->{boot} =~ m!/dev/md\d+$!) {
+ $b->{'raid-extra-boot'} = 'mbr';
+ } else {
+ delete $b->{'raid-extra-boot'} if $b->{'raid-extra-boot'} eq 'mbr';
+ }
+
if ($b->{method} eq 'grub') {
- $in->do_pkgs->ensure_is_installed('grub', "/usr/sbin/grub", 1) or return 0;
+ $in->do_pkgs->ensure_binary_is_installed('grub', "grub", 1) or return 0;
}
- bootloader::set_profiles($b, $profiles);
bootloader::set_append($b, "mem", $memsize || 0);
if ($prev_force_acpi != $force_acpi) {
bootloader::set_append($b, acpi => ($force_acpi ? '' : 'ht'));
@@ -264,8 +345,8 @@ sub setupBootloader__general {
}
if ($prev_clean_tmp != $clean_tmp) {
- if ($clean_tmp && !fsedit::has_mntpoint('/tmp', $all_hds)) {
- push @{$all_hds->{special}}, { device => 'none', mntpoint => '/tmp', pt_type => 'tmpfs' };
+ if ($clean_tmp && !fs::get::has_mntpoint('/tmp', $all_hds)) {
+ push @{$all_hds->{special}}, { device => 'none', mntpoint => '/tmp', fs_type => 'tmpfs' };
} else {
@{$all_hds->{special}} = grep { $_->{mntpoint} ne '/tmp' } @{$all_hds->{special}};
}
@@ -279,20 +360,23 @@ sub setupBootloader__entries {
require Xconfig::resolution_and_depth;
my $Modify = sub {
+ require network::netconnect; #- to list network profiles
my ($e) = @_;
my $default = my $old_default = $e->{label} eq $b->{default};
my $vga = Xconfig::resolution_and_depth::from_bios($e->{vga});
+ my ($append, $netprofile) = bootloader::get_append_netprofile($e);
my @l;
if ($e->{type} eq "image") {
@l = (
{ label => N("Image"), val => \$e->{kernel_or_dev}, list => [ map { "/boot/$_" } bootloader::installed_vmlinuz() ], not_edit => 0 },
{ label => N("Root"), val => \$e->{root}, list => [ map { "/dev/$_->{device}" } @$fstab ], not_edit => !$::expert },
-{ label => N("Append"), val => \$e->{append} },
+{ label => N("Append"), val => \$append },
if_(arch() !~ /ppc|ia64/,
{ label => N("Video mode"), val => \$vga, list => [ '', Xconfig::resolution_and_depth::bios_vga_modes() ], format => \&Xconfig::resolution_and_depth::to_string, advanced => 1 },
),
{ label => N("Initrd"), val => \$e->{initrd}, list => [ map { if_(/^initrd/, "/boot/$_") } all("$::prefix/boot") ], not_edit => 0, advanced => 1 },
+{ label => N("Network profile"), val => \$netprofile, list => [ sort(uniq('', $netprofile, network::netconnect::get_profiles())) ], advanced => 1 },
);
} else {
@l = (
@@ -331,6 +415,7 @@ sub setupBootloader__entries {
$b->{default} = $old_default || $default ? $default && $e->{label} : $b->{default};
$e->{vga} = ref($vga) ? $vga->{bios} : $vga;
+ bootloader::set_append_netprofile($e, $append, $netprofile);
bootloader::configure_entry($e); #- hack to make sure initrd file are built.
1;
};
@@ -343,7 +428,7 @@ sub setupBootloader__entries {
N_("Other OS (MacOS...)") : N_("Other OS (Windows...)") ]
) eq "Linux") {
$e = { type => 'image',
- root => '/dev/' . fsedit::get_root($fstab)->{device}, #- assume a good default.
+ root => '/dev/' . fs::get::root($fstab)->{device}, #- assume a good default.
};
$prefix = "linux";
} else {
@@ -384,17 +469,6 @@ You can create additional entries or change the existing ones."), [ {
}
}
-my @etc_pass_fields = qw(name pw uid gid realname home shell);
-sub unpack_passwd {
- my ($l) = @_;
- my %l; @l{@etc_pass_fields} = split ':', chomp_($l);
- \%l;
-}
-sub pack_passwd {
- my ($l) = @_;
- join(':', @$l{@etc_pass_fields}) . "\n";
-}
-
sub get_autologin() {
my %desktop = getVarsFromSh("$::prefix/etc/sysconfig/desktop");
my $desktop = $desktop{DESKTOP} || 'KDE';
@@ -411,33 +485,51 @@ sub get_autologin() {
}
sub set_autologin {
- my ($user, $desktop) = @_;
- my $autologin = bool2text($user);
+ my ($o_user, $o_wm) = @_;
+ log::l("set_autologin $o_user $o_wm");
+ my $autologin = bool2text($o_user);
#- Configure KDM / MDKKDM
eval { update_gnomekderc("$::prefix/usr/share/config/kdm/kdmrc", 'X-:0-Core' => (
AutoLoginEnable => $autologin,
- AutoLoginUser => $user,
+ AutoLoginUser => $o_user,
)) };
#- Configure GDM
eval { update_gnomekderc("$::prefix/etc/X11/gdm/gdm.conf", daemon => (
AutomaticLoginEnable => $autologin,
- AutomaticLogin => $user,
+ AutomaticLogin => $o_user,
)) };
- if ($user) {
- my %l = getVarsFromSh("$::prefix/etc/sysconfig/desktop");
- $l{DESKTOP} = $desktop;
- setVarsInSh("$::prefix/etc/sysconfig/desktop", \%l);
- log::l("cat $::prefix/etc/sysconfig/desktop ($desktop):\n", cat_("$::prefix/etc/sysconfig/desktop"));
- }
my $xdm_autologin_cfg = "$::prefix/etc/sysconfig/autologin";
- if (member($desktop, 'KDE', 'GNOME')) {
+ if (member($o_wm, 'KDE', 'GNOME')) {
unlink $xdm_autologin_cfg;
} else {
setVarsInShMode($xdm_autologin_cfg, 0644,
- { USER => $user, AUTOLOGIN => bool2yesno($user), EXEC => '/usr/X11R6/bin/startx.autologin' });
+ { USER => $o_user, AUTOLOGIN => bool2yesno($o_user), EXEC => '/usr/X11R6/bin/startx.autologin' });
+ }
+
+ if ($o_user) {
+ my $home = (getpwnam($o_user))[7];
+ set_window_manager($home, $o_wm);
+ }
+}
+sub set_window_manager {
+ my ($home, $wm) = @_;
+ log::l("set_window_manager $home $wm");
+ my $p_home = "$::prefix$home";
+
+ #- for KDM/GDM
+ my $wm_number = sessions_with_order()->{$wm} || '';
+ update_gnomekderc("$p_home/.dmrc", 'Desktop', Session => "$wm_number$wm");
+ my $user = find { $p_home eq $_->[7] } list_passwd();
+ chown($user->[2], $user->[3], "$p_home/.dmrc");
+
+ #- for startx/autologin
+ {
+ my %l = getVarsFromSh("$p_home/.desktop");
+ $l{DESKTOP} = $wm;
+ setVarsInSh("$p_home/.desktop", \%l);
}
}
@@ -481,7 +573,7 @@ sub inspect {
$dir = '';
} else {
mkdir $dir, 0700;
- eval { fs::mount($part->{device}, $dir, type2fs($part, 'skip'), !$b_rw) };
+ eval { fs::mount($part->{device}, $dir, $part->{fs_type}, !$b_rw) };
$@ and return;
}
my $h = before_leaving {
@@ -540,7 +632,7 @@ sub ask_user_one {
ok_disabled => sub { $security >= 4 && !@$users || $options{needauser} && !$u->{name} },
} }, [
{ label => N("Real name"), val => \$u->{realname} },
- { label => N("User name"), val => \$u->{name} },
+ { label => N("Login name"), val => \$u->{name} },
{ label => N("Password"),val => \$u->{password}, hidden => 1 },
{ label => N("Password (again)"), val => \$u->{password2}, hidden => 1 },
{ label => N("Shell"), val => \$u->{shell}, list => [ shells() ], not_edit => !$::expert, advanced => 1 },
@@ -574,6 +666,10 @@ sub ask_users {
sub sessions() {
split(' ', run_program::rooted_get_stdout($::prefix, '/usr/sbin/chksession', '-l'));
}
+sub sessions_with_order() {
+ my %h = map { /(.*)=(.*)/ } split(' ', run_program::rooted_get_stdout($::prefix, '/usr/sbin/chksession', '-L'));
+ \%h;
+}
sub autologin {
my ($o, $in) = @_;
@@ -605,7 +701,7 @@ sub selectLanguage {
my ($in, $lang, $o_langs_) = @_;
my $common = { messages => N("Please choose a language to use."),
- title => 'language choice',
+ title => N("Language choice"),
interactive_help_id => 'selectLanguage' };
if ($::isInstall) {
@@ -646,7 +742,7 @@ when your installation is complete and you restart your system.")),
$in->ask_from_($common,
[ { val => \$lang, separator => '|',
if_($using_images, image2f => sub { $name2l{$_[0]} =~ /^[a-z]/ ? ('', "langs/lang-$name2l{$_[0]}") : $_[0] }),
- format => sub { $_[0] =~ /(.*\|)(.*)/ ? $1.lang::l2name($2) : lang::l2name($_[0]) },
+ format => sub { $_[0] =~ /(.*\|)(.*)/ ? $1 . lang::l2name($2) : lang::l2name($_[0]) },
list => \@langs, sort => 0 },
if_($o_langs_ && !$::move,
{ val => \$in->{locale}{utf8}, type => 'bool', text => N("Use Unicode by default"), advanced => 1 },
@@ -693,38 +789,23 @@ sub selectCountry {
messages => N("Please choose your country."),
interactive_help_id => 'selectCountry',
advanced_messages => N("Here is the full list of available countries"),
- advanced_label => N("More"),
+ advanced_label => N("Other Countries"),
advanced_state => $ext_country && scalar(@best),
- callbacks => { changed => sub { $other = $_[0] == 1 } },
+ callbacks => { changed => sub { $_[0] != 2 and $other = $_[0] == 1 } },
},
[ if_(@best, { val => \$country, type => 'list', format => \&lang::c2name,
list => \@best, sort => 1 }),
{ val => \$ext_country, type => 'list', format => \&lang::c2name,
- list => [ difference2(\@countries, \@best) ], advanced => scalar(@best) }
+ list => [ @countries ], advanced => scalar(@best) },
+ { val => \$locale->{IM}, type => 'combo', label => N("Input method:"), sort => 0,
+ list => [ N_("None"), sort(lang::get_ims()) ], format => sub { uc(translate($_[0])) },
+ advanced => !$locale->{IM} || $locale->{IM} eq 'None',
+ },
]) or return;
$locale->{country} = $other || !@best ? $ext_country : $country;
}
-sub write_passwd_user {
- my ($u, $isMD5) = @_;
-
- $u->{pw} = $u->{password} ? &crypt($u->{password}, $isMD5) : $u->{pw} || '';
- $u->{shell} ||= '/bin/bash';
-
- substInFile {
- my $l = unpack_passwd($_);
- if ($l->{name} eq $u->{name}) {
- add2hash_($u, $l);
- $_ = pack_passwd($u);
- $u = {};
- }
- if (eof && $u->{name}) {
- $_ .= pack_passwd($u);
- }
- } "$::prefix/etc/passwd";
-}
-
sub set_login_serial_console {
my ($port, $speed) = @_;
@@ -790,6 +871,8 @@ UNREGISTER ^$devfs_if\$ CFUNCTION GLOBAL unlink $of
UNREGISTER ^$devfs_if\$ CFUNCTION GLOBAL unlink $if
") if $devfs_if ne $if && $if !~ /^hd[a-z]/ && $if !~ /^sr/ && $if !~ /^sd[a-z]/;
+ output_p("$::prefix/etc/udev/rules.d/$of.rules", qq(KERNEL="$if", SYMLINK="$of"\n));
+
#- when creating a symlink on the system, use devfs name if devfs is mounted
symlinkf($devfs_if, "$::prefix/dev/$if") if $devfs_if ne $if && detect_devices::dev_is_devfs();
symlinkf($if, "$::prefix/dev/$of");
@@ -807,13 +890,18 @@ UNREGISTER ^$devfs_if\$ EXECUTE /etc/dynamic/scripts/rawdevice.script del /dev/$
");
}
-sub fix_broken_alternatives() {
+sub fix_broken_alternatives {
+ my ($force_default) = @_;
#- fix bad update-alternatives that may occurs after upgrade (and sometimes for install too).
-d "$::prefix/etc/alternatives" or return;
foreach (all("$::prefix/etc/alternatives")) {
- next if run_program::rooted($::prefix, 'test', '-e', "/etc/alternatives/$_");
- log::l("fixing broken alternative $_");
+ if ($force_default) {
+ log::l("setting alternative $_");
+ } else {
+ next if run_program::rooted($::prefix, 'test', '-e', "/etc/alternatives/$_");
+ log::l("fixing broken alternative $_");
+ }
run_program::rooted($::prefix, 'update-alternatives', '--auto', $_);
}
}
@@ -852,8 +940,10 @@ Allowing this will permit users to simply click on \"Share\" in konqueror and na
%l = ($type => 1);
} else {
%l = map_each { $::a => services::starts_on_boot($::b->[1]) } %types;
- $in->ask_from('', N("You can export using NFS or SMB. Please select which you would like to use."),
- [ map { { text => $types{$_}[2], val => \$l{$_}, type => 'bool' } } keys %l ]) or return;
+ $in->ask_from_({ messages => N("You can export using NFS or SMB. Please select which you would like to use."),
+ callbacks => { ok_disabled => sub { !any { $_ } values %l } },
+ },
+ [ map { { text => $types{$_}[2], val => \$l{$_}, type => 'bool' } } keys %l ]) or return;
}
foreach (keys %types) {
my ($pkg, $service, $_descr) = @{$types{$_}};
@@ -875,7 +965,7 @@ Allowing this will permit users to simply click on \"Share\" in konqueror and na
run_program::rooted($::prefix, 'groupadd', '-r', 'fileshare');
if ($in->ask_from_no_check(
{
- -e '/usr/sbin/userdrake' ? (ok => N("Launch userdrake"), cancel => N("Cancel")) : (cancel => ''),
+ -e '/usr/sbin/userdrake' ? (ok => N("Launch userdrake"), cancel => N("Close")) : (cancel => ''),
messages =>
N("The per-user sharing uses the group \"fileshare\".
You can use userdrake to add a user to this group.")