1
#!/usr/bonsaitools/bin/perl -w
2
# -*- Mode: perl; indent-tabs-mode: nil -*-
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/
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.
14
# The Original Code is the Bugzilla Bug Tracking System.
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
21
# Contributor(s): Terry Weissman <terry@mozilla.org>
22
# David Gardiner <david.gardiner@unisa.edu.au>
28
require "defparams.pl";
30
# Shut up misguided -w warnings about "used only once". "use vars" just
31
# doesn't work for me.
35
open SAVEOUT,">/dev/null";
37
$zz = $::dbwritesallowed;
46
my $shutdown_msg = "Bugzilla is temporarily disabled while the database is backed up. Try again in a few minutes.";
49
print "Usage: syncshadowdb [-v] [-syncall] [-shutdown] [-tempdir dirname] [-force]\n";
53
while (my $opt = shift @ARGV) {
56
} elsif ($opt eq '-syncall') {
59
} elsif ($opt eq '-shutdown') {
61
} elsif ($opt eq '-tempdir') {
62
my $dir = shift @ARGV;
66
print "$dir does not exist or is not a directory. No syncing performed";
69
} elsif ($opt eq '-force') {
71
} elsif ($opt eq '--') {
72
# do nothing - null parameter so we can use
73
# multi-param system() call in globals.pl
86
print STDERR $str, "\n";
93
if (!Param("shadowdb")) {
94
Verbose("We don't have shadow databases turned on; no syncing performed.");
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");
104
my $wasshutdown = "";
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;
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";
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) {
128
open (PROC, $cmd . "|");
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/) {
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
145
Verbose ("*** Waited for 10 minutes and there were still active \n" .
146
" connections to the bugzilla database. Giving up.");
147
$::param{'shutdownhtml'} = $wasshutdown;
155
my $wasusing = Param("queryagainstshadowdb");
157
$::param{'queryagainstshadowdb'} = 1; # Force us to be able to use the
158
# shadowdb, even if other processes
159
# are not supposed to.
162
ConnectToDatabase(1);
164
Verbose("Acquiring lock");
165
if ( $syncall == 1) {
166
SendSQL("SELECT GET_LOCK('synclock', 2700)");
168
SendSQL("SELECT GET_LOCK('synclock', 1)");
170
if (!FetchOneColumn()) {
171
Verbose("Couldn't get the lock to do the shadow database syncing.");
175
my $shadowtable = "$::db_name.shadowlog";
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()) {
187
Verbose("Syncing up the shadow database by copying entire database in.");
189
$::param{'queryagainstshadowdb'} = 0;
192
Verbose("Disabled reading from the shadowdb. Sleeping 10 seconds to let other procs catch up.");
195
$::param{'queryagainstshadowdb'} = 1;
198
SendSQL("SHOW TABLES");
200
while (MoreSQLData()) {
201
my $table = FetchOneColumn();
202
push(@tables, $table);
204
$query .= ", $table WRITE";
206
$query = "LOCK TABLES $table WRITE";
210
Verbose("Locking entire shadow database");
212
foreach my $table (@tables) {
213
Verbose("Dropping old shadow table $table");
214
SendSQL("DROP TABLE $table");
216
SendSQL("UNLOCK TABLES");
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");
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);
234
Verbose("Locking entire database");
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";
249
$extra .= " -p$::db_pass";
254
open(MYSQL, "cat $tempfile | $::mysqlpath/mysql $extra " .
255
Param("shadowdb") . "|") || die "Couldn't do db copy";
260
if ($count % 70 == 0) {
269
$::dbwritesallowed = 1;
270
# SendSQL("UPDATE shadowlog SET reflected = 1 WHERE reflected = 0", 1);
271
SendSQL("DELETE FROM shadowlog", 1);
272
SendSQL("UNLOCK TABLES");
274
Verbose("Reenabling other processes to read from the shadow db");
275
$::param{'queryagainstshadowdb'} = 1;
279
Verbose("Restoring the original shutdown message (if any)");
280
$::param{'shutdownhtml'} = $wasshutdown;
283
Verbose("OK, done.");
286
Verbose("Looking for commands to execute.");
287
$::dbwritesallowed = 1;
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");
296
SendSQL("SELECT id, command FROM $shadowtable WHERE reflected = 0 " .
297
"ORDER BY id LIMIT 1");
298
my ($id, $command) = (FetchSQLData());
302
Verbose("Executing command in shadow db: $command");
303
SendSQL($command, 1);
304
SendSQL("UPDATE $shadowtable SET reflected = 1 WHERE id = $id", 1);
307
Verbose("Releasing lock.");
308
SendSQL("SELECT RELEASE_LOCK('synclock')");