Don't create a detail.work.work file when rerunning after
[freeradius.git] / scripts / radsqlrelay
1 #!/usr/bin/perl
2 ##
3 ##  radsqlrelay.pl      This program tails a SQL logfile and forwards
4 ##                      the queries to a database server. Used to
5 ##                      replicate accounting records to one (central)
6 ##                      database, even if the database has extended
7 ##                      downtime.
8 ##
9 ##  Version:    $Id$
10 ##
11 ##  Author:     Nicolas Baradakis <nicolas.baradakis@cegetel.net>
12 ##
13 ##  Copyright (C) 2005 Cegetel
14 ##
15 ##  This program is free software; you can redistribute it and/or
16 ##  modify it under the terms of the GNU General Public License
17 ##  as published by the Free Software Foundation; either version 2
18 ##  of the License, or (at your option) any later version.
19 ##
20 ##  This program is distributed in the hope that it will be useful,
21 ##  but WITHOUT ANY WARRANTY; without even the implied warranty of
22 ##  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 ##  GNU General Public License for more details.
24 ##
25 ##  You should have received a copy of the GNU General Public License
26 ##  along with this program; if not, write to the Free Software
27 ##  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
28 ##
29
30 use DBI;
31 use Fcntl;
32 use Getopt::Std;
33 use POSIX qw(:unistd_h :errno_h);
34
35 use warnings;
36 use strict;
37
38 my $need_exit = 0;
39
40 sub got_signal()
41 {
42     $need_exit = 1;
43 }
44
45 # /!\ OS-dependent structure
46 # Linux struct flock
47 #   short l_type;
48 #   short l_whence;
49 #   off_t l_start;
50 #   off_t l_len;
51 #   pid_t l_pid;
52 # c2ph says: typedef='s2 l2 i', sizeof=16
53 my $FLOCK_STRUCT = 's2l2i';
54
55 sub setlock($;$$)
56 {
57     my ($fh, $start, $len) = @_;
58     $start = 0 unless defined $start;
59     $len = 0 unless defined $len;
60
61                                     #type     whence    start   till  pid
62     my $packed = pack($FLOCK_STRUCT, F_WRLCK, SEEK_SET, $start, $len, 0);
63     if (fcntl($fh, F_SETLKW, $packed)) { return 1 }
64     else { return 0 }
65 }
66
67 sub usage()
68 {
69     print STDERR <<HERE;
70 usage: radsqlrelay [options] file_path
71 options:
72         -?              Print this help message.
73         -1              One-shot mode: push the file to database and exit.
74         -b database     Name of the database to use.
75         -d sql_driver   Driver to use: mysql, pg.
76         -h host         Connect to host.
77         -P port         Port number to use for connection.
78         -p passord      Password to use when connecting to server.
79         -u user         User for login.
80         -x              Turn on debugging.
81 HERE
82 }
83
84 sub connect_wait($)
85 {
86     my $dbinfo = shift;
87     my $dbh;
88     while (!$dbh) {
89         $dbh = DBI->connect($dbinfo->{base}, $dbinfo->{user}, $dbinfo->{pass},
90                             { RaiseError => 0, PrintError => 0,
91                               AutoCommit => 1 });
92         sleep (1) if !$dbh;
93         exit if $need_exit;
94     }
95     $dbinfo->{handle} = $dbh;
96 }
97
98 sub process_file($$)
99 {
100     my ($dbinfo, $path) = @_;
101
102     unless (-e $path.'.work') {
103         until (rename($path, $path.'.work')) {
104             if ($! == ENOENT) {
105                 sleep(1);
106                 return if $need_exit;
107             } else {
108                 print STDERR "error: Couldn't move $path to $path.work: $!\n";
109                 exit 1;
110             }
111         }
112     }
113
114     open(FILE, "+< $path.work") or die "error: Couldn't open $path.work: $!\n";
115     setlock(\*FILE) or die "error: Couldn't lock $path.work: $!\n";
116
117     while (<FILE>) {
118         chomp(my $query = $_);
119         until ($dbinfo->{handle}->do($query)) {
120             print $dbinfo->{handle}->errstr."\n";
121             if ($dbinfo->{handle}->ping) {
122                 sleep (1);
123             } else {
124                 print "error: Lost connection to database\n";
125                 $dbinfo->{handle}->disconnect;
126                 connect_wait($dbinfo);
127             }
128         }
129     }
130
131     unlink($path.'.work');
132     close(FILE); # and unlock
133 }
134
135 # sub main()
136
137 my %args = (
138             b => 'radius',
139             d => 'mysql',
140             h => 'localhost',
141             p => 'radius',
142             u => 'radius',
143 );
144 my $ret = getopts("b:d:fh:P:p:u:x1?", \%args);
145 if (!$ret or @ARGV != 1) {
146     usage();
147     exit 1;
148 }
149 if ($args{'?'}) {
150     usage();
151     exit 0;
152 }
153
154 my $data_source;
155 if (lc($args{d}) eq 'mysql') {
156     $data_source = "DBI:mysql:database=$args{b};host=$args{h}";
157 } elsif (lc($args{d}) eq 'pg') {
158     $data_source = "DBI:Pg:dbname=$args{b};host=$args{h}";
159 } else {
160     print STDERR "error: SQL driver not supported yet: $args{d}\n";
161     exit 1;
162 }
163 $data_source .= ";port=$args{P}" if $args{'P'};
164
165 $SIG{INT} = \&got_signal;
166 $SIG{TERM} = \&got_signal;
167
168 my %dbinfo = (
169               base => $data_source,
170               user => $args{u},
171               pass => $args{p},
172 );
173 connect_wait(\%dbinfo);
174
175 my $path = shift @ARGV;
176
177 until ($need_exit) {
178     process_file(\%dbinfo, $path);
179     last if ($args{1} || $need_exit);
180     sleep(10);
181 }
182
183 $dbinfo{handle}->disconnect;