//
// aegis - project change supervisor
// Copyright (C) 1991-1995, 2002-2006, 2008, 2012 Peter Miller
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, see
// .
//
#include
#include
#include
#include
#include
#include
#include
#include
//
// This file contains "commit" functions.
//
// The idea is that aegis can be interrupted, or fail from errors, and
// behave as if "nothing" had happened. That is, no user discernable
// difference to their environment, and certainly no changes to aegis'
// data base.
//
// To do this, many database functions write updates to temporary files
// "near" where they are to go, eventually. A commit and an abort
// function are both issued, one to put the new file where it really
// goes, and one to remove it. Exactly one option will be exercised.
//
// Commit actions will be performed with the user-id set the same as was
// set at the time the commit call was issued.
//
enum what_ty
{
what_rename,
what_unlink_errok,
what_rmdir_errok,
what_rmdir_tree_bg,
what_rmdir_tree_errok,
what_hard_link,
what_symlink
};
struct action_ty
{
what_ty what;
nstring path1;
nstring path2;
action_ty *next;
int uid;
int gid;
int umask;
action_ty() :
what((what_ty)-1),
next(0),
uid(0),
gid(0),
umask(0)
{
os_become_query(&uid, &gid, &umask);
}
};
static action_ty *head1;
static action_ty *tail1;
static action_ty *head2;
/**
* The link1 function is used to add an new item to the head of chain1.
*
* @param action
* what to do
* @param path1
* mandatory argument
* @param path2
* optional argument
*/
static void
link1(what_ty what, const nstring &path1, const nstring &path2)
{
trace(("commit::link1(what = %d, path1 = \"%s\", path2 = \"%s\")\n{\n",
what, path1.c_str(), path2.c_str()));
action_ty *new_thing = new action_ty;
new_thing->what = what;
new_thing->path1 = path1;
new_thing->path2 = path2;
if (head1)
{
tail1->next = new_thing;
tail1 = new_thing;
}
else
head1 = tail1 = new_thing;
trace(("}\n"));
}
/**
* The link2 function is used to add an new item to the head of chain2
*
* @param action
* what to do
* @param path1
* mandatory argument
* @param path2
* optional argument
*/
static void
link2(what_ty what, const nstring &path1, const nstring &path2)
{
trace(("commit::link2(what = %d, path1 = \"%s\", path2 = \"%s\")\n{\n",
what, path1.c_str(), path2.c_str()));
action_ty *new_thing = new action_ty;
new_thing->what = what;
new_thing->path1 = path1;
new_thing->path2 = path2;
new_thing->next = head2;
head2 = new_thing;
trace(("}\n"));
}
void
commit_rename(string_ty *from, string_ty *to)
{
commit_rename(nstring(from), nstring(to));
}
void
commit_rename(const nstring &from, const nstring &to)
{
trace(("commit_rename(from = \"%s\", to = \"%s\")\n{\n", from.c_str(),
to.c_str()));
link1(what_rename, from, to);
trace(("}\n"));
}
void
commit_symlink(string_ty *from, string_ty *to)
{
commit_symlink(nstring(from), nstring(to));
}
void
commit_symlink(const nstring &from, const nstring &to)
{
trace(("commit_symlink(from = \"%s\", to = \"%s\")\n{\n", from.c_str(),
to.c_str()));
link1(what_symlink, from, to);
trace(("}\n"));
}
void
commit_hard_link(string_ty *from, string_ty *to)
{
commit_hard_link(nstring(from), nstring(to));
}
void
commit_hard_link(const nstring &from, const nstring &to)
{
trace(("commit_hard_link(from = \"%s\", to = \"%s\")\n{\n", from.c_str(),
to.c_str()));
link1(what_hard_link, from, to);
trace(("}\n"));
}
void
commit_unlink_errok(string_ty *path)
{
commit_unlink_errok(nstring(path));
}
void
commit_unlink_errok(const nstring &path)
{
trace(("commit_unlink_errok(path = \"%s\")\n{\n", path.c_str()));
link2(what_unlink_errok, path, "");
trace(("}\n"));
}
void
commit_rmdir_errok(string_ty *path)
{
commit_rmdir_errok(nstring(path));
}
void
commit_rmdir_errok(const nstring &path)
{
trace(("commit_rmdir_errok(path = \"%s\")\n{\n", path.c_str()));
link2(what_rmdir_errok, path, "");
trace(("}\n"));
}
void
commit_rmdir_tree_bg(string_ty *path)
{
commit_rmdir_tree_bg(nstring(path));
}
void
commit_rmdir_tree_bg(const nstring &path)
{
trace(("commit_rmdir_tree_bg(path = \"%s\")\n{\n", path.c_str()));
link2(what_rmdir_tree_bg, path, "");
trace(("}\n"));
}
void
commit_rmdir_tree_errok(string_ty *path)
{
commit_rmdir_tree_errok(nstring(path));
}
void
commit_rmdir_tree_errok(const nstring &path)
{
trace(("commit_rmdir_tree_errok(path = \"%s\")\n{\n", path.c_str()));
link2(what_rmdir_tree_errok, path, "");
trace(("}\n"));
}
void
commit(void)
{
//
// Disable interrupts (such as ^C) for the duration. Note that
// commit consists solely of file renames and removes. No long
// writes are performed at this time. Sometimes there is a lot
// to do.
//
trace(("commit()\n{\n"));
interrupt_disable();
//
// Perform the queued actions.
//
while (head1 || head2)
{
//
// Take the first item off the list.
// Note that actions may append more items to the list.
//
action_ty *action = 0;
if (head1)
{
action = head1;
head1 = action->next;
if (!head1)
tail1 = 0;
}
else
{
action = head2;
head2 = action->next;
}
//
// Do the action.
//
os_become(action->uid, action->gid, action->umask);
switch (action->what)
{
case what_rename:
os_rename(action->path1, action->path2);
undo_rename(action->path2, action->path1);
break;
case what_symlink:
os_symlink(action->path1, action->path2);
undo_unlink_errok(action->path2);
break;
case what_hard_link:
os_link(action->path1, action->path2);
undo_unlink_errok(action->path2);
break;
case what_unlink_errok:
os_unlink_errok(action->path1);
break;
case what_rmdir_errok:
os_rmdir_errok(action->path1);
break;
case what_rmdir_tree_bg:
os_rmdir_bg(action->path1);
break;
case what_rmdir_tree_errok:
if (os_exists(action->path1))
{
dir_functor_rm_dir_tree eraser;
dir_walk(action->path1, eraser);
}
break;
}
os_become_undo();
//
// Delete the list element.
//
delete action;
}
//
// it's all committed, nothing left to undo.
//
undo_cancel();
//
// Enable interrupts once more.
//
interrupt_enable();
trace(("}\n"));
}
// vim: set ts=8 sw=4 et :