From d4c61b72813b880d86b316770f2e3819a6428202 Mon Sep 17 00:00:00 2001 From: joey Date: Thu, 15 Feb 2007 02:22:08 +0000 Subject: * Many changes to make ikiwiki very resistant to write failures including out of disk space situations. ikiwiki should never leave truncated files, and if the error occurs during a web-based file edit, the user will be given an opportunity to retry. Inspired by the many ways Moin Moin destroys itself when out of disk. :-) * Fix syslogging of errors. --- IkiWiki/CGI.pm | 29 +++++++++++++++++++++++++---- IkiWiki/Plugin/aggregate.pm | 13 +++++++++---- IkiWiki/Plugin/passwordauth.pm | 3 ++- IkiWiki/Plugin/search.pm | 28 ++++++++++++++++++---------- IkiWiki/Render.pm | 34 +++++++++++++++++++--------------- IkiWiki/UserInfo.pm | 11 +++++++++-- 6 files changed, 82 insertions(+), 36 deletions(-) (limited to 'IkiWiki') diff --git a/IkiWiki/CGI.pm b/IkiWiki/CGI.pm index 6c489df8d..973053427 100644 --- a/IkiWiki/CGI.pm +++ b/IkiWiki/CGI.pm @@ -252,12 +252,15 @@ sub cgi_prefs ($$) { #{{{ elsif ($form->submitted eq 'Save Preferences' && $form->validate) { foreach my $field (qw(email subscriptions)) { if (defined $form->field($field) && length $form->field($field)) { - userinfo_set($user_name, $field, $form->field($field)) || error("failed to set $field"); + userinfo_set($user_name, $field, $form->field($field)) || + error("failed to set $field"); } } if (is_admin($user_name)) { set_banned_users(grep { ! is_admin($_) } - split(' ', $form->field("banned_users"))); + split(' ', + $form->field("banned_users"))) || + error("failed saving changes"); } $form->text(gettext("Preferences saved.")); } @@ -487,7 +490,25 @@ sub cgi_editpage ($$) { #{{{ $content=~s/\r\n/\n/g; $content=~s/\r/\n/g; - writefile($file, $config{srcdir}, $content); + + $config{cgi}=0; # avoid cgi error message + eval { writefile($file, $config{srcdir}, $content) }; + $config{cgi}=1; + if ($@) { + $form->field(name => "rcsinfo", value => rcs_prepedit($file), + force => 1); + $form->tmpl_param("failed_save", 1); + $form->tmpl_param("error_message", $@); + $form->field("editcontent", value => $content, force => 1); + $form->field(name => "comments", value => $form->field('comments'), force => 1); + $form->field("do", "edit)"); + $form->tmpl_param("page_select", 0); + $form->field(name => "page", type => 'hidden'); + $form->field(name => "type", type => 'hidden'); + $form->title(sprintf(gettext("editing %s"), $page)); + print $form->render(submit => \@buttons); + return; + } if ($config{rcs}) { my $message=""; @@ -616,7 +637,7 @@ sub cgi (;$$) { #{{{ email => "", password => "", regdate => time, - }); + }) || error("failed adding user"); } } } diff --git a/IkiWiki/Plugin/aggregate.pm b/IkiWiki/Plugin/aggregate.pm index 7fceb0df3..a6f850236 100644 --- a/IkiWiki/Plugin/aggregate.pm +++ b/IkiWiki/Plugin/aggregate.pm @@ -153,8 +153,11 @@ sub loadstate () { #{{{ sub savestate () { #{{{ eval q{use HTML::Entities}; error($@) if $@; - open (OUT, ">$config{wikistatedir}/aggregate" || - die "$config{wikistatedir}/aggregate: $!"); + my $newfile="$config{wikistatedir}/aggregate.new"; + # TODO: This cleanup function could use improvement. Any newly + # aggregated files are left behind unrecorded, and should be deleted. + my $cleanup = sub { unlink($newfile) }; + open (OUT, ">$newfile") || error("open $newfile: $!", $cleanup); foreach my $data (values %feeds, values %guids) { if ($data->{remove}) { if ($data->{name}) { @@ -188,9 +191,11 @@ sub savestate () { #{{{ push @line, "$field=".$data->{$field}; } } - print OUT join(" ", @line)."\n"; + print OUT join(" ", @line)."\n" || error("write $newfile: $!", $cleanup); } - close OUT; + close OUT || error("save $newfile: $!", $cleanup); + rename($newfile, "$config{wikistatedir}/aggregate") || + error("rename $newfile: $!", $cleanup); } #}}} sub expire () { #{{{ diff --git a/IkiWiki/Plugin/passwordauth.pm b/IkiWiki/Plugin/passwordauth.pm index 6d395324e..e0aa72a19 100644 --- a/IkiWiki/Plugin/passwordauth.pm +++ b/IkiWiki/Plugin/passwordauth.pm @@ -187,7 +187,8 @@ sub formbuilder (@) { #{{{ my $user_name=$form->field('name'); foreach my $field (qw(password)) { if (defined $form->field($field) && length $form->field($field)) { - IkiWiki::userinfo_set($user_name, $field, $form->field($field)) || error("failed to set $field"); + IkiWiki::userinfo_set($user_name, $field, $form->field($field)) || + error("failed to set $field"); } } } diff --git a/IkiWiki/Plugin/search.pm b/IkiWiki/Plugin/search.pm index 1b5c66716..d35c33e76 100644 --- a/IkiWiki/Plugin/search.pm +++ b/IkiWiki/Plugin/search.pm @@ -89,14 +89,20 @@ sub estcfg () { #{{{ my $estdir="$config{wikistatedir}/hyperestraier"; my $cgi=IkiWiki::basename($config{cgiurl}); $cgi=~s/\..*$//; - open(TEMPLATE, ">:utf8", "$estdir/$cgi.tmpl") || - error("write $estdir/$cgi.tmpl: $!"); + + my $newfile="$estdir/$cgi.tmpl.new"; + my $cleanup = sub { unlink($newfile) }; + open(TEMPLATE, ">:utf8", $newfile) || error("open $newfile: $!", $cleanup); print TEMPLATE IkiWiki::misctemplate("search", "\n\n\n\n\n\n", - baseurl => IkiWiki::dirname($config{cgiurl})."/"); - close TEMPLATE; - open(TEMPLATE, ">$estdir/$cgi.conf") || - error("write $estdir/$cgi.conf: $!"); + baseurl => IkiWiki::dirname($config{cgiurl})."/") || + error("write $newfile: $!", $cleanup); + close TEMPLATE || error("save $newfile: $!", $cleanup); + rename($newfile, "$estdir/$cgi.tmpl") || + error("rename $newfile: $!", $cleanup); + + $newfile="$estdir/$cgi.conf"; + open(TEMPLATE, ">$newfile") || error("open $newfile: $!", $cleanup); my $template=template("estseek.conf"); eval q{use Cwd 'abs_path'}; $template->param( @@ -105,13 +111,15 @@ sub estcfg () { #{{{ destdir => abs_path($config{destdir}), url => $config{url}, ); - print TEMPLATE $template->output; - close TEMPLATE; + print TEMPLATE $template->output || error("write $newfile: $!", $cleanup); + close TEMPLATE || error("save $newfile: $!", $cleanup); + rename($newfile, "$estdir/$cgi.conf") || + error("rename $newfile: $!", $cleanup); + $cgi="$estdir/".IkiWiki::basename($config{cgiurl}); unlink($cgi); my $estseek = defined $config{estseek} ? $config{estseek} : '/usr/lib/estraier/estseek.cgi'; - symlink($estseek, $cgi) || - error("symlink $estseek $cgi: $!"); + symlink($estseek, $cgi) || error("symlink $estseek $cgi: $!"); } # }}} sub estcmd ($;@) { #{{{ diff --git a/IkiWiki/Render.pm b/IkiWiki/Render.pm index d8bc5a9d9..cbba28251 100644 --- a/IkiWiki/Render.pm +++ b/IkiWiki/Render.pm @@ -198,22 +198,26 @@ sub render ($) { #{{{ my $srcfd=readfile($srcfile, 1, 1); delete $depends{$file}; will_render($file, $file, 1); - my $destfd=writefile($file, $config{destdir}, undef, 1, 1); - my $blksize = 16384; - my ($len, $buf, $written); - while ($len = sysread $srcfd, $buf, $blksize) { - if (! defined $len) { - next if $! =~ /^Interrupted/; - error("failed to read $srcfile: $!"); - } - my $offset = 0; - while ($len) { - defined($written = syswrite OUT, $buf, $len, $offset) - or error("failed to write $file: $!"); - $len -= $written; - $offset += $written; + writefile($file, $config{destdir}, undef, 1, sub { + my $destfd=shift; + my $cleanup=shift; + + my $blksize = 16384; + my ($len, $buf, $written); + while ($len = sysread $srcfd, $buf, $blksize) { + if (! defined $len) { + next if $! =~ /^Interrupted/; + error("failed to read $srcfile: $!", $cleanup); + } + my $offset = 0; + while ($len) { + defined($written = syswrite $destfd, $buf, $len, $offset) + or error("failed to write $file: $!", $cleanup); + $len -= $written; + $offset += $written; + } } - } + }); $oldpagemtime{$file}=time; } } #}}} diff --git a/IkiWiki/UserInfo.pm b/IkiWiki/UserInfo.pm index 115a263ce..02c27991c 100644 --- a/IkiWiki/UserInfo.pm +++ b/IkiWiki/UserInfo.pm @@ -15,12 +15,19 @@ sub userinfo_retrieve () { #{{{ sub userinfo_store ($) { #{{{ my $userinfo=shift; + my $newfile="$config{wikistatedir}/userdb.new"; my $oldmask=umask(077); - my $ret=Storable::lock_store($userinfo, "$config{wikistatedir}/userdb"); + my $ret=Storable::lock_store($userinfo, $newfile); umask($oldmask); + if (defined $ret && $ret) { + if (! rename($newfile, "$config{wikistatedir}/userdb")) { + unlink($newfile); + $ret=undef; + } + } return $ret; } #}}} - + sub userinfo_get ($$) { #{{{ my $user=shift; my $field=shift; -- cgit v1.2.3