# yum-lib.pl # Functions for installing packages with yum if ($config{'yum_config'}) { $yum_config = $config{'yum_config'}; } elsif (&has_command("yum")) { $yum_config = "/etc/yum.conf"; } elsif (&has_command("dnf")) { $yum_config = "/etc/dnf/dnf.conf"; } $yum_command = &has_command("dnf") || &has_command("yum") || "yum"; $yum_repos_dir = "/etc/yum.repos.d"; sub list_update_system_commands { return ($yum_command); } # get_dnf_version() # Returns the DNF major version, or zero when using YUM. sub get_dnf_version { return 0 if ($yum_command !~ /(?:^|\/)dnf(?:-\d+)?$/); if (!defined($dnf_version)) { &clean_language(); my $out = &backquote_command( "$yum_command --version 2>&1 &1 = 5 ? '--setopt=disable_excludes=*' : '--disableplugin=versionlock'; } # update_system_install([packages], [&in], [no-force], [flags]) # Installs or updates packages with YUM or DNF. sub update_system_install { local $update = $_[0] || $in{'update'}; local $in = $_[1]; local $force = !$_[2]; local $flags = $_[3]; local $versionlock_update = defined($flags) && $flags eq &update_system_hold_flags(); local $runflags = $versionlock_update ? undef : $flags; local $qflags; $qflags = &trim(join(" ", map { quotemeta($_) } split(/ /, $runflags))) if ($runflags); $update =~ s/\.\*/\*/g; local $enable; if ($in->{'enablerepo'}) { $enable = "enablerepo=".quotemeta($in->{'enablerepo'}); } local (@rv, @newpacks); # If there are multiple architectures to update for a package, split them out local @updates = split(/\s+/, $update); local @names = map { &append_architectures($_) } split(/\s+/, $update); if (@names == 1) { @names = ( $update ); } $update = join(" ", map { quotemeta($_) } @names); # Temporarily unlock selected packages, then restore their holds after the # transaction. local @relock; if ($versionlock_update) { my @packages = &unique(@updates); my $unlock_error = &delete_update_system_holds( \@packages, \@relock); if ($unlock_error) { my $relock_error = &restore_update_system_holds(\@relock); if ($relock_error) { print &text('yum_reholdfailed', "".&html_escape(join(" ", @packages))."", &html_escape($relock_error)),"

\n"; } print &text('yum_unholdfailed', "".&html_escape(join(" ", @packages))."", &html_escape($unlock_error)),"

\n"; return ( ); } } # Use update for installed DNF packages and install for everything else. local $cmd; if ($yum_command =~ /dnf$/) { local @pinfo = &package_info($updates[0]); if ($pinfo[0]) { $cmd = "update"; } else { $cmd = "install"; } } else { $cmd = "install"; } # Work out the command to run, which may enable some repos my $uicmd = "$yum_command $enable -y $cmd ".join(" ", @names); $uicmd .= " $runflags" if ($runflags); my $fullcmd = "$yum_command $enable -y $cmd $update"; $fullcmd .= " $qflags" if ($qflags); foreach my $u (@updates) { my $repo = &update_system_repo($u); if ($repo) { $fullcmd = "$yum_command -y $cmd $repo ; $fullcmd"; } } print &text('yum_install', "".&html_escape($uicmd).""),"\n"; print "

";
&additional_log('exec', undef, $fullcmd);
$SIG{'TERM'} = 'ignore';	# Installing webmin itself may kill this script
&open_execute_command(CMD, "$fullcmd ) {
	s/\r|\n//g;
	if (/^\[(update|install|deps):\s+(\S+)\s+/) {
		push(@rv, $2);
		}
	elsif (/^(Installed|Dependency Installed|Updated|Dependency Updated|Upgraded):\s*(.*)/) {
		# Line like :
		# Updated:
		#   wbt-virtual-server-theme.x86
		local @pkgs = split(/\s+/, $2);
		if (!@pkgs) {
			# Wrapped to next line(s)
			while(1) {
				local $pkgs = ;
				last if (!$pkgs);
				print &html_escape($pkgs);
				$pkgs =~ s/^\s+//;
				$pkgs =~ s/\s+$//;
				my @linepkgs = split(/\s+/, $_);
				last if (!@linepkgs);
				push(@pkgs, @linepkgs);
				}
			}
		foreach my $p (@pkgs) {
			if ($p !~ /:/ && $p =~ /^(\S+)\.(\S+)$/) {
				my $pname = $1;
				if ($p =~ /[^0-9\.\-\_i]/) {
					push(@rv, $pname);
					}
				}
			}
		}
	elsif (/^\s+(Updating|Installing|Upgrading)\s+:\s+(\S+)/) {
		# Older DNF and YUM progress lines, for example:
		#   Updating       : wbt-virtual-server-theme       1/2 
		# or
		#   Installing : 2:nmap-5.51-2.el6.i686             1/1
		local $pkg = $2;
		$pkg =~ s/^\d://;	# Strip epoch from front
		$pkg =~ s/\-\d.*$//;	# Strip version number from end
		push(@rv, $pkg);
		}
	elsif (/\]\s+(Upgrading|Installing|Downgrading|Reinstalling)\s+(\S+)/) {
		# DNF 5 progress line, for example:
		# [3/8] Upgrading libcurl-0:8.11.1-5.fc42 100% ...
		local $pkg = &update_system_nevra_name($2);
		push(@rv, $pkg) if ($pkg);
		}
	if (!/ETA/ && !/\%\s+done\s+\d+\/\d+\s*$/) {
		print &html_escape($_."\n");
		}
	if ($update =~ /perl\(/ && /No\s+package\s+.*available/i) {
		$nopackage = 1;
		}
	}
close(CMD);
local $status = $?;

# Restore holds at the versions now installed.
if (@relock) {
	local $relock_error = &restore_update_system_holds(\@relock);
	if ($relock_error) {
		print &text('yum_reholdfailed',
			"".&html_escape(join(" ", &unique(@updates)))."",
			&html_escape($relock_error)),"

\n"; } } print "

\n"; if ($status || $nopackage) { print "$text{'yum_failed'}

\n"; return ( ); } else { print "$text{'yum_ok'}

\n"; return &unique(@rv); } } # append_architectures(package) # Given a package name, if it has multiple architectures return the name with # each appended sub append_architectures { my ($name) = @_; local %packages; my $n = &list_packages($name); return ( $name ) if (!$n); my @rv; for(my $i=0; $i<$n; $i++) { if ($packages{$i,'arch'}) { push(@rv, $packages{$i,'name'}.".".$packages{$i,'arch'}); } else { push(@rv, $packages{$i,'name'}); } } @rv = &unique(@rv); return @rv; } # update_system_nevra_name(nevra) # Extracts the package name from a NEVRA string printed by DNF. sub update_system_nevra_name { my ($nevra) = @_; $nevra =~ s/^\d+://; # Older DNF may put the epoch before the name return $1 if ($nevra =~ /^(.+)-\d+:/); return $1 if ($nevra =~ /^(.+)-\d[^-]*-[^-]+(?:\.[^.]+)?$/); return undef; } # update_system_operations(packages, [flags]) # Returns packages YUM or DNF would install or update. DNF previews the # transaction directly; YUM uses shell mode. sub update_system_operations { my ($packages, $flags) = @_; if ($yum_command =~ /(?:^|\/)dnf(?:-\d+)?$/) { return &update_system_dnf_operations($packages, $flags); } my $temp = &transname(); &open_tempfile(SHELL, ">$temp", 0, 1); &print_tempfile(SHELL, "install $packages\n"); &print_tempfile(SHELL, "transaction solve\n"); &close_tempfile(SHELL); my @rv; open(SHELL, "$yum_command shell $temp |"); while() { if (/Package\s+(\S+)\s+(\S+)\s+(set|will\s+be\s+an\s+update)/i) { my $pkg = { 'name' => $1, 'version' => $2 }; if ($pkg->{'name'} =~ s/\.([^\.]+)$//) { $pkg->{'arch'} = $1; } if ($pkg->{'version'} =~ s/^(\S+)://) { $pkg->{'epoch'} = $1; } push(@rv, $pkg); } } close(SHELL); &unlink_file($temp); return @rv; } # update_system_dnf_operations(packages, [flags]) # Returns packages DNF would install or update in a simulated transaction. sub update_system_dnf_operations { my ($packages, $flags) = @_; my @rv; my $hold_override = defined($flags) && $flags eq &update_system_hold_flags(); my $runflags = defined($flags) && !$hold_override ? &trim(join(" ", map { quotemeta($_) } split(/\s+/, $flags))) : ""; my @relock; if ($hold_override) { my @selected = &unique(split(/\s+/, $packages)); my $error = &delete_update_system_holds(\@selected, \@relock); if ($error) { &restore_update_system_holds(\@relock) if (@relock); return ( ); } } # DNF 5 does not upgrade installed packages with install, so use upgrade for # its previews. my $action = &get_dnf_version() >= 5 ? 'upgrade' : 'install'; my $command = "$yum_command --assumeno $action ". join(" ", map { quotemeta($_) } split(/\s+/, $packages)); $command .= " $runflags" if ($runflags); $command .= " 2>/dev/null"; &clean_language(); &open_execute_command(DNF, $command, 1, 1); my ($intable, $skip, $wrapped); while() { s/\r|\n//g; if (/^\s*Package\s+Arch(itecture)?\s+Version\s+Repo/i) { # Start reading the transaction table. $intable = 1; } elsif (/^\s*Transaction\s+Summary/i) { last; } elsif (!$intable || /^=+$/) { next; } elsif (/^\S/) { # Ignore sections that do not add package versions. $skip = !/^(Installing|Upgrading|Reinstalling|Downgrading)/i; $wrapped = undef; } elsif ($skip) { next; } elsif (/^\s+(\S+)\s*$/) { # Save a long package name wrapped onto its own line. $wrapped = $1; } elsif (/^\s+replacing\s/i) { # Ignore an old package shown below its replacement. $wrapped = undef; } elsif (/^\s+\S/) { # Parse a complete row, or the remainder of a wrapped row. my @cols = split(/\s+/, &trim($_)); unshift(@cols, $wrapped) if ($wrapped); $wrapped = undef; next if (@cols < 4); my $pkg = { 'name' => $cols[0], 'arch' => $cols[1], 'version' => $cols[2] }; if ($pkg->{'version'} =~ s/^(\S+)://) { $pkg->{'epoch'} = $1; } push(@rv, $pkg); } } close(DNF); &reset_environment(); if (@relock) { my $error = &restore_update_system_holds(\@relock); return ( ) if ($error); } return @rv; } # show_update_system_opts() # Returns HTML for enabling a repository, if any are disabled sub show_update_system_opts { local @pinfo = &package_info("yum"); if (&compare_versions($pinfo[4], "2.1.10") > 0) { local $conf = &get_yum_config(); local @ena; foreach my $r (@$conf) { if ($r->{'values'}->{'enabled'} eq '0') { push(@ena, $r->{'name'}); } } if (@ena) { return $text{'yum_enable'}." ". &ui_select("enablerepo", "", [ [ "", $text{'yum_none'} ], map { [ $_ ] } @ena ]); } } return undef; } # update_system_resolve(name) # Converts a standard package name like apache, sendmail or squid into # the name used by YUM. sub update_system_resolve { my ($name) = @_; $name = $name eq "apache" ? "httpd mod_.*" : $name eq "dhcpd" ? "dhcp dhcp-server" : $name eq "mysql" ? "perl-DBD-MySQL mariadb mariadb-server mysql mysql-server" : $name eq "openssh" ? "openssh openssh-server" : $name eq "postgresql" ? "postgresql postgresql-libs postgresql-server" : $name eq "openldap" ? "openldap-servers openldap-clients" : $name eq "ldap" ? "nss-pam-ldapd pam_ldap nss_ldap" : $name eq "virtualmin-modules" ? "wbm-.*" : $name; my $flags; $flags = '--skip-broken' if ($_[0] =~ /^dhcpd|mysql$/); return wantarray ? ($name, $flags) : $name; } # update_system_repo(package) # Returns the extra repo package that needs to be installed first before # installing some package, if needed sub update_system_repo { local ($name) = @_; return $name eq "certbot" ? "epel-release" : undef; } # update_system_available() # Returns a list of package names and versions that are available from YUM sub update_system_available { local @rv; local %done; &open_execute_command(PKG, "$yum_command info", 1, 1); while() { s/\r|\n//g; if (/^Name\s*:\s*(\S+)/) { if ($done{$1}) { # Seen before .. update with newer info. This can happen # when YUM shows the installed version first. $pkg = $done{$1}; delete($pkg->{'epoch'}); delete($pkg->{'version'}); } else { # Start of a new package $pkg = { 'name' => $1 }; $done{$pkg->{'name'}} = $pkg; push(@rv, $pkg); } } elsif (/^Arch\s*:\s*(\S+)/) { $pkg->{'arch'} = $1; } elsif (/^Version\s*:\s*(\S+)/) { $pkg->{'version'} = $1; if ($pkg->{'version'} =~ s/^(\S+)://) { $pkg->{'epoch'} = $1; } } elsif (/^Release\s*:\s*(\S+)/) { $pkg->{'version'} .= "-".$1; } elsif (/^Repo\s*:\s*(\S+)/) { $pkg->{'source'} = $1; } elsif (/^Summary\s*:\s*(\S.*)/) { $pkg->{'desc'} = $1; } elsif (/^Epoch\s*:\s*(\S.*)/) { $pkg->{'epoch'} = $1; } } close(PKG); @rv = grep { $_->{'arch'} ne 'src' } @rv; &set_yum_security_field(\%done); return @rv; } # set_yum_security_field(&package-hash) # Set security field on packages which are security updates sub set_yum_security_field { local ($done) = @_; &open_execute_command(PKG, "$yum_command updateinfo list sec 2>/dev/null", 1, 1); while() { s/\r|\n//g; if (/^\S+\s+\S+\s+(\S+?)\-([0-9]\S+)\.([^\.]+)$/) { local ($name, $ver) = ($1, $2); if ($done->{$name}) { $done->{$name}->{'source'} ||= 'security'; $done->{$name}->{'security'} = 1; } } } close(PKG); &open_execute_command(PKG, "$yum_command list-sec 2>/dev/null", 1, 1); while() { s/\r|\n//g; next if (/^(Loaded|updateinfo)/); if (/^\S+\s+\S+\s+(\S+?)\-([0-9]\S+)\.([^\.]+)$/) { local ($name, $ver) = ($1, $2); if ($done->{$name}) { $done->{$name}->{'source'} ||= 'security'; $done->{$name}->{'security'} = 1; } } } close(PKG); } # update_system_updates([include-holds]) # Returns available package updates, optionally including version-locked ones. sub update_system_updates { my ($include_holds) = @_; local @rv; local %done; if ($yum_command =~ /dnf/) { my $holdflag = $include_holds && &supports_update_system_holds() ? " ".&update_system_hold_flags() : ""; $holdflag =~ s/\*/\\*/g; &open_execute_command(PKG, "$yum_command$holdflag check-update 2>/dev/null", 1, 1); } else { &open_execute_command(PKG, "$yum_command check-update 2>/dev/null | tr '\n' '#' | sed -e 's/# / /g' | tr '#' '\n'", 1, 1); } while() { s/\r|\n//g; if (/^(\S+)\.([^\.]+)\s+(\S+)\s+(\S+)/ && $2 ne 'src') { local $pkg = { 'name' => $1, 'arch' => $2, 'version' => $3, 'source' => $4 }; if ($pkg->{'version'} =~ s/^(\S+)://) { $pkg->{'epoch'} = $1; } $done{$pkg->{'name'}} = $pkg; push(@rv, $pkg); } last if (/Obsoleting\s+Packages/i); } close(PKG); if (&supports_update_system_holds()) { my %holds = map { $_, 1 } &list_update_system_holds(); foreach my $pkg (@rv) { $pkg->{'held'} = 1 if ($holds{$pkg->{'name'}}); } @rv = grep { !$_->{'held'} } @rv if (!$include_holds); } &set_yum_security_field(\%done); return @rv; } # update_system_hold_spec_name(spec) # Extracts the package name from an exact DNF 4 versionlock entry. sub update_system_hold_spec_name { my ($spec) = @_; return undef if ($spec =~ /^!/); return $1 if ($spec =~ /^(.+)-\d+:[^-]+-[^-]+\.[^.]+$/); return $1 if ($spec =~ /^\d+:(.+)-[^-]+-[^-]+\.[^.]+$/); return undef; } # list_update_system_hold_specs() # Returns exact locks that Webmin can safely manage by package name. sub list_update_system_hold_specs { return ( ) if (!&supports_update_system_holds()); my @locks; &clean_language(); &open_execute_command(VLOCK, "$yum_command -q versionlock list 2>/dev/null", 1, 1); if (&get_dnf_version() >= 5) { # DNF 5 formats each lock as a multi-line package block. my ($lock, @blocks); while() { s/\r|\n//g; if (/^Package name:\s*(\S+)/) { push(@blocks, $lock) if ($lock); $lock = { 'name' => $1, 'spec' => $1 }; } elsif ($lock && /^evr\s*=\s*(\S+)/) { $lock->{'exact'} = 1; } elsif ($lock && /\S/ && !/^\s*#/) { $lock->{'custom'} = 1; } elsif (!/\S/) { push(@blocks, $lock) if ($lock); $lock = undef; } } push(@blocks, $lock) if ($lock); # Ignore globs, duplicate entries and custom conditions because deleting # them by name could remove rules that Webmin cannot restore. my (%blocks, %custom); foreach my $block (@blocks) { $blocks{$block->{'name'}}++; $custom{$block->{'name'}} = 1 if (!$block->{'exact'} || $block->{'custom'}); } @locks = grep { !$custom{$_->{'name'}} && $blocks{$_->{'name'}} == 1 && $_->{'name'} !~ /[\*\?\[\]]/ } @blocks; } else { # DNF 4 prints standard entries as name-epoch:version-release.arch. while() { s/\r|\n//g; my $name = &update_system_hold_spec_name($_); push(@locks, { 'name' => $name, 'spec' => $_ }) if ($name); } } close(VLOCK); &reset_environment(); return @locks; } # list_update_system_holds() # Returns package names held by exact DNF version locks. sub list_update_system_holds { my %holds = map { $_->{'name'}, 1 } &list_update_system_hold_specs(); return sort keys %holds; } # run_update_system_hold(action, package) # Runs one versionlock operation. Returns undef on success, or error text. sub run_update_system_hold { my ($action, $package) = @_; my $cmd = "$yum_command -q versionlock $action ".quotemeta($package); my $out; &clean_language(); my $status = &execute_command_logged($cmd, undef, \$out, \$out); &reset_environment(); if ($status) { $out = &trim($out); return $out || &text('yum_versionlock_failed', $action); } return undef; } # delete_update_system_holds(&packages, &removed) # Unlocks selected packages and records each removed hold for restoration. sub delete_update_system_holds { my ($packages, $removed) = @_; my %held = map { $_, 1 } &list_update_system_holds(); my $error; foreach my $package (&unique(@$packages)) { next if (!$held{$package}); $error = &run_update_system_hold('delete', $package); last if ($error); push(@$removed, $package); } return $error; } # restore_update_system_holds(&packages) # Re-locks packages at their currently installed versions. sub restore_update_system_holds { my ($packages) = @_; foreach my $package (&unique(@$packages)) { my $error = &run_update_system_hold('add', $package); return $error if ($error); } return undef; } # update_system_hold(&packages, hold) # Holds or unholds DNF packages by name. Returns undef on success, or error text. sub update_system_hold { my ($packages, $hold) = @_; return $text{'yum_versionlock_missing'} if (!&supports_update_system_holds()); my @packages = &unique(@$packages); return $text{'yum_versionlock_none'} if (!@packages); if ($hold) { foreach my $package (@packages) { my $error = &run_update_system_hold('add', $package); return $error if ($error); } return undef; } my @removed; return &delete_update_system_holds(\@packages, \@removed); } # get_yum_config() # Returns entries from the YUM config file, as a list of hash references sub get_yum_config { local @rv; local $sect; open(CONF, "<".$yum_config); while() { s/\r|\n//g; s/^\s*#.*$//; if (/^\s*\[(.*)\]/) { # Start of a section $sect = { 'name' => $1, 'values' => { } }; push(@rv, $sect); } elsif (/^\s*(\S+)\s*=\s*(.*)/ && $sect) { # Value in a section $sect->{'values'}->{lc($1)} = $2; } } close(CONF); return \@rv; } # list_package_repos() # Returns a list of configured repositories sub list_package_repos { my @rv; # Parse the raw repo files my $repo; foreach my $f (glob("$yum_repos_dir/*.repo")) { my $lref = &read_file_lines($f, 1); my $lnum = 0; foreach my $l (@$lref) { $l =~ s/#.*$//; if ($l =~ /^\[(\S+)\]/) { # Start of a new repo $repo = { 'file' => $f, 'line' => $lnum, 'eline' => $lnum, 'id' => $1, }; push(@rv, $repo); } elsif ($l =~ /^([^= ]+)=(.*)$/ && $repo) { # Line in a repo $repo->{'raw'}->{$1} = $2; $repo->{'eline'} = $lnum; } $lnum++; } } # Extract common information foreach my $repo (@rv) { my $name = $repo->{'raw'}->{'name'}; $name =~ s/\s*-.*//; $name =~ s/\s*\$[a-z0-9]+//gi; $repo->{'name'} = $repo->{'id'}." (".$name.")"; $repo->{'url'} = $repo->{'raw'}->{'baseurl'} || $repo->{'raw'}->{'mirrorlist'}; $repo->{'enabled'} = defined($repo->{'raw'}->{'enabled'}) ? $repo->{'raw'}->{'enabled'} : 1; } return @rv; } # create_repo_form() # Returns HTML for a package repository creation form sub create_repo_form { my $rv; $rv .= &ui_table_row($text{'yum_repo_id'}, &ui_textbox("id", undef, 20)); $rv .= &ui_table_row($text{'yum_repo_name'}, &ui_textbox("name", undef, 60)); $rv .= &ui_table_row($text{'yum_repo_url'}, &ui_textbox("url", undef, 60)); $rv .= &ui_table_row($text{'yum_repo_gpg'}, &ui_opt_textbox("gpg", undef, 60, $text{'yum_repo_none'})); return $rv; } # create_repo_parse(&in) # Parses input from create_repo_form, and returns either a new repo object or # an error string sub create_repo_parse { my ($in) = @_; my $repo = { 'raw' => { 'enabled' => 1 } }; # ID must be valid and unique $in->{'id'} =~ /^[a-z0-9\-\_]+$/i || return $text{'yum_repo_eid'}; my ($clash) = grep { $_->{'id'} eq $in->{'id'} } &list_package_repos(); $clash && return $text{'yum_repo_eidclash'}; $repo->{'id'} = $in->{'id'}; # Human-readable repo name $in->{'name'} =~ /\S/ || return $text{'yum_repo_ename'}; $repo->{'raw'}->{'name'} = $in->{'name'}; # Base URL $in->{'url'} =~ /^(http|https):/ || return $text{'yum_repo_eurl'}; $repo->{'raw'}->{'baseurl'} = $in->{'url'}; # GPG key file if (!$in->{'gpg_def'}) { -r $in->{'gpg'} || return $text{'yum_repo_egpg'}; $repo->{'raw'}->{'gpgcheck'} = 1; $repo->{'raw'}->{'gpgkey'} = 'file://'.$in->{'gpg'}; } return $repo; } # create_package_repo(&repo) # Creates a new repository from the given hash (returned by create_repo_parse) sub create_package_repo { my ($repo) = @_; my $file = "$yum_repos_dir/$repo->{'id'}.repo"; -r $file && return $text{'yum_repo_efile'}; &lock_file($file); my $lref = &read_file_lines($file); push(@$lref, "[$repo->{'id'}]"); foreach my $r (keys %{$repo->{'raw'}}) { push(@$lref, $r."=".$repo->{'raw'}->{$r}); } &flush_file_lines($file); &unlock_file($file); return undef; } # delete_package_repo(&repo) # Delete a repository from it's config file. Does not delete the file even if # empty, to prevent it from being re-created if it came from an RPM package. sub delete_package_repo { my ($repo) = @_; &lock_file($repo->{'file'}); my $lref = &read_file_lines($repo->{'file'}); splice(@$lref, $repo->{'line'}, $repo->{'eline'}-$repo->{'line'}+1); &flush_file_lines($repo->{'file'}); &unlock_file($repo->{'file'}); } # enable_package_repo(&repo, enable?) # Enable or disable a repository sub enable_package_repo { my ($repo, $enable) = @_; &lock_file($repo->{'file'}); my $lref = &read_file_lines($repo->{'file'}); my $e = "enabled=".($enable ? 1 : 0); if (defined($repo->{'raw'}->{'enabled'})) { # There's a line to update already for(my $i=$repo->{'line'}; $i<=$repo->{'eline'}; $i++) { if ($lref->[$i] =~ /^enabled=/) { $lref->[$i] = $e; last; } } } else { # Need to add a line splice(@$lref, $repo->{'eline'}, 0, $e); } &flush_file_lines($repo->{'file'}); &unlock_file($repo->{'file'}); } 1;