#!/usr/bin/env perl #-*- Mode: perl; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- # Boot manager configurator: grub-related routines. # # Copyright (C) 2001 Ximian, Inc. # # Authors: Arturo Espinosa # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU Library General Public License as published # by the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Library General Public License for more details. # # You should have received a copy of the GNU Library General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. $SCRIPTSDIR = "@scriptsdir@"; if ($SCRIPTSDIR =~ /^@scriptsdir[@]/) { $SCRIPTSDIR = "."; $DOTIN = ".in"; } require "$SCRIPTSDIR/general.pl$DOTIN"; require "$SCRIPTSDIR/util.pl$DOTIN"; require "$SCRIPTSDIR/file.pl$DOTIN"; require "$SCRIPTSDIR/xml.pl$DOTIN"; require "$SCRIPTSDIR/parse.pl$DOTIN"; require "$SCRIPTSDIR/replace.pl$DOTIN"; require "$SCRIPTSDIR/partition.pl$DOTIN"; my @grub_common_image_vars = qw (title); my @grub_image_vars = (@grub_common_image_vars, qw (root kernel )); my @grub_other_vars = (@grub_common_image_vars, qw (rootnoverify makeactive chainloader)); sub gst_boot_grub_verify { return "success"; } sub gst_boot_grub_get_config_file { return "/etc/grub.conf" if -f "/etc/grub.conf"; return "/boot/grub/menu.lst" if -f "/boot/grub/menu.lst"; return "/boot/grub/grub.conf" if -f "/boot/grub/grub.conf"; return undef; } # find out which is the partition where the file (image or initrd) is stored in, just to # put /file instead of /path-to-boot/file sub gst_boot_grub_set_file_path { my ($image, $root) = @_; my ($mtab, $dir); $mtab = "$gst_prefix/etc/mtab"; $mntopts = &gst_parse_split_first_array ($mtab, $root, "[ \t]+", "[ \t]+"); $dir = $$mntopts[0]; if ($dir ne "") { if ($dir ne "/") # if mount point is /, we don't need to remove it { $image =~ s/^$dir//; } } return $image; } sub gst_boot_grub_known_var { my $key = shift; my $list = shift; my $from_xml = shift; if (ref ($list) ne "ARRAY") { # TODO: Give warning; return 0; } $key = lc ($key); # Hard coded known variables which are not standard variables. return 0 if ($key eq "key" && $from_xml); # "key" is valid in xml only. return 0 if ($key eq "type" && $from_xml); # "key" is valid in xml only. return &gst_item_is_in_list ($key, @$list); } # Scans @buff until finds first line which looks like entry. # Returns line number or -1 if no entry found. sub gst_boot_grub_find_entry { my ($buff, $lineno) = @_; my $i; for (; $lineno <= $#$buff; $lineno++) { $i = $$buff[$lineno]; return $lineno if ($i =~ /^[ \t]*(title)[ \t]+\S+/); } # Not found. return -1; } sub gst_boot_grub_delete_entry { my ($buff, $lineno) = @_; my ($end); $end = &gst_boot_grub_find_entry ($buff, $lineno + 1); $end = scalar @$buff if ($end < 0); # We delete all entry lines for ($lineno; $lineno < $end; $lineno++) { # delete $$buff[$lineno]; $$buff[$lineno] = ""; } return $buff; } sub gst_boot_grub_add_entry { my ($entry, $buff, $lineno, $device_map) = @_; my ($end, $val, $value, $image, $initrd, $module); if (exists ($entry->{"image"})) { # It's a linux image $value = $entry->{"root"}; $value = &gst_boot_grub_linux2grub ($device_map, $value); $image = &gst_boot_grub_set_file_path ($entry->{"image"}, $entry->{"root"}); if (exists ($entry->{"initrd"}) && ($entry->{"initrd"} ne "")) { $initrd = &gst_boot_grub_set_file_path ($entry->{"initrd"}, $entry->{"root"}); } if (exists ($entry->{"module"}) && ($entry->{"module"} ne "")) { $module = &gst_boot_grub_set_file_path ($entry->{"module"}, $entry->{"root"}); } $val = "title " . $entry->{"label"} . "\n"; $val .= "\troot (". $value. ")\n"; $val .= "\tkernel " . $image . " " . $entry->{"append"} . "\n"; if ($initrd ne "") { $val .= "\tinitrd ". $initrd . "\n"; } if ($module ne "") { $val .= "\tmodule ". $module. "\n"; } } elsif (exists ($entry->{"other"})) { # It's another boot $value = $entry->{"other"}; $value = &gst_boot_grub_linux2grub ($device_map, $value); $val = "title " . $entry->{"label"} . "\n"; $val .= "\trootnoverify (" . $value . ")\n"; $val .= "\tmakeactive\n"; $val .= "\tchainloader +1\n"; } else { return; } if (exists ($entry->{"password"})) { $val .= "\tpassword " . $entry->{"password"} . "\n"; } $val .= "\n"; $$buff [$lineno - 1] = $$buff [$lineno - 1] . $val; } sub gst_boot_grub_entry_is_valid { my ($buff, $lineno) = @_; my ($end, $value, $line, $i); $value = 0; $end = &gst_boot_grub_find_entry ($buff, $lineno + 1); if ($end < 1) { # no more entries left, get the size of the array $end = @$buff; } for ($i = $lineno; $i < $end; $i++) { $line = $$buff[$i]; if (($line =~ /[\t ]*kernel.*/) || ($line =~ /[\t ]*rootnoverify.*/)) { $value = 1; } } return $value; } sub gst_boot_grub_entries_set { my ($file, $device_map, $mtab, $entries) = @_; my ($buff, $lineno, $entry, $entry_nr, $found); return if (scalar @$entries <=0); $buff = &gst_file_buffer_load ($file); &gst_file_buffer_join_lines ($buff); $lineno = &gst_boot_grub_find_entry ($buff, 0); # delete all known entries while ($lineno > 0) { if (&gst_boot_grub_entry_is_valid ($buff, $lineno)) { $buff = &gst_boot_grub_delete_entry ($buff, $lineno); } $lineno = &gst_boot_grub_find_entry ($buff, $lineno + 1); } # insert all entries just before all the unknown entries (if any) $lineno = &gst_boot_grub_find_entry ($buff, 0); if ($lineno < 0) { $lineno = @$buff; } foreach $entry (@$entries) { &gst_boot_grub_add_entry ($entry, $buff, $lineno, $device_map); } # save the buffer &gst_file_buffer_clean ($buff); return &gst_file_buffer_save ($buff, $file); } # Grub device notation converters. sub gst_boot_grub_linux2grub { my ($device_map, $dev) = @_; my ($fd, $line, $res, $path, $drive, $part); if ($dev =~ /(.*\/)?(fd[0-9]|hd|sd|scd)([a-z]*)([0-9]*)/) { $path = "/dev/"; $path = $1 if $1 ne undef; $drive = $2 . $3; $part = $4 - 1 if $4 ne undef; } else { &gst_report ("boot_grub_convert_failed", $dev); return undef; } $fd = &gst_file_open_read_from_names ($device_map); $res = undef; while ($line = <$fd>) { chomp $line; if ($line =~ /\(([^\)]+)\)[ \t]+$path$drive$/) { $res = $1; last; } } close $fd; if ($res eq undef) { # ok, the device doesn't exist in device.map, try to guess it &gst_report ("boot_grub_convert_failed", $dev); if ($drive =~ /(fd.|hd.)/) { $res = "hd0" if $drive eq "hda"; $res = "hd1" if $drive eq "hdb"; $res = "hd2" if $drive eq "hdc"; $res = "hd3" if $drive eq "hdd"; $res = $drive if $drive =~ /fd[0-9]/; } else { # no way, the entry will be broken return undef; } } $res .= ",$part" if $part ne undef; return $res; } sub gst_boot_grub_grub2linux { my ($device_map, $grubdev) = @_; ($drive, $part, @add) = split (',', $grubdev); $dev = &gst_parse_split_first_str ($device_map, "\\($drive\\)", "[ \t]+"); if ($dev eq undef) { # ok, the device doesn't exist in devices.map, we'll have to guess it if ($drive =~ /fd[0-9]/) { $dev = "/dev/" . $drive; } else { $dev = "/dev/hda" if $drive eq "hd0"; $dev = "/dev/hdb" if $drive eq "hd1"; $dev = "/dev/hdc" if $drive eq "hd2"; $dev = "/dev/hdd" if $drive eq "hd3"; } } $dev .= $part + 1 if $part ne undef; return $dev; } sub gst_boot_grub_clean_line { my ($line) = @_; chomp $line; $line =~ s/\#.*//; $line =~ s/^[ \t]+//; $line =~ s/[ \t]+$//; return $line; } sub gst_boot_grub_line_is_title { my ($line) = @_; if ($line =~ /^title[= \t][ \t]*(.*)$/) { return $1; } return undef; } sub gst_boot_grub_get_next_entry { my ($buff, $line_no) = @_; my ($line, $res); while ($$line_no < scalar (@$buff)) { $line = $$buff[$$line_no]; $line = &gst_boot_grub_clean_line ($line); $res = &gst_boot_grub_line_is_title ($line); last if $res ne undef; $$line_no ++; } return $res; } sub gst_boot_grub_search_entry { my ($buff, $line_no, $entry) = @_; my ($key, $title); $key = 0; while ($title = &gst_boot_grub_get_next_entry ($buff, $line_no)) { return $title if $key == $entry; $key ++; $$line_no ++; } return undef; } sub gst_boot_grub_get_entries { my ($grub_conf) = @_; my ($fd, $line, @res, $title, $i); my ($buff, $line_no); $grub_conf = [$grub_conf] if (ref $grub_conf eq undef); foreach $i (@$grub_conf) { $fd = &gst_file_open_read_from_names ($i); last if $fd; } return undef if !$fd; $buff = &gst_file_buffer_load_fd ($fd); $line_no = 0; while ($title = &gst_boot_grub_get_next_entry ($buff, \$line_no)) { push @res, $title; $line_no ++; } return @res; } sub gst_boot_grub_get_entries_fn { my ($fn) = @_; return &gst_boot_grub_get_entries ($$fn{"GRUB_CONF"}); } sub gst_boot_grub_insert_entry { my ($buff, $line_no, $title) = @_; splice (@$buff, $line_no, 0, "title $title\n"); } sub gst_boot_grub_remove_entry { my ($buff, $line_no) = @_; $$buff[$$line_no] = ""; $$line_no ++; while (&gst_boot_grub_line_is_title ($$buff[$$line_no]) eq undef) { $$buff[$$line_no] = "" if (! ($$buff[$$line_no] =~ /^\#/)); $$line_no ++; } } # @$entries must be sorted. sub gst_boot_grub_remove_entries { my ($fn, $entries) = @_; my ($grub_conf, $buff, $line_no); my ($find, $curr); return 0 if scalar (@$entries) == 0; $grub_conf = $$fn{"GRUB_CONF"}; $buff = &gst_file_buffer_load ($grub_conf); $buff = [] if $buff eq undef; $line_no = 0; $curr = 0; $find = shift (@$entries); while (&gst_boot_grub_get_next_entry ($buff, \$line_no)) { if ($find == $curr) { &gst_boot_grub_remove_entry ($buff, \$line_no); $find = shift (@$entries); last if $find eq undef; } else { $line_no ++; } $curr ++; } return &gst_file_buffer_save ($buff, $grub_conf); } sub gst_boot_grub_search_cmd { my ($buff, $line_no, $cmd) = @_; my ($line); while ($$line_no < scalar (@$buff)) { $line = $$buff[$$line_no]; $line = &gst_boot_grub_clean_line ($line); if ($line eq "") { $$line_no ++; next; } return 0 if &gst_boot_grub_line_is_title ($line) ne undef; return 1 if ($line =~ /^$cmd[= \t]/); $$line_no ++; } return 0; } sub gst_boot_grub_parse_cmd { my ($grub_conf, $cmd, $entry) = @_; my ($line, $buff, $line_no); $buff = &gst_file_buffer_load ($grub_conf); return undef if $buff eq undef; $line_no = 0; if ($entry ne undef) { return undef if &gst_boot_grub_search_entry ($buff, \$line_no, $entry) eq undef; $line_no ++; } if (&gst_boot_grub_search_cmd ($buff, \$line_no, $cmd)) { $line = &gst_boot_grub_clean_line ($$buff[$line_no]); if ($line =~ /^$cmd[= \t][ \t]*(.*)/) { return 1 if $1 eq ""; return $1; } } return undef; } sub gst_boot_grub_insert_cmd { my ($buff, $line_no, $cmd, $value) = @_; splice (@$buff, $line_no, 0, "$cmd $value\n") unless $value eq undef; } sub gst_boot_grub_parse_file_cmd { my ($grub_conf, $cmd, $device_map, $mtab, $entry) = @_; my ($value, $mtopts, $grubdev, $file, $dev, $root); $value = &gst_boot_grub_parse_cmd ($grub_conf, $cmd, $entry); return undef if $value eq undef; $value =~ /^\(([^\)]+)\)(.*)/; $grubdev = $1; $file = $2; if ($grubdev eq undef) { $file = $value; $root = &gst_boot_grub_parse_cmd ($grub_conf, "root", $entry); $root =~ /^\(([^\)]+)\)/; $grubdev = $1; } $file =~ s/[ \t].*//; $dev = &gst_boot_grub_grub2linux ($device_map, $grubdev); $mntopts = &gst_parse_split_first_array ($mtab, $dev, "[ \t]+", "[ \t]+"); $res = $$mntopts[0] . "/$file"; $res =~ s/\/+/\//g; return $res; } sub gst_boot_grub_parse_pixmap { my ($grub_conf, $device_map, $mtab) = @_; return &gst_boot_grub_parse_file_cmd ($grub_conf, "splashimage", $device_map, $mtab); } sub gst_boot_grub_parse_image { my ($grub_conf, $device_map, $mtab, $entry) = @_; my ($image); $image = &gst_boot_grub_parse_file_cmd ($grub_conf, "kernel", $device_map, $mtab, $entry); return $image; } sub gst_boot_grub_parse_other { my ($grub_conf, $device_map, $mtab, $entry) = @_; my ($value, $grubdev, $dev, $root); $value = &gst_boot_grub_parse_cmd ($grub_conf, "rootnoverify", $entry); return undef if $value eq undef; $value =~ /^\(([^\)]+)\)/; $grubdev = $1; if ($grubdev eq undef) { $root = &gst_boot_grub_parse_cmd ($grub_conf, "root", $entry); $root =~ /^\(([^\)]+)\)/; $grubdev = $1; } $dev = &gst_boot_grub_grub2linux ($device_map, $grubdev); return $dev; } sub gst_boot_grub_parse_timeout { my ($grub_conf) = @_; my ($timeout); $timeout = &gst_boot_grub_parse_cmd ($grub_conf, "timeout"); return undef if $timeout eq undef; return $timeout * 10; } sub gst_boot_grub_parse_prompt { my ($grub_conf) = @_; return (&gst_boot_grub_parse_cmd ($grub_conf, "hiddenmenu"))? 0: 1; } sub gst_boot_grub_parse_default { my ($grub_conf) = @_; my ($default, @entries); @entries = &gst_boot_grub_get_entries ($grub_conf); return undef if (scalar @entries) == 0; $default = &gst_boot_grub_parse_cmd ($grub_conf, "default"); $default = 0 if $default eq undef; return $entries[$default]; } sub gst_boot_grub_parse_root { my ($grub_conf, $device_map, $entry) = @_; my ($value, $dev); # we check if there is the 'root' label, then extract root device from it $value = &gst_boot_grub_parse_cmd ($grub_conf, "root", $entry); if ($value ne undef) { $value =~ s/[()]//g; return &gst_boot_grub_grub2linux ($device_map, $value); } return undef; } sub gst_boot_grub_parse_append { my ($grub_conf, $entry) = @_; my ($cmd); $cmd = &gst_boot_grub_parse_cmd ($grub_conf, "kernel", $entry); return undef if $cmd eq undef; $cmd =~ s/^[^ \t]+[ \t]?//; return $cmd; } sub gst_boot_grub_parse_type { my ($grub_conf, $entry, $partition, $rootdev) = @_; my ($line, $res, $buff, $line_no); $buff = &gst_file_buffer_load ($grub_conf); return undef if $buff eq undef; $line_no = 0; return undef if &gst_boot_grub_search_entry ($buff, \$line_no, $entry) eq undef; $line_no ++; while ($line_no < scalar (@$buff)) { $line = $$buff[$line_no]; last if &gst_boot_grub_line_is_title ($line) ne undef; if ($line =~ /^\#[ \t]*GstEntryType[ \t]+(.*)$/) { $res = $1; last; } $line_no ++; } if ($res eq undef) { $rootdev =~ s/.*\///; $res = $ {$$partition{$rootdev}}{"typestr"} if exists $$partition{$rootdev}; } return $res; } sub gst_boot_grub_parse_initrd { my ($grub_conf, $device_map, $mtab, $entry) = @_; my ($initrd); $initrd = &gst_boot_grub_parse_file_cmd ($grub_conf, "initrd", $device_map, $mtab, $entry); return $initrd; } sub gst_boot_grub_parse_module { my ($grub_conf, $device_map, $mtab, $entry) = @_; my ($module); $module = &gst_boot_grub_parse_file_cmd ($grub_conf, "module", $device_map, $mtab, $entry); return $module; } sub gst_boot_grub_parse_password { my ($grub_conf, $entry) = @_; return &gst_boot_grub_parse_cmd ($grub_conf, "password", $entry); } # Watch it: this assumes that the entry exists. sub gst_boot_grub_replace_cmd_in_buff { my ($buff, $cmd, $value, $entry) = @_; my ($line, $line_no); my ($pre, $post); $line_no = 0; if ($entry ne undef) { # Watch it: this assumes that the entry exists. &gst_boot_grub_search_entry ($buff, \$line_no, $entry); $line_no ++; } if (&gst_boot_grub_search_cmd ($buff, \$line_no, $cmd)) { if ($value eq undef) { $$buff[$line_no] = ""; } else { $line = $$buff[$line_no]; $line =~ s/^([ \t]+)//; $pre = $1; $line =~ s/([ \t]+)$//; $post = $1; $line =~ s/^$cmd([= \t][ \t]*).*/$pre$cmd\1$value$post/; $$buff[$line_no] = $line; } } else { $cmd = "\t$cmd" if $entry ne undef; &gst_boot_grub_insert_cmd ($buff, $line_no, $cmd, $value) if ($value ne undef); } } sub gst_boot_grub_replace_cmd { my ($grub_conf, $cmd, $value, $entry) = @_; $buff = &gst_file_buffer_load ($grub_conf); $buff = [] if $buff eq undef; &gst_boot_grub_replace_cmd_in_buff ($buff, $cmd, $value, $entry); return &gst_file_buffer_save ($buff, $grub_conf); } sub gst_boot_grub_get_stat { my ($file, $field) = @_; my (@tmp); @tmp = stat ($file); return $tmp[$field]; } sub gst_boot_grub_find_device { my ($file) = @_; my ($filedev, $dev, $res); local *DIR; $res = undef; $filedev = &gst_boot_grub_get_stat ($file, 0); opendir (DIR, "/dev"); while ($dev = readdir (DIR)) { $dev = "/dev/$dev"; if ((-b $dev) && (&gst_boot_grub_get_stat ($dev, 6) eq $filedev)) { $res = $dev; last; } } closedir (DIR); return $res; } sub gst_boot_grub_replace_file_cmd { my ($grub_conf, $cmd, $device_map, $mtab, $value, $entry) = @_; my ($grubdev, $file, $dev, $root); my ($mntopts, $res, $dev, $dir, $oldval); $file = $value; $dev = &gst_boot_grub_find_device ($file); $mntopts = &gst_parse_split_first_array ($mtab, $dev, "[ \t]+", "[ \t]+"); $dir = $$mntopts[0]; $file =~ s/^$dir//; $file =~ s/\/+/\//g; $grubdev = &gst_boot_grub_linux2grub ($device_map, $dev); $res = "($grubdev)$file"; $oldval = &gst_boot_grub_parse_cmd ($grub_conf, $cmd, $entry); $oldval =~ s/^[^ \t]+//; $res .= $oldval; return &gst_boot_grub_replace_cmd ($grub_conf, $cmd, $res, $entry); } sub gst_boot_grub_replace_pixmap { my ($grub_conf, $device_map, $mtab, $value) = @_; return &gst_boot_grub_replace_file_cmd ($grub_conf, "splashimage", $device_map, $mtab, $value); } sub gst_boot_grub_replace_image { my ($grub_conf, $device_map, $mtab, $entry, $value) = @_; return &gst_boot_grub_replace_cmd ($grub_conf, "kernel", $value, $entry); } sub gst_boot_grub_replace_other { my ($grub_conf, $device_map, $entry, $value) = @_; my ($grubdev); $grubdev = &gst_boot_grub_linux2grub ($device_map, $value); &gst_boot_grub_replace_cmd ($grub_conf, "rootnoverify", "($grubdev)", $entry); # &gst_boot_grub_replace_cmd ($grub_conf, "makeactive", " ", $entry); return &gst_boot_grub_replace_cmd ($grub_conf, "chainloader", "+1", $entry); } sub gst_boot_grub_replace_timeout { my ($grub_conf, $value) = @_; my ($timeout); if ($value == -1 || $value eq undef) { return &gst_boot_grub_replace_cmd ($grub_conf, "timeout", undef); } $timeout = int ($value / 10); return &gst_boot_grub_replace_cmd ($grub_conf, "timeout", $timeout); } sub gst_boot_grub_replace_prompt { my ($grub_conf, $value) = @_; return &gst_boot_grub_replace_cmd ($grub_conf, "hiddenmenu", ($value)? undef: " "); } sub gst_boot_grub_replace_default { my ($grub_conf, $entries, $value) = @_; my ($default); return -1 if (scalar @$entries) == 0; for ($default = 0; $default < scalar (@$entries); $default++) { last if $value eq $$entries[$default]{"label"}; } return -1 if $default == scalar (@$entries); # didn't find it. return &gst_boot_grub_replace_cmd ($grub_conf, "default", $default); } sub gst_boot_grub_replace_root { my ($grub_conf, $device_map, $entry, $type, $value) = @_; my ($root, $buff, $line_no, $entry_line, $currval, $root_dev); $root_dev = "(". &gst_boot_grub_linux2grub ($device_map, $value) . ")"; return &gst_boot_grub_replace_cmd ($grub_conf, "root", $root_dev, $entry); } sub gst_boot_grub_replace_append { my ($grub_conf, $device_map, $entry, $value) = @_; my ($kernel, $append, $currval, $root); $kernel = &gst_boot_grub_parse_cmd ($grub_conf, "kernel", $entry); $currval = &gst_boot_grub_parse_cmd ($grub_conf, "root", $entry); $currval =~ s/[()]//g; $root = &gst_boot_grub_grub2linux ($device_map, $currval); $kernel .= " root=$root $value"; return &gst_boot_grub_replace_cmd ($grub_conf, "kernel", $kernel, $entry); } sub gst_boot_grub_replace_type { my ($grub_conf, $entry, $value) = @_; my ($buff, $line_no, $line, $res); $buff = &gst_file_buffer_load ($grub_conf); $buff = [] if $buff eq undef; $line_no = 0; return -1 if &gst_boot_grub_search_entry ($buff, \$line_no, $entry) eq undef; $line_no ++; while ($line_no < scalar (@$buff)) { $line = $$buff[$line_no]; last if &gst_boot_grub_line_is_title ($line) ne undef; if ($line =~ /^\#[ \t]*GstEntryType[ \t]+(.*)$/) { $res = $1; last; } $line_no ++; } if ($res eq undef) { &gst_boot_grub_insert_cmd ($buff, $line_no, "#\tGstEntryType", $value); } else { $$buff[$line_no] =~ s/^(\#[ \t]*GstEntryType[ \t]+).*$/\1$value/; } return &gst_file_buffer_save ($buff, $grub_conf); } sub gst_boot_grub_replace_label { my ($grub_conf, $entry, $value) = @_; my ($buff, $line_no, $title); $buff = &gst_file_buffer_load ($grub_conf); $buff = [] if $buff eq undef; $line_no = 0; $title = &gst_boot_grub_search_entry ($buff, \$line_no, $entry); if ($title eq undef) { $line_no ++; &gst_boot_grub_insert_entry ($buff, \$line_no, $value); } else { $$buff[$line_no] =~ s/^title([= \t][ \t]*).*/title\1$value/; } return &gst_file_buffer_save ($buff, $grub_conf); } 1;