June 2, 2004

SysAdmin to SysAdmin: Perl's Tie::File module

Author: Brian Jones

Two factors outshine all others as reasons I became a systems administrator.
The first is that, well, I like computers and computing. The
second is that I'm not particularly fond of writing code. If I were, I'd
probably be a programmer. As it stands, if I can find tools that work, I won't
write code. However, inevitably, some site-specific situation will come up for
which I have no choice. In that case, I at least want to write very little
code. Perl's Tie::File has helped me do that on more than one occasion.

I'm sure hardcore Perl hackers could leave some crazy code snippets in the
comments for this article as examples of the insanity that Tie::File is capable
of. However, in my day-to-day work, I'm dealing with system files. Many of them
are configuration files, or flat-file databases of one form or another.
Tie::File allows me to act on an entire file line by
line, instead of bit by bit. Here's an example.

The big setup

In my environment, we have a research lab in which we use compat mode for
account information, and point the machines to an LDAP server outside our
domain. Compat mode provides compatibility with + and +@ syntax in the
/etc/passwd, /etc/group, and /etc/shadow files. We'll use /etc/passwd in this
example. The syntax compat mode supports allows you to add an account to the
local system whose information (shell, home directory, and crypted password
string, among other things) resides elsewhere, like on a NIS or LDAP server. So
I can add a line like this to the end of my /etc/passwd file:


This makes the user jonesy a valid account on the local machine, but I can
still maintain the user's account information centrally via NIS or LDAP. Adding
a line starting with +@ to my passwd file adds all users belonging to a particular netgroup, instead of just one user. My experience so far, though, is that this only works against a NIS server; the code for LDAP support is presumably on the way.

In my nsswitch.conf file, I need to configure the passwd system database
using two lines: one to tell it to use compat mode, and the second to tell the
system which service to use to look up information on lines starting with +:

passwd: compat
passwd_compat: ldap

There's a small catch to this, in that I can't just tag account entries to the end of my /etc/passwd file, because the last line of my passwd file has a safety net feature which looks like this:


This line includes all other valid LDAP accounts, making their login shell
/bin/false. This is to keep any system goofiness from letting a user in who
has a valid LDAP account, but no + entry in the /etc/passwd file. On some systems this can happen, though the user won't have a home directory, and they'll have the old "I have no name!" in their shell prompt. Therefore this needs to be the last line
of the file

Here's where Tie::File comes in. Here's a part of an adduser
script (with line numbers added) that adds a user to the /etc/passwd file just
above the last line:

1 #!/usr/bin/perl

2 use Tie::File;

3 tie @rows, 'Tie::File', '/etc/passwd' || die "Can't open: $!\n";
4 $numrows = @rows;
5 $insertpoint = $numrows - 1;
6 $newrec="+$ARGV[0]\n";
7 splice @rows, $insertpoint, 0, $newrec;
8 untie @rows;

If this file is called as adduser jonesy, then it will add the line +jonesy (in the code it's +$ARGV[0]) just above the last line of the passwd file. Line 1 tells the system which interpreter to use for the following code, and line 2 says to load a particular module. In line 3, I bind (or "tie") my array variable @rows to the rows in the /etc/passwd file, using Tie::File. Now
I'm free to act upon the rows in this file as individual widgets. I can push
new records to the file, or use a regex to determine if a line (in this case, an
account) is still valid, and remove it only if it is invalid.

On line 4, I get the number of rows in the file (returned by @rows) and assign it to $numrows. Then I set up my insertion point on line 5 to be the
row before the last row. On line 6, I define the record to be inserted as the
argument passed on the command line, preceded with a plus-sign. Finally, I use
splice on line 7, using the line before the last line as my offset, character 0 as my starting point, and the new record as the value to be spliced in between the last two existing lines of the file. To clean up, I simply untie my array on line 8.

In conclusion

This real-world scenario illustrates an
actual useful purpose for the Tie::File module, and it's only one of thousands
of possibilities. Think about all of the files systems administrators, or even
home users, deal with: iptables scripts, passwd and group files,
tcpwrappers files, simple flat-file database files. Tie::Files is an
extremely easy-to-use module for performing insert, delete, and modify operations, one line at a time.

Click Here!