User:Feystorm

From Gentoo Wiki
Jump to: navigation, search
Feystorm
Patrick Hemmer
Contact info
qubit (IRC)
Userboxes
Stack Exchange Patrick
Gentoo user since 2002
en-5 This user is able to contribute with a professional level of English.

Heres some notes I'm keeping and storing here in the hope that google might turn up results on this page and make some people's lives easier.

garbled or distorted vga console with xorg

So I've ran into this problem on several of my machines (coincidentally all nvidia). Basically the problem is when using vga console mode and switching to a terminal after launching xorg.
The best way to describe it is the console will be widely spaced out vertically with the normal 80x25 height font in the background, and the vga mode resolution font you selected on top of it.

I eventually gave up on the issue, but here is what I found. The only way to get the console clean again is to run a util like 'resizecons' (from the kbd package), or SVGATextMode (just a theory, didnt try it, but supposedly does the same thing). However this just makes the console readable again. For some reason it doesnt get the number of lines restored properly, and a couple lines will still go off the bottom of the screen (though it is less than when it is in the distorted mode).
My box is x86_64, and for some reason the kbd package doesnt install resizecons on this arch. So I had to manually download the package and compile that binary. The magic that seems to somewhat fix this is that it uses the ioctl VT_RESIZE.

But as I mentioned, I eventually gave up on trying to solve this and went over to the dark side (framebuffer).

vesafb vs. uvesafb

So I was tring to determine which of the two was the better choice, and the primary concern was speed. I couldnt find anywhere that documented which method of framebuffer was faster, so I set out to conduct an experiment and see.
To cut to the chase, uvesafb is *WAY* faster than vesafb.

In my experiment I ran `time` on catting a 4k file. With vesafb, this took on average 4 seconds (default settings). With uvesafb, the same operation took on average about 0.2 seconds. So in my setup, uvesafb is approximately 20 times faster than vesafb, both with default settings.


Thunar and cifs/smb

Getting thunar to access cifs/smb natively is actually quite easy. Unfortunately there is diddly squat for documentation on how to do it. All you need is to emerge the gnome-base/gvfs package with the samba use flag enabled. After that you'll have to log out and back in for dbus perms to take effect (so dbus needs to be running), but after that just enter 'smb://host/share' in the Thunar path bar and it'll access whatever remote share you want without actually having to mount it manually. For shares that require a authentication, its 'smb://user@host/share'.


On-demand ssh tunnel

This is how to setup ssh tunnels that start up on demand when connected to.

  • Setup ssh public key authentication to the host you wish to tunnel through
  • Install the xinetd package.
  • Create a file in /etc/xinetd.d similar to the following
service unlisted
{
	type		= UNLISTED
	socket_type	= stream
	protocol	= tcp
	port		= 1234
	wait		= no
	user		= someuser
	server		= /usr/bin/ssh
	server_args	= -y -C proxyhost.example.com -W target.example.com:2345
	disable		= no
	only_from	= 127.0.0.1
}
  • Start xinetd


It is important to set the 'user' attribute to the user who's public key you want to use. It will also use the known_hosts file for this user, so make sure to log into the box at least once an accept the fingerprint.

In the above configuration, any connections to 127.0.0.1:1234 will be tunneled through proxyhost.example.com to target.example.com:2345. The tunnel will shut down as soon as the connection is closed. You can adjust the ssh parameters like compression as desired.


HP EliteBook 8540w

ACPI

Control of the backlight and most of the rest of the ACPI system requires you to enable 2 critical options that are a PITA to find; CONFIG_ACPI_WMI and CONFIG_HP_WMI. The backlight itself requires CONFIG_ACPI_VIDEO.
Once all these options are enabled (and loaded), you can control the backlight through /sys/class/backlight/acpi_video0/brightness.
Note, I also found that these modules have to be loaded before nvidia if you are using it. I dont know why but if you load them after, /sys/class/backlight never shows up.

Sound

Uses the Sigmatel IDT 92HD75B3X5 chipset.

Requires the following enabled kernel options

CONFIG_SND_HDA_INTEL
CONFIG_SND_HDA_CODEC_SIGMATEL

For the headphone jack to work, you then need to add

options snd-hda-intel model=hp-dv5

to /etc/modprobe.d/alsa.conf (or any file in that dir).


wine & mono

If you have the unfortunate need to run windows apps requiring mono, theres a simple fix you have to do if you dont want to install mono through portage, but install it just in the wine environment.


  • use winetricks to install mono28 (mono210 hangs during install when I tried)
  • cd to ~/.wine/drive_c/Program Files/Mono-2.8.2/bin
  • symlink mono-2.0.dll to mono.dll


Without the symlink, attempting to run a mono app through wine will just result in

wine: Install the Windows version of Mono to run .NET executables


boinc gui

When attempting to use the boinc GUI interface, it was refusing to connect to the local boinc client, even with 'ALLOW_REMOTE_RPC="yes"' in /etc/conf.d/boinc. In simple view, it was presenting the error "unable to connect to the core client". To solve this issue, switch to advanced view, go to advanced->select computer, put in '127.0.0.1' (this was the critical part for me as 'localhost' wouldnt work' but '127.0.0.1' would), and for password put the contents of /var/lib/boinc/gui_rpc_auth.cfg

If you wish to change the authentication password, just shut down boinc, edit /var/lib/boinc/gui_rpc_auth.cfg and start it back up.


FreeRADIUS wireless authentication via LDAP bind

I have a wireless router running DD-WRT and I wanted to be able to authenticate users against an LDAP database via LDAP bind (and not have to give the password, encrypted or unencrypted, to freeradius). I couldnt find any documentation on how to do this anywhere (at least not any documentation that worked). This is how I got it to work.

Server

  • FreeRADIUS version - 2.1.12
  • Router IP - 192.168.0.1
  • Shared secret - secret1234


Configure modules/ldap ldap{} section with your LDAP server configuration. This is fairly straightforward, but is dependent upon your LDAP configuration. I did not set the identity or password attributes as anonymous bind is able to look up users (though the userPassword attribute is restricted). I also set edir_account_policy_check to no.


Configure eap.conf. In the eap { tls {} } section, configure your certificates. For the dh_file, you can create it with `openssl dhparam -out server.dh 1024`. For the random_file parameter, I specified /dev/urandom


Configure clients.conf. Add a section for your router similar to the following

client router {
   ipaddr = 192.168.0.1
   secret = secret1234
   require_message_authenticator = no
   nastype = other
}


Configure sites-enabled/inner-tunnel.

  • Go to the authorize section and uncomment "ldap"
  • Go to the authenticate section and change the contents of "Auth-Type PAP" from "pap" to "ldap"


Router

  • Security mode - WPA2 Enterprise
  • Radius Auth Server Address - 192.168.0.2
  • Radius Auth Server Port - 1812
  • Radius Auth Shared Secret - secret1234


Client

I use NetworkManager for wireless management, and configuring it is pretty simple.

Under the Wireless Security tab

  • Security - WPA & WPA2 Enterprise
  • Authentication - Protected EAP (PEAP)
  • Anonymous identity - *blank*
  • CA certificate - (None)
  • PEAP version - Automatic
  • Inner authentication - GTC
  • Username - *LDAP user, just the name not DN*
  • Password - *LDAP pass*


KVM & bridging

While attempting to setup a VM through virt-manager, I was unable to get traffic from the VM to cross the bridge. Doing a tcpdump on the VM's interface (vnet0) showed DHCP requests, but a tcpdump on the bridge interface (virbr0) would not show them. Thus dnsmasq was unable to see the queries, and respond to them. Turns out the issue is that the VM's mac address must start with 52. This makes absolutely no sense, but thats just what I observed. So a valid mac address would be 52:00:00:00:00:00.

EncFS & autofs/fstab

For some stupid reason encfs goes against all standard practices for taking arguments from fuse. This makes it impossible to mount from autofs or fstab (and not have it prompt for a password).

So I wrote a custom wrapper script that makes encfs behave as it should so that it can take arguments when called by fuse, and not by the user.


Create /usr/bin/EncFS with the following contents and chmod it to 755

#!/usr/bin/perl
use warnings;
use strict;
use Getopt::Long qw(:config bundling no_auto_abbrev);
use Text::ParseWords;

my @options = ();
GetOptions(
	'o=s@' => \@options,
);
my @pass_options;
my @use_options;
for my $option (parse_line(',', 1, join(',', @options))) {
	my ($key, $value) = split(/=/, $option, 2);
	if(( $key eq "verbose" || $key eq "anykey" || $key eq "forcedecode" || $key eq "public" || $key eq "reverse" || $key eq "stdinpass") && !defined($value)) {
		push(@use_options, "--$key");
	} elsif($key eq "idle" || $key eq "extpass") {
		push(@use_options, "--$key=$value");
	} else {
		push(@pass_options, $option);
	}
}
exec("encfs", @use_options, $ARGV[0], $ARGV[1], '--', '-o', join(',', @pass_options));
exit(1);

Then for the fuse filesystem type, use "EncFS" (note the case difference, this needs to match the script filename).

Example: mount -t fuse -o extpass="cat /path/to/password" EncFS#/mount/source /mount/dest


Note: I advised placing the script in /usr/bin as opposed to /usr/local/bin because the script needs to be in $PATH, and when autofs is started its PATH does not contain /usr/local/bin. You can put it in /usr/local/bin if you dont use autofs, or if you want to modify the path used by autofs.


PulseAudio per-application volume control

PulseAudio supports per-application volume control, but by default this doesnt do much as you can only control these volumes from the pulseaudio volume control utility. Meaning that in an application like Audacious, when the output device is set to PulseAudio, and the volume control is set to hardware, it will adjust the master volume control, not the per-application volume control.

To fix this behavior, set the following in /etc/pulse/daemon.conf

flat-volumes = no

Now whenever Audacious goes to adjust the volume, it will adjust the audacious only volume and thus you wont have multiple applications fighting over the master volume control.

Log database

I ended up switching all my logging to a database instead of files. I dont mean a client/server database like MySQL or Postgres, but an embedded database; SQLite.

This approach has 2 main benefits

  • No more log rotating. Instead of having to rotate logs at fixed intervals, a simple SQL query can delete the logs you want.
  • Easy/fast searching. Instead of having to look through multiple rotated or priority-filtered logs, having to read a huge log to look for specific time frame, filter out debugging messages, etc, you can search for exactly what you want.

Setup

Required use flags

  • app-admin/syslog-ng sql
  • dev-db/libdbi-drivers sqlite3
root #emerge --ask app-admin/syslog-ng
This template uses deprecated syntax.
Important
There is a bug in all versions of libdbi-drivers that prevents syslog-ng from working with sqlite3 databases. See bug #399739. The patch in this bug report can be applied to fix the issue.
Important
There is another (apparent) bug in either syslog-ng (3.3.4) or the libdbi code. I've managed to cause syslog-ng to completely hang several times now. Wont respond to anything but a `kill -9`. Going to get a backtrace of the issue and see whats going on.
Note
The perl packages are for the view/query utility
File/etc/syslog-ng/syslog-ng.conf

@version: 3.2
options {
	keep_hostname(yes);
	chain_hostnames(no);
	normalize_hostnames(yes);
	use_dns(no);
	use_fqdn(yes);
	stats_freq(0);
	mark_freq(0);
	time_sleep(10);
	owner("root");
	group("sys");
};

source s_local {
 	system();
	internal();
};

destination d_sqlite {
	sql(
		type(sqlite)
		database("/var/log/logs.sql3")
		table("logs")
		columns("time", "time_r", "host", "facility", "priority", "program", "pid", "tag", "message")
		values("$S_UNIXTIME", "$R_UNIXTIME", "$FULLHOST", "$FACILITY_NUM", "$LEVEL_NUM", "$PROGRAM", "$PID", "$DBTAG", "$MSG")
		null("")
		flags(explicit-commits)
		flush_lines(10)
		flush_timeout(200)
	);
};


filter f_sqlite_iptables {
	program('^kernel')
	and message('^\s*\[\s*[\d\.]+\] iptables/' type(pcre))
};
filter f_sqlite_iptables_discard {
	# ignore iptables broadcast messages
	filter(f_sqlite_iptables)
	and message('MAC=ff:ff:ff:ff:ff:ff')
	;
};


filter f_sqlite_security {
	(
		program('^kernel')
		and (
			message('^\s*\[\s*[\d\.]+\] grsec: ' type(pcre))
			or message('^\s*\[\s*[\d\.]+\] type=\d+ audit\(' type(pcre))
		)
	) or (
		program('^setfiles')
	)
};


filter f_sqlite_discard {
	not (
		filter(f_sqlite_iptables_discard)
	);
};


rewrite r_sqlite_tag {
	set('iptables', value('DBTAG') condition(filter(f_sqlite_iptables)));
	set('security', value('DBTAG') condition(filter(f_sqlite_security)));
};


log {
	source(s_local);
	filter(f_sqlite_discard);
	rewrite(r_sqlite_tag);
	destination(d_sqlite);
};

This is a deprecated template. Help us update this template!

Note
This config is just a short example similar to the one I use. Edit to suit your needs.
root #
sqlite3 /var/log/logs.sql3 <<<'EOI'
CREATE TABLE logs (id integer primary key autoincrement, time unsigned integer not null, time_r unsigned integer not null, host text not null, facility unsigned integer not null, priority unsigned integer not null, program text not null, pid unsigned integer, tag text, message text not null);
CREATE INDEX host on logs (host, time);
CREATE INDEX program on logs (program, time);
EOI

httpd

httpd (apache2 as gentoo likes to call it) writes out directly to files by default. This behavior can be changed but requires a little extra work. httpd supports writing the error log straight to syslog, but for some reason not the info log. You could write out to a file and then have syslog-ng read that file in, but I dont like that solution as now you have to start cleaning up files. And writing to a fifo (named pipe) you run the risk of filling the pipe's buffer if syslog-ng dies.

File/etc/apache2/httpd.conf

ErrorLog syslog:daemon
CustomLog "|/usr/local/bin/pipelog -f daemon -p info" common

This is a deprecated template. Help us update this template!

File/usr/local/bin/pipelog

#!/usr/bin/perl
#
# Copyright (C) 2012 Patrick A. Hemmer
# <copyright@stormcloud9.net>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

use warnings;
use strict;
use Sys::Syslog;
use Getopt::Long;
Getopt::Long::Configure('bundling', 'no_auto_abbrev');

$0 =~ m#[^/]+$#;
my $basename = $&;

my $ident;
my $logopt;
my $facility = 'user';
my $priority; # we dont set a default here so that we can tell if the user set an explicity priority or pull it from $facility if possible
my $prefix = '';
my $tee = 0;
my $showhelp = 0;
my $nopid = 0;

GetOptions(
	'ident|tag|i|t=s' => \$ident,
	'facility|f=s' => \$facility,
	'priority|p=s' => \$priority,
	'prefix|x=s' => \$prefix,
	'nopid|n' => \$nopid,
	'tee' => \$tee,
	'help|h' => \$showhelp,
);

if($showhelp) {
	print("Usage: $basename [OPTIONS]
  -i, --ident                Ident to send to syslog
  -t, --tag                  Same as --ident
  -f, --facility[=user]      Syslog facility to use
  -p, --priority[=info]      Syslog priority
  -x, --prefix               Prefix messages with string
  -n, --nopid                Dont send parent pid to syslog
  --tee                      Send output to stdout also
  -h, --help                 This
");
	exit(0);
}


if(!defined($ident)) {
	my $ppid = getppid();
	my $cmdline;
	open($cmdline, '<', "/proc/$ppid/cmdline");
	local $/ = chr(0);
	$ident = readline($cmdline);
	chomp($ident);
	close($cmdline);
	if(defined($ident) && length($ident)) {
		$ident =~ s#^.+/(?=.)##;
	} else {
		$ident = $basename;
	}
}
if(!$nopid) {
	$ident .= "[" . getppid() . "]";
}

if($facility =~ /^([^.])\.(.+)$/) {
	$facility = $1;
	if(!defined($priority)) {
		$priority = $2;
	}
} elsif(!defined($priority)) {
	$priority = 'info';
}

openlog($ident, $logopt, $facility);

if(!$tee) {
	close(STDOUT);
}
close(STDERR);
while(my $line = readline(STDIN)) {
	if($tee) {
		print($line);
	}
	chomp($line);
	syslog($priority, $line);
}

This is a deprecated template. Help us update this template!

root #chmod a+x /usr/local/bin/pipelog

Using

To read the logs I have written a perl script to query the database. For the source code, see below.

Note
I have only put this script through basic usage. There might be bugs.

Example commands:

Show all log entries from postfix as they come in

user $logs -f -p 'postfix/*'

Show all emergency and alert messages that have occurred since 24 hours ago.

user $logs -P '<=alert' -t '>-24hr'

Delete all debugging messages older than 1 week ago

root #logs -P debug -t '<-1week' --delete

File/usr/local/bin/logs

#!/usr/bin/perl
#
# Copyright (C) 2012 Patrick A. Hemmer
# <copyright@stormcloud9.net>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

use warnings;
use strict;
use DBI;
use Sys::Syslog qw(:macros);
use Linux::Inotify2;
use Getopt::Long;
use Date::Manip;
use POSIX qw(strftime);

my $dbpath = '/var/log/logs.sql3';

sub xlate { return(Sys::Syslog::xlate(@_)); } # Sys::Syslog doesnt export this, but I want to use it anyway
Getopt::Long::Configure('bundling', 'no_auto_abbrev', 'pass_through'); # pass_through is so we can handle the error
use constant {
	'EQUALITY_NUMERIC' => 1,
	'EQUALITY_STRING' => 2,
};
$0 =~ m#([^/]+)$#;
my $basename = $1;
my @priorities = ('emerg', 'alert', 'crit', 'err', 'warning', 'notice', 'info', 'debug');
my @facilities = ('kern', 'user', 'mail', 'daemon', 'auth', 'syslog', 'lpr', 'news', 'uucp', 'cron', 'authpriv', 'ftp', 'ntp', 'audit', 'alert', 'cron', 'local0', 'local1', 'local2', 'local3', 'local4', 'local5', 'local6', 'local7');

my %opts = (
	'follow' => 0,
	'head' => 0,
	'lines' => undef,
	#'format' => exists($ENV{'LOGS_FORMAT'}) ? $ENV{'LOGS_FORMAT'} : '%t %p: %m',
	'format' => exists($ENV{'LOGS_FORMAT'}) ? $ENV{'LOGS_FORMAT'} : '%{%Y-%m-%d %H:%M:%S} %r: %m',
	# %t = Time
	# %f = Facility
	# %F = Facility (numeric)
	# %p = Priority
	# %P = Priority (numeric)
	# %h = host
	# %r = pRogram[pid]
	# %R = pRogram
	# %i = pId
	# %T = Tag
	# %m = Message

	'delete' => 0,
	'vacuum' => 0,

	'time' => undef,
	'host' => undef,
	'facility' => undef,
	'priority' => undef,
	'program' => undef,
	'pid' => undef,
	'tag' => undef,

	'debug' => 0,
	'help' => 0,
);
GetOptions(
	'lines|n=i' => \$opts{'lines'},
	'follow|f' => \$opts{'follow'},
	'head|H' => \$opts{'head'},
	'format=s' => \$opts{'format'},

	'delete' => \$opts{'delete'},
	'vacuum' => \$opts{'vacuum'},
	
	'time|date|t=s' => \$opts{'time'},
	'host|h=s' => \$opts{'time'},
	'facility|facil|F=s' => \$opts{'facility'},
	'priority|prio|P=s' => \$opts{'priority'},
	'program|prog|p=s' => \$opts{'program'},
	'pid|i=i' => \$opts{'pid'},
	'tag|T=s' => \$opts{'tag'},

	'debug' => \$opts{'debug'},
	'help' => \$opts{'help'},
);

if(scalar(@ARGV)) {
	print(STDERR "Unknown option $ARGV[0]\n");
	$opts{'help'} = 2;
}
if($opts{'help'}) {
	print <<EOI;
Usage: $basename [OPTIONS]
  -n, --lines=N              Show N messages (default 10)
  -f, --follow               Show messages as they are added
  -H, --head                 Show the first N lines instead of the last
      --format               Format to output messages in

  --delete                   Delete all matching entries instead of displaying
  --vacuum                   Vacuum the database after deletion (reclaims
                             free space).

  -t, --time=TIME          # Show only lines matching time TIME
      --date=TIME          # Same as --time
  -h, --host=HOST          * Show only lines matching host HOST
  -F, --facility=FACILITY  * Show only lines matching facility FACILITY
      --facil=FACILITY     # Same as --facility
  -P, --priority=PRIORITY  # Show only lines matching priority PRIORITY
      --prio=PRIORITY      # Same as --priority
  -p, --program=PROGRAM    * Show only lines matching program PROGRAM
      --prog=PROGRAM       * Same as --program
  -i, --pid=PID            # Show only lines matching pid PID
  -T, --tag=TAG            * Show only lines matching tag TAG

      --help                 This


 Filters:
  Above filters denoted with '*' support matching based on numeric operators.
   eg. `$basename --priority '<=notice'`
  Above filters denoted with '#' support matching based on globbing.
   eg. `$basename --program 'postfix/*'`

 Priorities:         Facilities:
  0 = emerg           0  = kern      8  = uucp      16 = local0
  1 = alert           1  = user      9  = cron      17 = local1
  2 = crit            2  = mail      10 = authpriv  18 = local2
  3 = err             3  = daemon    11 = ftp       19 = local3
  4 = warning         4  = auth      12 = (ntp)     20 = local4
  5 = notice          5  = syslog    13 = (audit)   31 = local5
  6 = info            6  = lpr       14 = (alert)   32 = local6
  7 = debug           7  = news      15 = (cron)    33 = local7
                      Facilities in () are not official

 Format options:
  %{} = strftime (strftime parameters are enclosed in the braces)
  %t  = Time                 %r = pRogram[pid]
  %f  = Facility             %R = pRogram
  %F  = Facility (numeric)   %i = pId
  %p  = Priority             %T = Tag
  %P  = Priority (numeric)   %m = Message
  %h  = Host
EOI
	if($opts{'help'} > 1) {
		exit(1);
	}
	exit(0);
}
if($opts{'delete'} && $opts{'follow'}) {
	print(STDERR "Cant use --follow with --delete\n");
	exit(1);
}
if($opts{'head'} && $opts{'follow'}) {
	print(STDERR "Cant use --follow with --head\n");
	exit(1);
}
if($opts{'delete'} && defined($opts{'lines'})) {
	print(STDERR "Can't use --lines with --delete\n");
	exit(1);
}

if($opts{'delete'}) {
	$opts{'head'} = 1;
}
if(!$opts{'delete'} && !defined($opts{'lines'})) {
	$opts{'lines'} = 10;
}


my $db = DBI->connect("dbi:SQLite:dbname=$dbpath",'','') || die($DBI::errstr);
$db->do('pragma cache_size = 24576'); # set a 24mb cache

my $sth = gen_sth(%opts);
if($opts{'debug'}) {
	print($sth->{'Statement'} . "\n");
	exit(0);
}
if($opts{'delete'}) {
	$sth->execute();
	if($opts{'vacuum'}) {
		$sth = $db->prepare('select min(id) from logs');
		$sth->execute();
		if(my $row = $sth->fetchrow_arrayref()) {
			$db->do('update logs set id = id - ' . $row->[0]); # start the log entries back at 0
		}

		$db->do('vacuum');
	}
	$db->commit();
} elsif(!$opts{'follow'}) {
	batch($sth, -1);
} else {
	my $in = Linux::Inotify2->new();
	$in->watch($dbpath, IN_MODIFY);
	my $last_id = batch($sth, -1);
	$opts{'time'} = undef;
	$opts{'lines'} = undef;
	my $sth = gen_sth(%opts);
	do {
		$last_id = batch($sth, $last_id);
	} while(defined($in->read()));
}
exit(0);

sub batch {
	my $sth = shift;
	my $last_id = shift;

	for my $message (fetch($sth, $last_id)) {
		my $line = $opts{'format'};
		$line =~ s/\%\{([^}]+)\}/strftime($1, localtime($message->[1]))/eg;
		$line =~ s/\%(.)/fmt($1, $message)/eg;
		print($line . "\n");
		$last_id = $message->[0];
	}
	return($last_id);
}
sub fmt {
	my $code = shift;
	my $message = shift;

	# %t = time
	# %f = Facility
	# %F = Facility (numeric)
	# %p = Priority
	# %P = Priority (numeric)
	# %h = host
	# %r = pRogram[pid]
	# %R = pRogram
	# %i = pId
	# %T = Tag
	# %m = Message

	# message
	# [0] = id
	# [1] = time
	# [2] = time_r
	# [3] = host
	# [4] = facility
	# [5] = priority
	# [6] = program
	# [7] = pid
	# [8] = tag
	# [9] = message

	if($code eq 't') {
		return(strftime('%Ec', localtime($message->[1])))
	}
	if($code eq 'f') {
		return($facilities[$message->[4]]);
	}
	if($code eq 'F') {
		return($message->[4]);
	}
	if($code eq 'p') {
		return($priorities[$message->[5]]);
	}
	if($code eq 'P') {
		return($message->[5]);
	}
	if($code eq 'h') {
		return($message->[3]);
	}
	if($code eq 'r') {
		return($message->[6] . (defined($message->[7]) ? '[' . $message->[7] . ']' : ''));
	}
	if($code eq 'R') {
		return($message->[6]);
	}
	if($code eq 'i') {
		return($message->[7]);
	}
	if($code eq 'T') {
		return($message->[8]);
	}
	if($code eq 'm') {
		return($message->[9]);
	}
	if($code eq '%') {
		return('%');
	}
	return('%' . $code); # unknown, spit it back out
}

sub gen_sth {
	my $opts = %_;
	my $q = '';
	if($opts{'delete'}) {
		$q .= 'delete ';
	} else {
		$q .= 'select id,time,time_r,host,facility,priority,program,pid,tag,message ';
	}
	$q .= 'from logs ';



	my @wheres = ();

	if(defined($opts{'time'})) {
		my ($equality, $value) = equality(EQUALITY_NUMERIC, $opts{'time'});
		if($value =~ /^\d+$/) {
			# unix timestamp
			push(@wheres, 'time ' . $equality . ' ' . $value);
		} else {
			# parse the date into a timestamp
			my $unixdate = UnixDate(ParseDate($value), '%s');
			if(!defined($unixdate)) {
				print(STDERR "Could not parse time '$value'\n");
				exit(1);
			}
			push(@wheres, 'time ' . $equality . ' ' . $unixdate);
		}
	}
	if(defined($opts{'program'})) {
		my ($equality, $value) = equality(EQUALITY_STRING, $opts{'program'});
		push(@wheres, 'program ' . $equality . ' ' . $value);
	}
	if(defined($opts{'host'})) {
		my ($equality, $value) = equality(EQUALITY_STRING, $opts{'host'});
		push(@wheres, 'host ' . $equality . ' ' . $value);
	}
	if(defined($opts{'tag'})) {
		my ($equality, $value) = equality(EQUALITY_STRING, $opts{'tag'});
		push(@wheres, 'tag ' . $equality . ' ' . $value);
	}
	if(defined($opts{'pid'})) {
		my ($equality, $value) = equality(EQUALITY_NUMERIC, $opts{'pid'});
		push(@wheres, 'pid ' . $equality . ' ' . $value);
	}
	if(defined($opts{'facility'})) {
		my ($equality, $value) = equality(EQUALITY_NUMERIC, $opts{'facility'});
		my $facility = xlate($value);
		if($facility == -1) {
			print(STDERR "Could not parse facility '$value'\n");
			exit(1);
		}
		push(@wheres, 'facility ' . $equality . ' ' . $facility);
	}
	if(defined($opts{'priority'})) {
		my ($equality, $value) = equality(EQUALITY_NUMERIC, $opts{'priority'});
		my $priority = xlate($value);
		if($priority == -1) {
			print(STDERR "Could not parse priority '$value'\n");
			exit(1);
		}
		push(@wheres, 'priority ' . $equality . ' ' . $priority);
	}

	if(!$opts{'delete'}) {
		push(@wheres, 'id > ?');
	}

	$q .= 'where ' . join(' and ', @wheres) . ' ';


	if(!$opts{'delete'}) {
		if($opts{'head'}) {
			$q .= 'order by time asc ';
		} else {
			$q .= 'order by time desc ';
		}
		if($opts{'lines'}) {
			$q .= 'limit ' . $opts{'lines'} . ' ';
		}
	}
	
	my $sth = $db->prepare($q) || die($q . "\n" . $db->errstr());
	if($opts{'head'}) {
		$sth->{'private_reverse'} = 0;
	} else {
		$sth->{'private_reverse'} = 1;
	}
	return($sth);
}

sub fetch {
	my $sth = shift;
	my $id_after = shift;

	my $r = $sth->execute($id_after) || die($db->errstr());
	my @rows;
	if(!$sth->{'private_reverse'}) {
		@rows = @{$sth->fetchall_arrayref()};
	} else {
		# we ordered with oldest first, reverse to get normal order
		@rows = reverse(@{$sth->fetchall_arrayref()});
	}
	
	return(@rows);
}

sub equality {
	my $type = shift;
	my $data = shift;

	if($type == EQUALITY_NUMERIC) {
		if($data =~ /^\s*([<>=]=?)\s*(.+)?\s*$/) {
			return($1, $2);
		} else {
			#print(STDERR "Unable to parse numeric atom: '$data'\n");
			#exit(2);
			return('=', $data);
		}
	} elsif($type == EQUALITY_STRING) {
		if($data =~ /^\s*=/) {
			return('=', $');
		}
		$data =~ s/\\/\\\\/g;
		$data =~ s/%/\\%/g;
		$data =~ s/_/\\_/g;
		$data =~ s/\*/%/g;
		$data =~ s/\?/_/g;
		$data =~ s/'/''/g;
		return('like', "'" . $data . "'");
	} else {
		die("Shouldnt be here\n");
	}
}

This is a deprecated template. Help us update this template!

Note
On systems with limited resources, it might be possible to increase performance of the script by compiling it into bytecode or machine code. I have not experimented with these options yet.


Sane GRUB2 config

I see a lot of people not wanting to upgrade to GRUB2 because of its stupidly complex configuration. Unfortunately none of the howto guides explain that its possible to dramatically simplify the config into something similar to what grub1 was like. You dont have to regenerate all the grub configs every time you build a new kernel, all you do is edit one file just like you did with grub1.

Here's what my grub2 config file looks like:

Filecustom.cfg

set default=0
set timeout=10
#set gfxmode="1024x768"
#set gfxpayload=keep
serial --unit=0 --speed=115200 --word=8 --parity=no --stop=8
terminal_output vga_text serial

menuentry "kernel-3.2.2-hardened-r1" {
	set root=(md/boot)
	linux /kernel-3.2.2-hardened-r1 console=ttyS0,115200n8 console=tty0 log_buf_len=128K consoleblank=0 panic=30 root=/dev/mirror/root
	initrd /initrd.gz
}
menuentry "kernel-3.1.5-hardened" {
	set root=(md/boot)
	linux /kernel-3.1.5-hardened console=ttyS0,115200n8 console=tty0 log_buf_len=128K consoleblank=0 panic=30 root=/dev/mirror/root
	initrd /initrd.gz
}

This is a deprecated template. Help us update this template!

Clean, simple, and easy to read.

Here's how.

Custom entry

Create a custom entry in the standard configuration. This is just to be 'proper'.

File/etc/grub.d/41_custom

#!/bin/sh
cat <<EOF
if [ -f  \$prefix/custom.cfg ]; then
  source \$prefix/custom.cfg;
fi
EOF

This is a deprecated template. Help us update this template!

root #chmod a+x /etc/grub.d/41_custom

This tells grub to look for the file /boot/grub2/custom.cfg and if it exists, include its contents

Regenerate config

This will be the last time you have to do this.

root #grub2-mkconfig

custom.cfg

Create /boot/grub2/custom.cfg with the contents similar to the file I show up above.

To make things even simpler I symlink /boot/grub2/custom.cfg to /boot/boot.cfg. There are very few files in /boot and so its easier to spot.

Use it

Thats it. Grub2 will automatically read in the custom.cfg on boot. Whenever you make a change to it, you dont need to run grub2-mkconfig any more.