1 //
2 //      aegis - project change supervisor
3 //      Copyright (C) 1991-1995, 2002-2006, 2008, 2012 Peter Miller
4 //
5 //      This program is free software; you can redistribute it and/or modify
6 //      it under the terms of the GNU General Public License as published by
7 //      the Free Software Foundation; either version 3 of the License, or
8 //      (at your option) any later version.
9 //
10 //      This program is distributed in the hope that it will be useful,
11 //      but WITHOUT ANY WARRANTY; without even the implied warranty of
12 //      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 //      GNU General Public License for more details.
14 //
15 //      You should have received a copy of the GNU General Public License
16 //      along with this program; if not, see
17 //      <http://www.gnu.org/licenses/>.
18 //
19 
20 #include <common/mem.h>
21 #include <common/trace.h>
22 #include <libaegis/commit.h>
23 #include <libaegis/dir.h>
24 #include <libaegis/dir/functor/rm_dir_tree.h>
25 #include <libaegis/interrupt.h>
26 #include <libaegis/os.h>
27 #include <libaegis/undo.h>
28 
29 
30 //
31 // This file contains "commit" functions.
32 //
33 // The idea is that aegis can be interrupted, or fail from errors, and
34 // behave as if "nothing" had happened.  That is, no user discernable
35 // difference to their environment, and certainly no changes to aegis'
36 // data base.
37 //
38 // To do this, many database functions write updates to temporary files
39 // "near" where they are to go, eventually.  A commit and an abort
40 // function are both issued, one to put the new file where it really
41 // goes, and one to remove it.  Exactly one option will be exercised.
42 //
43 // Commit actions will be performed with the user-id set the same as was
44 // set at the time the commit call was issued.
45 //
46 
47 enum what_ty
48 {
49     what_rename,
50     what_unlink_errok,
51     what_rmdir_errok,
52     what_rmdir_tree_bg,
53     what_rmdir_tree_errok,
54     what_hard_link,
55     what_symlink
56 };
57 
58 struct action_ty
59 {
60     what_ty         what;
61     nstring         path1;
62     nstring         path2;
63     action_ty       *next;
64     int             uid;
65     int             gid;
66     int             umask;
67 
action_tyaction_ty68     action_ty() :
69         what((what_ty)-1),
70         next(0),
71         uid(0),
72         gid(0),
73         umask(0)
74     {
75         os_become_query(&uid, &gid, &umask);
76     }
77 };
78 
79 static action_ty *head1;
80 static action_ty *tail1;
81 static action_ty *head2;
82 
83 
84 /**
85   * The link1 function is used to add an new item to the head of chain1.
86   *
87   * @param action
88   *     what to do
89   * @param path1
90   *     mandatory argument
91   * @param path2
92   *     optional argument
93   */
94 
95 static void
link1(what_ty what,const nstring & path1,const nstring & path2)96 link1(what_ty what, const nstring &path1, const nstring &path2)
97 {
98     trace(("commit::link1(what = %d, path1 = \"%s\", path2 = \"%s\")\n{\n",
99         what, path1.c_str(), path2.c_str()));
100     action_ty *new_thing = new action_ty;
101     new_thing->what = what;
102     new_thing->path1 = path1;
103     new_thing->path2 = path2;
104     if (head1)
105     {
106         tail1->next = new_thing;
107         tail1 = new_thing;
108     }
109     else
110         head1 = tail1 = new_thing;
111     trace(("}\n"));
112 }
113 
114 
115 /**
116   * The link2 function is used to add an new item to the head of chain2
117   *
118   * @param action
119   *     what to do
120   * @param path1
121   *     mandatory argument
122   * @param path2
123   *     optional argument
124   */
125 
126 static void
link2(what_ty what,const nstring & path1,const nstring & path2)127 link2(what_ty what, const nstring &path1, const nstring &path2)
128 {
129     trace(("commit::link2(what = %d, path1 = \"%s\", path2 = \"%s\")\n{\n",
130         what, path1.c_str(), path2.c_str()));
131     action_ty *new_thing = new action_ty;
132     new_thing->what = what;
133     new_thing->path1 = path1;
134     new_thing->path2 = path2;
135     new_thing->next = head2;
136     head2 = new_thing;
137     trace(("}\n"));
138 }
139 
140 
141 void
commit_rename(string_ty * from,string_ty * to)142 commit_rename(string_ty *from, string_ty *to)
143 {
144     commit_rename(nstring(from), nstring(to));
145 }
146 
147 
148 void
commit_rename(const nstring & from,const nstring & to)149 commit_rename(const nstring &from, const nstring &to)
150 {
151     trace(("commit_rename(from = \"%s\", to = \"%s\")\n{\n", from.c_str(),
152         to.c_str()));
153     link1(what_rename, from, to);
154     trace(("}\n"));
155 }
156 
157 
158 void
commit_symlink(string_ty * from,string_ty * to)159 commit_symlink(string_ty *from, string_ty *to)
160 {
161     commit_symlink(nstring(from), nstring(to));
162 }
163 
164 
165 void
commit_symlink(const nstring & from,const nstring & to)166 commit_symlink(const nstring &from, const nstring &to)
167 {
168     trace(("commit_symlink(from = \"%s\", to = \"%s\")\n{\n", from.c_str(),
169         to.c_str()));
170     link1(what_symlink, from, to);
171     trace(("}\n"));
172 }
173 
174 
175 void
commit_hard_link(string_ty * from,string_ty * to)176 commit_hard_link(string_ty *from, string_ty *to)
177 {
178     commit_hard_link(nstring(from), nstring(to));
179 }
180 
181 
182 void
commit_hard_link(const nstring & from,const nstring & to)183 commit_hard_link(const nstring &from, const nstring &to)
184 {
185     trace(("commit_hard_link(from = \"%s\", to = \"%s\")\n{\n", from.c_str(),
186         to.c_str()));
187     link1(what_hard_link, from, to);
188     trace(("}\n"));
189 }
190 
191 
192 void
commit_unlink_errok(string_ty * path)193 commit_unlink_errok(string_ty *path)
194 {
195     commit_unlink_errok(nstring(path));
196 }
197 
198 
199 void
commit_unlink_errok(const nstring & path)200 commit_unlink_errok(const nstring &path)
201 {
202     trace(("commit_unlink_errok(path = \"%s\")\n{\n", path.c_str()));
203     link2(what_unlink_errok, path, "");
204     trace(("}\n"));
205 }
206 
207 
208 void
commit_rmdir_errok(string_ty * path)209 commit_rmdir_errok(string_ty *path)
210 {
211     commit_rmdir_errok(nstring(path));
212 }
213 
214 
215 void
commit_rmdir_errok(const nstring & path)216 commit_rmdir_errok(const nstring &path)
217 {
218     trace(("commit_rmdir_errok(path = \"%s\")\n{\n", path.c_str()));
219     link2(what_rmdir_errok, path, "");
220     trace(("}\n"));
221 }
222 
223 
224 void
commit_rmdir_tree_bg(string_ty * path)225 commit_rmdir_tree_bg(string_ty *path)
226 {
227     commit_rmdir_tree_bg(nstring(path));
228 }
229 
230 
231 void
commit_rmdir_tree_bg(const nstring & path)232 commit_rmdir_tree_bg(const nstring &path)
233 {
234     trace(("commit_rmdir_tree_bg(path = \"%s\")\n{\n", path.c_str()));
235     link2(what_rmdir_tree_bg, path, "");
236     trace(("}\n"));
237 }
238 
239 
240 void
commit_rmdir_tree_errok(string_ty * path)241 commit_rmdir_tree_errok(string_ty *path)
242 {
243     commit_rmdir_tree_errok(nstring(path));
244 }
245 
246 
247 void
commit_rmdir_tree_errok(const nstring & path)248 commit_rmdir_tree_errok(const nstring &path)
249 {
250     trace(("commit_rmdir_tree_errok(path = \"%s\")\n{\n", path.c_str()));
251     link2(what_rmdir_tree_errok, path, "");
252     trace(("}\n"));
253 }
254 
255 
256 void
commit(void)257 commit(void)
258 {
259     //
260     // Disable interrupts (such as ^C) for the duration.  Note that
261     // commit consists solely of file renames and removes.  No long
262     // writes are performed at this time.  Sometimes there is a lot
263     // to do.
264     //
265     trace(("commit()\n{\n"));
266     interrupt_disable();
267 
268     //
269     // Perform the queued actions.
270     //
271     while (head1 || head2)
272     {
273         //
274         // Take the first item off the list.
275         // Note that actions may append more items to the list.
276         //
277         action_ty *action = 0;
278         if (head1)
279         {
280             action = head1;
281             head1 = action->next;
282             if (!head1)
283                 tail1 = 0;
284         }
285         else
286         {
287             action = head2;
288             head2 = action->next;
289         }
290 
291         //
292         // Do the action.
293         //
294         os_become(action->uid, action->gid, action->umask);
295         switch (action->what)
296         {
297         case what_rename:
298             os_rename(action->path1, action->path2);
299             undo_rename(action->path2, action->path1);
300             break;
301 
302         case what_symlink:
303             os_symlink(action->path1, action->path2);
304             undo_unlink_errok(action->path2);
305             break;
306 
307         case what_hard_link:
308             os_link(action->path1, action->path2);
309             undo_unlink_errok(action->path2);
310             break;
311 
312         case what_unlink_errok:
313             os_unlink_errok(action->path1);
314             break;
315 
316         case what_rmdir_errok:
317             os_rmdir_errok(action->path1);
318             break;
319 
320         case what_rmdir_tree_bg:
321             os_rmdir_bg(action->path1);
322             break;
323 
324         case what_rmdir_tree_errok:
325             if (os_exists(action->path1))
326             {
327                 dir_functor_rm_dir_tree eraser;
328                 dir_walk(action->path1, eraser);
329             }
330             break;
331         }
332         os_become_undo();
333 
334         //
335         // Delete the list element.
336         //
337         delete action;
338     }
339 
340     //
341     // it's all committed, nothing left to undo.
342     //
343     undo_cancel();
344 
345     //
346     // Enable interrupts once more.
347     //
348     interrupt_enable();
349     trace(("}\n"));
350 }
351 
352 
353 // vim: set ts=8 sw=4 et :
354