~ubuntu-branches/ubuntu/hardy/bugzilla/hardy-security

« back to all changes in this revision

Viewing changes to syncshadowdb

  • Committer: Bazaar Package Importer
  • Author(s): Rémi Perrot
  • Date: 2004-04-02 01:13:32 UTC
  • Revision ID: james.westby@ubuntu.com-20040402011332-hxrg0n2szimd7d25
Tags: upstream-2.16.5
Import upstream version 2.16.5

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bonsaitools/bin/perl -w
 
2
# -*- Mode: perl; indent-tabs-mode: nil -*-
 
3
#
 
4
# The contents of this file are subject to the Mozilla Public
 
5
# License Version 1.1 (the "License"); you may not use this file
 
6
# except in compliance with the License. You may obtain a copy of
 
7
# the License at http://www.mozilla.org/MPL/
 
8
#
 
9
# Software distributed under the License is distributed on an "AS
 
10
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 
11
# implied. See the License for the specific language governing
 
12
# rights and limitations under the License.
 
13
#
 
14
# The Original Code is the Bugzilla Bug Tracking System.
 
15
#
 
16
# The Initial Developer of the Original Code is Netscape Communications
 
17
# Corporation. Portions created by Netscape are
 
18
# Copyright (C) 1998 Netscape Communications Corporation. All
 
19
# Rights Reserved.
 
20
#
 
21
# Contributor(s): Terry Weissman <terry@mozilla.org>
 
22
#                 David Gardiner <david.gardiner@unisa.edu.au>
 
23
 
 
24
use diagnostics;
 
25
use strict;
 
26
 
 
27
require "globals.pl";
 
28
require "defparams.pl";
 
29
 
 
30
# Shut up misguided -w warnings about "used only once".  "use vars" just
 
31
# doesn't work for me.
 
32
 
 
33
sub sillyness {
 
34
    my $zz;
 
35
    open SAVEOUT,">/dev/null";
 
36
    $zz = $::db;
 
37
    $zz = $::dbwritesallowed;
 
38
}
 
39
 
 
40
my $verbose = 0;
 
41
my $syncall = 0;
 
42
my $shutdown = 0;
 
43
my $tempdir = "data";
 
44
my $force = 0;
 
45
 
 
46
my $shutdown_msg = "Bugzilla is temporarily disabled while the database is backed up. Try again in a few minutes.";
 
47
 
 
48
sub Usage {
 
49
    print "Usage: syncshadowdb [-v] [-syncall] [-shutdown] [-tempdir dirname] [-force]\n";
 
50
    exit;
 
51
}
 
52
 
 
53
while (my $opt = shift @ARGV) {
 
54
    if ($opt eq '-v') {
 
55
        $verbose = 1;
 
56
    } elsif ($opt eq '-syncall') {
 
57
        $syncall = 1;
 
58
        $verbose = 1;
 
59
    } elsif ($opt eq '-shutdown') {
 
60
        $shutdown = 1;
 
61
    } elsif ($opt eq '-tempdir') {
 
62
        my $dir = shift @ARGV;
 
63
        if (-d $dir) {
 
64
            $tempdir = $dir;
 
65
        } else {
 
66
            print "$dir does not exist or is not a directory.  No syncing performed";
 
67
            exit;
 
68
        }
 
69
    } elsif ($opt eq '-force') {
 
70
        $force = 1;
 
71
    } elsif ($opt eq '--') {
 
72
        # do nothing - null parameter so we can use
 
73
        # multi-param system() call in globals.pl
 
74
    } else {
 
75
        Usage();
 
76
    }
 
77
}
 
78
$| = 1;
 
79
 
 
80
my $logtostderr = 0;
 
81
 
 
82
sub Verbose ($) {
 
83
    my ($str) = (@_);
 
84
    if ($verbose) {
 
85
        if ($logtostderr) {
 
86
            print STDERR $str, "\n";
 
87
        } else {
 
88
            print $str, "\n";
 
89
        }
 
90
    }
 
91
}
 
92
 
 
93
if (!Param("shadowdb")) {
 
94
    Verbose("We don't have shadow databases turned on; no syncing performed.");
 
95
    exit;
 
96
}
 
97
 
 
98
if (Param("shutdownhtml") && ! $force) {
 
99
    Verbose("Bugzilla was shutdown prior to running syncshadowdb. \n" .
 
100
            "  If you wish to sync anyway, use the -force command line option");
 
101
    exit;
 
102
}
 
103
 
 
104
my $wasshutdown = "";
 
105
if ($shutdown) {
 
106
    Verbose ("Shutting down bugzilla and waiting for connections to clear");
 
107
    # Record the old shutdownhtml so it can be restored at the end (this will
 
108
    # only be an issue if we are called using the -force command line param)
 
109
    $wasshutdown = Param("shutdownhtml");
 
110
    $::param{'shutdownhtml'} = $shutdown_msg;
 
111
    WriteParams();
 
112
    # Now we need to wait for existing connections to this database to clear. We
 
113
    # do this by looking for connections to the main or shadow database using
 
114
    # 'mysqladmin processlist'
 
115
    my $cmd = "$::mysqlpath/mysqladmin -u $::db_user";
 
116
    if ($::db_pass) { $cmd .= " -p$::db_pass" }
 
117
    $cmd .= " processlist";
 
118
    my $found_proc = 1;
 
119
    # We need to put together a nice little regular expression to use in the
 
120
    # following loop that'll tell us if the return from mysqladmin contains
 
121
    # either the main or shadow database.
 
122
    my @dbs = ($::db_name, Param("shadowdb"));
 
123
    my $db_expr = "^\\s*(" . join ("\|", @dbs) . ")\\s*\$";
 
124
    # Don't let this thing wait forever...
 
125
    my $starttime = time();
 
126
    while ($found_proc) {
 
127
        $found_proc = 0;
 
128
        open (PROC, $cmd . "|");
 
129
        my @output = <PROC>;
 
130
        close (PROC);
 
131
        foreach my $line(@output) {
 
132
            my @info = split (/\|/, $line);
 
133
            # Ignore any line that doesn't have 9 pieces of info
 
134
            # or contain Id (pretty printing crap)
 
135
            if ($#info != 9 || $line =~ /Id/) { next }
 
136
            if ($info[4] =~ m/$db_expr/) {
 
137
                $found_proc = 1;
 
138
            }
 
139
        }
 
140
        # If there are still active connections to Bugzilla 10 minutes after
 
141
        # shutting it down, then something is wrong.
 
142
        if ((time() - $starttime) > 600) {
 
143
            # There should be a better way to notify the admin of something bad like
 
144
            # this happening.
 
145
            Verbose ("*** Waited for 10 minutes and there were still active \n" .
 
146
                     "    connections to the bugzilla database.  Giving up.");
 
147
            $::param{'shutdownhtml'} = $wasshutdown;
 
148
            WriteParams();
 
149
            exit;
 
150
        }
 
151
    }
 
152
}
 
153
 
 
154
 
 
155
my $wasusing = Param("queryagainstshadowdb");
 
156
 
 
157
$::param{'queryagainstshadowdb'} = 1; # Force us to be able to use the
 
158
                                      # shadowdb, even if other processes
 
159
                                      # are not supposed to.
 
160
 
 
161
 
 
162
ConnectToDatabase(1);
 
163
 
 
164
Verbose("Acquiring lock");
 
165
if ( $syncall == 1) {
 
166
    SendSQL("SELECT GET_LOCK('synclock', 2700)");
 
167
} else {
 
168
    SendSQL("SELECT GET_LOCK('synclock', 1)");
 
169
}
 
170
if (!FetchOneColumn()) {
 
171
    Verbose("Couldn't get the lock to do the shadow database syncing.");
 
172
    exit;
 
173
}
 
174
 
 
175
my $shadowtable = "$::db_name.shadowlog";
 
176
 
 
177
if (!$syncall) {
 
178
    Verbose("Looking for requests to sync the whole database.");
 
179
    SendSQL("SELECT id FROM $shadowtable " .
 
180
            "WHERE reflected = 0 AND command = 'SYNCUP'");
 
181
    if (FetchOneColumn()) {
 
182
        $syncall = 1;
 
183
    }
 
184
}
 
185
 
 
186
if ($syncall) {
 
187
    Verbose("Syncing up the shadow database by copying entire database in.");
 
188
    if ($wasusing) {
 
189
        $::param{'queryagainstshadowdb'} = 0;
 
190
        WriteParams();
 
191
        if (! $shutdown) {
 
192
            Verbose("Disabled reading from the shadowdb. Sleeping 10 seconds to let other procs catch up.");
 
193
            sleep(10);
 
194
        }
 
195
        $::param{'queryagainstshadowdb'} = 1;
 
196
    }
 
197
    my @tables;
 
198
    SendSQL("SHOW TABLES");
 
199
    my $query = "";
 
200
    while (MoreSQLData()) {
 
201
        my $table = FetchOneColumn();
 
202
        push(@tables, $table);
 
203
        if ($query) {
 
204
            $query .= ", $table WRITE";
 
205
        } else {
 
206
            $query = "LOCK TABLES $table WRITE";
 
207
        }
 
208
    }
 
209
    if (@tables) {
 
210
        Verbose("Locking entire shadow database");
 
211
        SendSQL($query);
 
212
        foreach my $table (@tables) {
 
213
            Verbose("Dropping old shadow table $table");
 
214
            SendSQL("DROP TABLE $table");
 
215
        }
 
216
        SendSQL("UNLOCK TABLES");
 
217
    }    
 
218
    # Carefully lock the whole real database for reading, except for the
 
219
    # shadowlog table, which we lock for writing.  Then dump everything
 
220
    # into the shadowdb database.  Then mark everything in the shadowlog
 
221
    # as reflected.  Only then unlock everything.  This sequence causes
 
222
    # us to be sure not to miss anything or get something twice.
 
223
    SendSQL("USE $::db_name");
 
224
    SendSQL("SHOW TABLES");
 
225
    @tables = ();
 
226
    $query = "LOCK TABLES shadowlog WRITE";
 
227
    while (MoreSQLData()) {
 
228
        my $table = FetchOneColumn();
 
229
        if ($table ne "shadowlog") {
 
230
            $query .= ", $table READ";
 
231
            push(@tables, $table);
 
232
        }
 
233
    }
 
234
    Verbose("Locking entire database");
 
235
    SendSQL($query);
 
236
    my $tempfile = "$tempdir/tmpsyncshadow.$$";
 
237
    Verbose("Dumping database to a temp file ($tempfile).");
 
238
    my @ARGS = ("-u", $::db_user);
 
239
    if ($::db_pass) { push @ARGS, "-p$::db_pass" }
 
240
    push @ARGS, "-l", "-e", $::db_name, @tables;
 
241
    open SAVEOUT, ">&STDOUT";     # stash the original output stream
 
242
    open STDOUT, ">$tempfile";    # redirect to file
 
243
    select STDOUT; $| = 1;        # disable buffering
 
244
    system("$::mysqlpath/mysqldump", @ARGS);
 
245
    open STDOUT, ">&SAVEOUT";     # redirect back to original stream
 
246
    Verbose("Restoring from tempfile into shadowdb");
 
247
    my $extra = "-u $::db_user";
 
248
    if ($::db_pass) {
 
249
        $extra .= " -p$::db_pass";
 
250
    }
 
251
    if ($verbose) {
 
252
        $extra .= " -v";
 
253
    }
 
254
    open(MYSQL, "cat $tempfile | $::mysqlpath/mysql $extra " .
 
255
         Param("shadowdb") . "|") || die "Couldn't do db copy";
 
256
    my $count = 0;
 
257
    while (<MYSQL>) {
 
258
        print ".";
 
259
        $count++;
 
260
        if ($count % 70 == 0) {
 
261
            print "\n";
 
262
        }
 
263
    }
 
264
    close(MYSQL);
 
265
    unlink($tempfile);
 
266
    Verbose("");
 
267
    
 
268
    
 
269
    $::dbwritesallowed = 1;
 
270
#    SendSQL("UPDATE shadowlog SET reflected = 1 WHERE reflected = 0", 1);
 
271
    SendSQL("DELETE FROM shadowlog", 1);
 
272
    SendSQL("UNLOCK TABLES");
 
273
    if ($wasusing) {
 
274
        Verbose("Reenabling other processes to read from the shadow db");
 
275
        $::param{'queryagainstshadowdb'} = 1;
 
276
        WriteParams();
 
277
    }
 
278
    if ($shutdown) {
 
279
        Verbose("Restoring the original shutdown message (if any)");
 
280
        $::param{'shutdownhtml'} = $wasshutdown;
 
281
        WriteParams();
 
282
    }
 
283
    Verbose("OK, done.");
 
284
}
 
285
 
 
286
Verbose("Looking for commands to execute.");
 
287
$::dbwritesallowed = 1;
 
288
 
 
289
# Make us low priority, to not block anyone who is trying to actually use
 
290
# the shadowdb.  Note that this is carefully coded to ignore errors; we want
 
291
# to keep going even on older mysqld's that don't have the
 
292
# SQL_LOW_PRIORITY_UPDATES option.
 
293
$::db->do("SET OPTION SQL_LOW_PRIORITY_UPDATES = 1"); 
 
294
 
 
295
while (1) {
 
296
    SendSQL("SELECT id, command FROM $shadowtable WHERE reflected = 0 " .
 
297
            "ORDER BY id LIMIT 1");
 
298
    my ($id, $command) = (FetchSQLData());
 
299
    if (!$id) {
 
300
        last;
 
301
    }
 
302
    Verbose("Executing command in shadow db: $command");
 
303
    SendSQL($command, 1);
 
304
    SendSQL("UPDATE $shadowtable SET reflected = 1 WHERE id = $id", 1);
 
305
}
 
306
 
 
307
Verbose("Releasing lock.");
 
308
SendSQL("SELECT RELEASE_LOCK('synclock')");