1 //
2 //      aegis - project change supervisor
3 //      Copyright (C) 1991-1995, 1997-1999, 2001-2008, 2011, 2012 Peter Miller
4 //      Copyright (C) 2007-2009 Walter Franzini
5 //
6 //      This program is free software; you can redistribute it and/or modify
7 //      it under the terms of the GNU General Public License as published by
8 //      the Free Software Foundation; either version 3 of the License, or
9 //      (at your option) any later version.
10 //
11 //      This program is distributed in the hope that it will be useful,
12 //      but WITHOUT ANY WARRANTY; without even the implied warranty of
13 //      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 //      GNU General Public License for more details.
15 //
16 //      You should have received a copy of the GNU General Public License
17 //      along with this program. If not, see
18 //      <http://www.gnu.org/licenses/>.
19 //
20 
21 #include <common/ac/assert.h>
22 #include <common/ac/ctype.h>
23 #include <common/ac/stdio.h>
24 #include <common/ac/stdlib.h>
25 #include <common/ac/string.h>
26 #include <common/ac/unistd.h>
27 
28 #include <common/env.h>
29 #include <common/error.h>
30 #include <common/libdir.h>
31 #include <common/mem.h>
32 #include <common/nstring/list.h>
33 #include <common/progname.h>
34 #include <common/str_list.h>
35 #include <common/trace.h>
36 #include <libaegis/arglex2.h>
37 #include <libaegis/commit.h>
38 #include <libaegis/gonzo.h>
39 #include <libaegis/gstate.fmtgen.h>
40 #include <libaegis/lock.h>
41 #include <libaegis/os.h>
42 #include <libaegis/project.h>
43 #include <libaegis/sub.h>
44 #include <libaegis/undo.h>
45 #include <libaegis/user.h>
46 #include <libaegis/ustate.fmtgen.h>
47 
48 struct gonzo_ty
49 {
50     string_ty       *dir;
51     string_ty       *gstate_filename;
52     gstate_ty       *gstate_data;
53     int             is_a_new_file;
54     int             modified;
55     long            lock_magic;
56     int             temporary;
57 };
58 
59 static size_t   ngonzos;
60 static size_t   ngonzos_max;
61 static gonzo_ty **gonzo;
62 static int      done_tail;
63 
64 
65 static user_ty::pointer
gonzo_user(void)66 gonzo_user(void)
67 {
68     static user_ty::pointer u;
69 
70     if (!u)
71     {
72         u = user_ty::create(configured_aegis_uid(), configured_aegis_gid());
73         u->umask_set(022);
74 
75         if (u->get_uid() >= AEGIS_MIN_UID)
76         {
77             sub_context_ty  *scp;
78 
79             scp = sub_context_new();
80             sub_var_set_long(scp, "Number1", u->get_uid());
81             sub_var_set_long(scp, "Number2", AEGIS_MIN_UID);
82             fatal_intl
83             (
84                 scp,
85      i18n("AEGIS_USER_UID ($number1) vs AEGIS_MIN_UID ($number2) misconfigured")
86             );
87             // NOTREACHED
88             sub_context_delete(scp);
89         }
90         if (u->get_gid() >= AEGIS_MIN_GID)
91         {
92             sub_context_ty  *scp;
93 
94             scp = sub_context_new();
95             sub_var_set_long(scp, "Number1", u->get_gid());
96             sub_var_set_long(scp, "Number2", AEGIS_MIN_GID);
97             fatal_intl
98             (
99                 scp,
100      i18n("AEGIS_USER_GID ($number1) vs AEGIS_MIN_GID ($number2) misconfigured")
101             );
102             // NOTREACHED
103             sub_context_delete(scp);
104         }
105     }
106     return u;
107 }
108 
109 
110 static int
is_temporary(string_ty * s)111 is_temporary(string_ty *s)
112 {
113     return !!strstr(s->str_text, "/tmp/");
114 }
115 
116 
117 void
gonzo_library_append(const char * s)118 gonzo_library_append(const char *s)
119 {
120     gonzo_ty        *gp;
121     string_ty       *tmp;
122     string_ty       *dir;
123 
124     //
125     // resolve the pathname
126     //
127     trace(("gonzo_library_append(s = \"%s\")\n{\n", s));
128     assert(s);
129     assert(!done_tail);
130     tmp = str_from_c(s);
131     os_become_orig();
132     dir = os_pathname(tmp, 1);
133     os_become_undo();
134     str_free(tmp);
135 
136     //
137     // append the new entry to the end of the list
138     //
139     gp = (gonzo_ty *)mem_alloc_clear(sizeof(gonzo_ty));
140     gp->dir = dir;
141     gp->temporary = is_temporary(dir);
142     gp->gstate_filename = str_format("%s/state", gp->dir->str_text);
143     trace(("gonzo = %p;\n", gonzo));
144     trace(("ngonzos = %ld;\n", (long)ngonzos));
145 
146     if (ngonzos >= ngonzos_max)
147     {
148         ngonzos_max = ngonzos_max * 2 + 4;
149         gonzo_ty **new_gonzo = new gonzo_ty * [ngonzos_max];
150         for (size_t k = 0; k < ngonzos; ++k)
151             new_gonzo[k] = gonzo[k];
152         delete [] gonzo;
153         gonzo = new_gonzo;
154     }
155     gonzo[ngonzos++] = gp;
156     trace(("}\n"));
157 }
158 
159 
160 static void
lock_sync(gonzo_ty * gp)161 lock_sync(gonzo_ty *gp)
162 {
163     trace(("lock_sync(gp = %p)\n{\n", gp));
164     long n = lock_magic();
165     if (gp->lock_magic == n)
166         goto ret;
167     assert(!gp->modified);
168     gp->lock_magic = n;
169 
170     if (gp->gstate_data && !gp->is_a_new_file)
171     {
172         gstate_type.free(gp->gstate_data);
173         gp->gstate_data = 0;
174     }
175     ret:
176     trace(("}\n"));
177 }
178 
179 
180 static gstate_ty *
gonzo_gstate_get(gonzo_ty * gp)181 gonzo_gstate_get(gonzo_ty *gp)
182 {
183     trace(("gonzo_gstate_get(gp = %p)\n{\n", gp));
184     lock_sync(gp);
185     if (!gp->gstate_data)
186     {
187         //
188         // The problem here is if we get EACCES (Permission denied)
189         // for the lstat to see if the file exists.  For now, we say
190         // that EACCESS is OK (that's what the second argument says)
191         // because most of the time this means we simply ignore global
192         // state files we can't read.  But if there is a new project
193         // being created (the only time one of these ignored files will
194         // need writing) when the "new" file is opened, the EACCES will
195         // happen again, so nothing will be damaged, and the user will
196         // see an appropriate error.
197         //
198         gonzo_become();
199         gp->is_a_new_file = !os_exists(gp->gstate_filename, true);
200         if (!gp->is_a_new_file)
201         {
202             os_chown_check
203             (
204                 gp->gstate_filename,
205                 0644,
206                 gonzo_user()->get_uid(),
207                 (gp->temporary ? -1 : gonzo_user()->get_gid())
208             );
209             gp->gstate_data = gstate_read_file(gp->gstate_filename);
210         }
211         else
212             gp->gstate_data = (gstate_ty *)gstate_type.alloc();
213         if (!gp->gstate_data->where)
214             gp->gstate_data->where =
215                 (gstate_where_list_ty *)gstate_where_list_type.alloc();
216         gonzo_become_undo();
217     }
218     trace(("return %p;\n", gp->gstate_data));
219     trace(("}\n"));
220     return gp->gstate_data;
221 }
222 
223 
224 //
225 // NAME
226 //      construct_library_directory
227 //
228 // SYNOPSIS
229 //      void construct_library_directory(gonzo_ty *gp);
230 //
231 // DESCRIPTION
232 //      The construct_library_directory function is used to
233 //      construct missing elements of the aegis library search path.
234 //      These elements must be constructed with great care so that
235 //      they may work on NFS mounted disks of "data-less" clients.
236 //
237 //      The path up to, but not including, the library directory must
238 //      be owned by the executing user.  The actions will fail if the
239 //      executing user does not have sufficient permissions, as
240 //      one would expect.
241 //
242 //      The library directory itself must be owned by AEGIS_USER.
243 //      This is to defend against tampering and ignorance.
244 //      Having this change of ownership requires some fast footwork
245 //      with chmod's to allow AEGIS_USER temporary access.
246 //
247 // ARGUMENTS
248 //      gp      - library directory to act upon
249 //
250 // CAVEAT
251 //      The chown() system call will not work on data-less clients,
252 //      because the remote hosts which owns the NFS mounted disks
253 //      will rarely trust the local "root" user, and map all
254 //      foreign "root" users to the "unknown" user instead.
255 //
256 
257 static void
construct_library_directory(gonzo_ty * gp)258 construct_library_directory(gonzo_ty *gp)
259 {
260     int             exists;
261     string_ty       *above;
262     string_ty       *above2;
263     string_ty       *root;
264     int             mode;
265 
266     //
267     // If the library directory already exists,
268     // then we need do nothing.
269     //
270     os_become_orig();
271     exists = os_exists(gp->dir);
272     if (exists)
273     {
274         os_become_undo();
275         return;
276     }
277 
278     //
279     // Construct all directories up to,
280     // but not including, the library directory,
281     // if they do not already exist.
282     // Construct these directories as the original user,
283     // possibly discovering relevant permission problems.
284     //
285     above = os_dirname(gp->dir);
286     above2 = str_n_from_c(above->str_text + 1, above->str_length - 1);
287     root = str_from_c("/");
288     os_mkdir_between(root, above2, 0755); // NOT setgid
289     str_free(root);
290     str_free(above2);
291 
292     //
293     // Get the mode of the directory containing the library directory,
294     // so that we may restore it later.  Change the mode to world
295     // writable, so that the library directory itself may be created
296     // belonging to gonzo.
297     //
298     mode = os_chmod_query(above);
299     mode |= 0111; // minimum: all may search
300     undo_chmod_errok(above, mode);
301     os_chmod(above, mode | 0777);
302     os_become_undo();
303 
304     //
305     // Create the library directory itself belonging to gonzo,
306     // and make sure it is world accessable.
307     //      (must be world writable in testing situations)
308     //
309     gonzo_become();
310     os_mkdir(gp->dir, 0755);
311     if (gp->temporary)
312         os_chmod(gp->dir, 0777);
313     gonzo_become_undo();
314 
315     //
316     // Restore permissions for the containing directory.
317     //
318     os_become_orig();
319     os_chmod(above, mode);
320     os_become_undo();
321     str_free(above);
322 }
323 
324 
325 static void
gonzo_gstate_write_sub(gonzo_ty * gp)326 gonzo_gstate_write_sub(gonzo_ty *gp)
327 {
328     string_ty       *filename_new;
329     string_ty       *filename_old;
330     static int      count;
331 
332     if (!gp->modified)
333         return;
334     trace(("gonzo_gstate_write_sub()\n{\n"));
335     assert(gp->gstate_data);
336     assert(gp->gstate_filename);
337     filename_new = str_format("%s,%d", gp->gstate_filename->str_text, ++count);
338     filename_old = str_format("%s,%d", gp->gstate_filename->str_text, ++count);
339     if (gp->is_a_new_file)
340     {
341         construct_library_directory(gp);
342         gonzo_become();
343         undo_unlink_errok(filename_new);
344         gstate_write_file(filename_new, gp->gstate_data, 0);
345         commit_rename(filename_new, gp->gstate_filename);
346         os_chmod(filename_new, 0644);
347         gonzo_become_undo();
348     }
349     else
350     {
351         gonzo_become();
352         undo_unlink_errok(filename_new);
353         gstate_write_file(filename_new, gp->gstate_data, 0);
354         commit_rename(gp->gstate_filename, filename_old);
355         commit_rename(filename_new, gp->gstate_filename);
356         commit_unlink_errok(filename_old);
357         os_chmod(filename_new, 0644);
358         gonzo_become_undo();
359     }
360     str_free(filename_new);
361     str_free(filename_old);
362     gp->modified = 0;
363     trace(("}\n"));
364 }
365 
366 
367 static void
do_tail(void)368 do_tail(void)
369 {
370     string_ty       *s1;
371     string_ty       *s2;
372     char            *cp;
373     size_t          j;
374     size_t          max;
375 
376     //
377     // only do this once
378     //
379     if (done_tail)
380         return;
381 
382     //
383     // fetch the environment variable
384     //
385     trace(("do_tail()\n{\n"));
386     cp = getenv("AEGIS_PATH");
387     if (!cp)
388     {
389         cp = getenv("AEGIS");
390         if (cp)
391         {
392             sub_context_ty  *scp;
393 
394             scp = sub_context_new();
395             sub_var_set_charstar(scp, "Name1", "AEGIS");
396             sub_var_set_charstar(scp, "Name2", "AEGIS_PATH");
397             verbose_intl
398             (
399                 scp,
400             i18n("warning: $name1 is obsolete, use $name2 environment variable")
401             );
402             sub_context_delete(scp);
403         }
404     }
405     if (cp)
406     {
407         s1 = str_from_c(cp);
408         string_list_ty path;
409         path.split(s1, ":", true);
410         str_free(s1);
411         for (j = 0; j < path.nstrings; ++j)
412         {
413             s1 = path.string[j];
414             if (!os_testing_mode() || is_temporary(s1))
415                 gonzo_library_append(s1->str_text);
416         }
417     }
418 
419 #ifndef SINGLE_USER
420     if (os_testing_mode())
421     {
422         if (!ngonzos)
423         {
424             sub_context_ty  *scp;
425 
426             scp = sub_context_new();
427             sub_var_set_charstar
428             (
429                 scp,
430                 "Name",
431                 arglex_token_name(arglex_token_library)
432             );
433             fatal_intl(scp, i18n("test mode needs $name"));
434             // NOTREACHED
435             sub_context_delete(scp);
436         }
437         max = ngonzos;
438     }
439     else
440 #endif
441     {
442         //
443         // always have the system one last
444         //      (this is where locks are taken)
445         //
446         gonzo_library_append(configured_comdir());
447         max = ngonzos - 1;
448     }
449 
450     //
451     // build a new environment variable
452     //
453     string_list_ty path;
454     for (j = 0; j < max; ++j)
455         path.push_back(gonzo[j]->dir);
456     s1 = path.unsplit(":");
457     env_set("AEGIS_PATH", s1->str_text);
458     str_free(s1);
459 
460     //
461     // zap the obsolete one, if present
462     //
463     s1 = str_from_c(progname_get());
464     s2 = str_upcase(s1);
465     str_free(s1);
466     env_unset(s2->str_text);
467     str_free(s2);
468 
469     //
470     // do not repeat
471     //
472     done_tail = 1;
473     trace(("}\n"));
474 }
475 
476 
477 static gonzo_ty *
gonzo_nth(size_t j)478 gonzo_nth(size_t j)
479 {
480     gonzo_ty        *result;
481 
482     trace(("gonzo_nth(j = %ld)\n{\n", (long)j));
483     do_tail();
484     if (j >= ngonzos)
485         result = 0;
486     else
487         result = gonzo[j];
488     trace(("return %p;\n", result));
489     trace(("}\n"));
490     return result;
491 }
492 
493 
494 void
gonzo_gstate_write(void)495 gonzo_gstate_write(void)
496 {
497     size_t          j;
498     gonzo_ty        *gp;
499 
500     trace(("gonzo_gstate_write()\n{\n"));
501     for (j = 0;; ++j)
502     {
503         gp = gonzo_nth(j);
504         if (!gp)
505             break;
506         gonzo_gstate_write_sub(gp);
507     }
508     trace(("}\n"));
509 }
510 
511 
512 static string_ty *
gonzo_project_home_path_sub(gonzo_ty * gp,string_ty * name)513 gonzo_project_home_path_sub(gonzo_ty *gp, string_ty *name)
514 {
515     gstate_ty       *gstate_data;
516     size_t          j;
517     string_ty       *result;
518     gstate_where_ty *addr =         0;
519 
520     //
521     // find the project in the gstate
522     //
523     trace(("gonzo_project_home_path_sub(gp = %p, name = \"%s\")\n{\n",
524         gp, name->str_text));
525     gstate_data = gonzo_gstate_get(gp);
526     assert(gstate_data->where);
527     result = 0;
528     for (j = 0; j < gstate_data->where->length; ++j)
529     {
530         addr = gstate_data->where->list[j];
531         if (addr->alias_for)
532             continue;
533         if (str_equal(addr->project_name, name))
534         {
535             result = addr->directory;
536             break;
537         }
538     }
539     trace(("return \"%s\";\n", (result ? result->str_text : "")));
540     trace(("}\n"));
541     return result;
542 }
543 
544 
545 string_ty *
gonzo_project_home_path_from_name(string_ty * name)546 gonzo_project_home_path_from_name(string_ty *name)
547 {
548     gonzo_ty        *gp;
549     size_t          j;
550     string_ty       *result;
551     string_ty       *s;
552 
553     //
554     // find the project in the gstate list
555     //
556     trace(("gonzo_project_home_path_from_name(name = \"%s\")\n{\n",
557         name->str_text));
558     result = 0;
559     for (j = 0;; ++j)
560     {
561         gp = gonzo_nth(j);
562         if (!gp)
563             break;
564         s = gonzo_project_home_path_sub(gp, name);
565         if (s)
566         {
567             result = s;
568             break;
569         }
570     }
571     trace(("return \"%s\";\n", (result ? result->str_text : "")));
572     trace(("}\n"));
573     return result;
574 }
575 
576 
577 static string_ty *
gonzo_alias_to_actual_sub(gonzo_ty * gp,string_ty * name)578 gonzo_alias_to_actual_sub(gonzo_ty *gp, string_ty *name)
579 {
580     gstate_ty       *gstate_data;
581     size_t          j;
582     string_ty       *result;
583 
584     //
585     // find the project in the gstate
586     //
587     trace(("gonzo_alias_to_actual_sub(gp = %p, name = \"%s\")\n{\n",
588         gp, name->str_text));
589     gstate_data = gonzo_gstate_get(gp);
590     assert(gstate_data->where);
591     result = 0;
592     for (j = 0; j < gstate_data->where->length; ++j)
593     {
594         gstate_where_ty *addr;
595 
596         addr = gstate_data->where->list[j];
597         if (!addr->alias_for)
598             continue;
599         if (str_equal(addr->project_name, name))
600         {
601             result = addr->alias_for;
602             break;
603         }
604     }
605     trace(("return \"%s\";\n", (result ? result->str_text : "")));
606     trace(("}\n"));
607     return result;
608 }
609 
610 
611 string_ty *
gonzo_alias_to_actual(string_ty * name)612 gonzo_alias_to_actual(string_ty *name)
613 {
614     gonzo_ty        *gp;
615     size_t          j;
616     string_ty       *result;
617     string_ty       *s;
618 
619     //
620     // find the project in the gstate list
621     //
622     trace(("gonzo_alias_to_actual(name = \"%s\")\n{\n", name->str_text));
623     result = 0;
624     for (j = 0;; ++j)
625     {
626         gp = gonzo_nth(j);
627         if (!gp)
628             break;
629         s = gonzo_alias_to_actual_sub(gp, name);
630         if (s)
631         {
632             result = s;
633             break;
634         }
635     }
636     trace(("return \"%s\";\n", (result ? result->str_text : "")));
637     trace(("}\n"));
638     return result;
639 }
640 
641 
642 void
gonzo_project_list(string_list_ty * result)643 gonzo_project_list(string_list_ty *result)
644 {
645     size_t          n;
646     size_t          j;
647     gonzo_ty        *gp;
648     gstate_ty       *gstate_data;
649 
650     trace(("gonzo_project_list(result = %p)\n{\n", result));
651     result->clear();
652     for (n = 0;; ++n)
653     {
654         gp = gonzo_nth(n);
655         if (!gp)
656             break;
657 
658         //
659         // read the gstate file
660         //
661         gstate_data = gonzo_gstate_get(gp);
662 
663         //
664         // list the projects
665         //
666         assert(gstate_data->where);
667         for (j = 0; j < gstate_data->where->length; ++j)
668         {
669             gstate_where_ty *addr;
670 
671             addr = gstate_data->where->list[j];
672             if (addr->alias_for)
673                 continue;
674             result->push_back_unique(addr->project_name);
675         }
676     }
677     trace(("}\n"));
678 }
679 
680 
681 void
gonzo_alias_list(string_list_ty * result)682 gonzo_alias_list(string_list_ty *result)
683 {
684     size_t          n;
685     size_t          j;
686     gonzo_ty        *gp;
687     gstate_ty       *gstate_data;
688 
689     trace(("gonzo_project_list(result = %p)\n{\n", result));
690     result->clear();
691     for (n = 0;; ++n)
692     {
693         gp = gonzo_nth(n);
694         if (!gp)
695             break;
696 
697         //
698         // read the gstate file
699         //
700         gstate_data = gonzo_gstate_get(gp);
701 
702         //
703         // list the projects
704         //
705         assert(gstate_data->where);
706         for (j = 0; j < gstate_data->where->length; ++j)
707         {
708             gstate_where_ty *addr;
709 
710             addr = gstate_data->where->list[j];
711             if (!addr->alias_for)
712                 continue;
713             result->push_back_unique(addr->project_name);
714         }
715     }
716     trace(("}\n"));
717 }
718 
719 
720 void
gonzo_project_list_user(const nstring & uname,nstring_list & result)721 gonzo_project_list_user(const nstring &uname, nstring_list &result)
722 {
723     trace(("gonzo_project_list_user(uname = %s, result = @%p)\n{\n",
724         uname.quote_c().c_str(), &result));
725     result.clear();
726     for (size_t n = 0;; ++n)
727     {
728         gonzo_ty *gp = gonzo_nth(n);
729         if (!gp)
730             break;
731 
732         //
733         // check out the ustate
734         //
735         nstring ustate_path =
736             nstring::format("%s/user/%s", gp->dir->str_text, uname.c_str());
737         trace(("ustate_path = %s;\n", ustate_path.quote_c().c_str()));
738         gonzo_become();
739         bool ok = os_exists(ustate_path);
740         gonzo_become_undo();
741         if (!ok)
742             continue;
743 
744         gonzo_become();
745         ustate_ty *ustate_data = ustate_read_file(ustate_path);
746         gonzo_become_undo();
747         if (!ustate_data->own)
748         {
749             ustate_data->own =
750                 (ustate_own_list_ty *)ustate_own_list_type.alloc();
751         }
752 
753         //
754         // collect all projects this user owns changes in
755         //
756         for (size_t j = 0; j < ustate_data->own->length; ++j)
757         {
758             nstring pn(ustate_data->own->list[j]->project_name);
759             trace(("remember %s;\n", pn.quote_c().c_str()));
760             result.push_back_unique(pn);
761         }
762         ustate_type.free(ustate_data);
763     }
764     trace(("found %zd items\n", result.size()));
765     trace(("}\n"));
766 }
767 
768 
769 static int
sort_gstate_cmp(const void * va,const void * vb)770 sort_gstate_cmp(const void *va, const void *vb)
771 {
772     const gstate_where_ty *a = *(const gstate_where_ty **)va;
773     const gstate_where_ty *b = *(const gstate_where_ty **)vb;
774     return strcmp(a->project_name->str_text, b->project_name->str_text);
775 }
776 
777 
778 static void
sort_gstate_where(gonzo_ty * gp)779 sort_gstate_where(gonzo_ty *gp)
780 {
781     assert(gp);
782     gstate_ty *gstate_data = gonzo_gstate_get(gp);
783     assert(gstate_data);
784     assert(gstate_data->where);
785     assert(gstate_data->where->length > 0);
786     qsort
787     (
788         gstate_data->where->list,
789         gstate_data->where->length,
790         sizeof(gstate_data->where->list[0]),
791         sort_gstate_cmp
792     );
793 }
794 
795 
796 void
gonzo_project_add(project * pp)797 gonzo_project_add(project *pp)
798 {
799     trace(("gonzo_project_add(pp = %p)\n{\n", pp));
800     gonzo_ty *gp = gonzo_nth(0);
801     gstate_ty *gstate_data = gonzo_gstate_get(gp);
802     meta_type *type_p = 0;
803     gstate_where_ty **addr_p =
804         (gstate_where_ty **)
805         gstate_where_list_type.list_parse(gstate_data->where, &type_p);
806     assert(type_p == &gstate_where_type);
807     gstate_where_ty *addr = (gstate_where_ty *)gstate_where_type.alloc();
808     *addr_p = addr;
809     trace_pointer(addr);
810     addr->project_name = project_name_get(pp).get_ref_copy();
811     addr->directory = str_copy(pp->home_path_get());
812     gp->modified = 1;
813     sort_gstate_where(gp);
814     trace(("}\n"));
815 }
816 
817 
818 void
gonzo_alias_add(project * pp,string_ty * name)819 gonzo_alias_add(project *pp, string_ty *name)
820 {
821     trace(("gonzo_alias_add(pp = %p)\n{\n", pp));
822     gonzo_ty *gp = gonzo_nth(0);
823     gstate_ty *gstate_data = gonzo_gstate_get(gp);
824     meta_type *type_p = 0;
825     gstate_where_ty **addr_p =
826         (gstate_where_ty **)
827         gstate_where_list_type.list_parse(gstate_data->where, &type_p);
828     assert(type_p == &gstate_where_type);
829     gstate_where_ty *addr = (gstate_where_ty *)gstate_where_type.alloc();
830     *addr_p = addr;
831     trace_pointer(addr);
832     addr->project_name = str_copy(name);
833     addr->alias_for = project_name_get(pp).get_ref_copy();
834     gp->modified = 1;
835     sort_gstate_where(gp);
836     trace(("}\n"));
837 }
838 
839 
840 static int
gonzo_project_delete_sub(gonzo_ty * gp,project * pp)841 gonzo_project_delete_sub(gonzo_ty *gp, project *pp)
842 {
843     gstate_ty       *gstate_data;
844     size_t          j;
845     int             result;
846 
847     //
848     // find the project in the gstate
849     //
850     trace(("gonzo_project_delete_sub(gp = %p, pp = %p)\n{\n",
851         gp, pp));
852     gstate_data = gonzo_gstate_get(gp);
853     assert(gstate_data->where);
854     result = 0;
855     for (j = 0; j < gstate_data->where->length; ++j)
856     {
857         gstate_where_ty *addr;
858         size_t          k;
859 
860         addr = gstate_data->where->list[j];
861         if (addr->alias_for)
862             continue;
863         if (nstring(addr->project_name) != project_name_get(pp))
864             continue;
865 
866         //
867         // delete the item from the list
868         // (keep the list sorted)
869         //
870         for (k = j + 1; k < gstate_data->where->length; ++k)
871             gstate_data->where->list[k - 1] = gstate_data->where->list[k];
872         gstate_data->where->length--;
873 
874         //
875         // free the item
876         //
877         gstate_where_type.free(addr);
878 
879         //
880         // mark this gstate file as modified
881         //
882         gp->modified = 1;
883         result = 1;
884         break;
885     }
886     trace(("return %d;\n", result));
887     trace(("}\n"));
888     return result;
889 }
890 
891 
892 static int
is_leading_prefix(string_ty * s1,string_ty * s2)893 is_leading_prefix(string_ty *s1, string_ty *s2)
894 {
895     if (str_equal(s1, s2))
896         return 1;
897     return
898         (
899             s1->str_length < s2->str_length
900         &&
901             ispunct((unsigned char)s2->str_text[s1->str_length])
902         &&
903             !memcmp(s1->str_text, s2->str_text, s1->str_length)
904         );
905 }
906 
907 
908 static void
gonzo_alias_delete_destination_sub(gonzo_ty * gp,string_ty * name)909 gonzo_alias_delete_destination_sub(gonzo_ty *gp, string_ty *name)
910 {
911     gstate_ty       *gstate_data;
912     size_t          j;
913 
914     //
915     // find the project in the gstate
916     //
917     trace(("gonzo_alias_delete_destination_sub(gp = %p, name = \"%s\")\n{\n",
918            gp, name->str_text));
919     gstate_data = gonzo_gstate_get(gp);
920     assert(gstate_data->where);
921     for (j = 0; j < gstate_data->where->length; ++j)
922     {
923         gstate_where_ty *addr;
924         size_t          k;
925 
926         addr = gstate_data->where->list[j];
927         if (!addr->alias_for)
928             continue;
929         if (!is_leading_prefix(name, addr->alias_for))
930             continue;
931 
932         trace_string (addr->alias_for->str_text);
933         //
934         // delete the item from the list
935         //
936         for (k = j + 1; k < gstate_data->where->length; ++k)
937             gstate_data->where->list[k - 1] = gstate_data->where->list[k];
938         gstate_data->where->length--;
939         --j;
940 
941         //
942         // free the item
943         //
944         gstate_where_type.free(addr);
945 
946         //
947         // mark this gstate file as modified
948         //
949         gp->modified = 1;
950     }
951     trace(("}\n"));
952 }
953 
954 
955 void
gonzo_project_delete(project * pp)956 gonzo_project_delete(project *pp)
957 {
958     gonzo_ty        *gp;
959     long            j;
960 
961     trace(("gonzo_project_delete(pp = %p)\n{\n", pp));
962     for (j = 0;; ++j)
963     {
964         gp = gonzo_nth(j);
965         if (!gp)
966             break;
967         gonzo_alias_delete_destination_sub(gp, project_name_get(pp).get_ref());
968         if (gonzo_project_delete_sub(gp, pp))
969             break;
970     }
971     trace(("}\n"));
972 }
973 
974 
975 static int
gonzo_alias_delete_sub(gonzo_ty * gp,string_ty * name)976 gonzo_alias_delete_sub(gonzo_ty *gp, string_ty *name)
977 {
978     gstate_ty       *gstate_data;
979     size_t          j;
980     int             result;
981 
982     //
983     // find the project in the gstate
984     //
985     trace(("gonzo_alias_delete_sub(gp = %p, name = \"%s\")\n{\n",
986         gp, name->str_text));
987     gstate_data = gonzo_gstate_get(gp);
988     assert(gstate_data->where);
989     result = 0;
990     for (j = 0; j < gstate_data->where->length; ++j)
991     {
992         gstate_where_ty *addr;
993         size_t          k;
994 
995         addr = gstate_data->where->list[j];
996         if (!addr->alias_for)
997             continue;
998         if (!str_equal(addr->project_name, name))
999             continue;
1000 
1001         //
1002         // delete the item from the list
1003         //
1004         for (k = j + 1; k < gstate_data->where->length; ++k)
1005             gstate_data->where->list[k - 1] = gstate_data->where->list[k];
1006         gstate_data->where->length--;
1007 
1008         //
1009         // free the item
1010         //
1011         gstate_where_type.free(addr);
1012 
1013         //
1014         // mark this gstate file as modified
1015         //
1016         gp->modified = 1;
1017         result = 1;
1018         break;
1019     }
1020     trace(("return %d;\n", result));
1021     trace(("}\n"));
1022     return result;
1023 }
1024 
1025 
1026 void
gonzo_alias_delete(string_ty * name)1027 gonzo_alias_delete(string_ty *name)
1028 {
1029     gonzo_ty        *gp;
1030     long            j;
1031 
1032     trace(("gonzo_alias_delete(name = \"%s\")\n{\n", name->str_text));
1033     for (j = 0;; ++j)
1034     {
1035         gp = gonzo_nth(j);
1036         if (!gp)
1037             break;
1038         gonzo_alias_delete_destination_sub(gp, name);
1039         if (gonzo_alias_delete_sub(gp, name))
1040             break;
1041     }
1042     trace(("}\n"));
1043 }
1044 
1045 
1046 static void
waiting_callback(void *)1047 waiting_callback(void *)
1048 {
1049     if (user_ty::create()->lock_wait())
1050         error_intl(0, i18n("waiting for global state lock"));
1051     else
1052         fatal_intl(0, i18n("global state lock not available"));
1053 }
1054 
1055 
1056 void
gonzo_gstate_lock_prepare_new(void)1057 gonzo_gstate_lock_prepare_new(void)
1058 {
1059     trace(("gonzo_gstate_lock_prepare_new()\n{\n"));
1060     lock_prepare_gstate(waiting_callback, 0);
1061     trace(("}\n"));
1062 }
1063 
1064 
1065 string_ty *
gonzo_lockpath_get(void)1066 gonzo_lockpath_get(void)
1067 {
1068     static string_ty *path;
1069     gonzo_ty        *gp;
1070 
1071     trace(("gonzo_lockpath_get()\n{\n"));
1072     if (!path)
1073     {
1074         do_tail();
1075         gp = gonzo[ngonzos - 1];
1076         construct_library_directory(gp);
1077         path = str_format("%s/lockfile", gp->dir->str_text);
1078     }
1079     trace(("return \"%s\";\n", path->str_text));
1080     trace(("}\n"));
1081     return path;
1082 }
1083 
1084 
1085 static int
gonzo_ustate_path_sub(gonzo_ty * gp,string_ty * project_name)1086 gonzo_ustate_path_sub(gonzo_ty *gp, string_ty *project_name)
1087 {
1088     gstate_ty       *gstate_data;
1089     size_t          j;
1090     int             result;
1091 
1092     //
1093     // find the project in the gstate
1094     //
1095     trace(("gonzo_ustate_path_sub(gp = %p)\n{\n", gp));
1096     gstate_data = gonzo_gstate_get(gp);
1097     assert(gstate_data->where);
1098     result = 0;
1099     for (j = 0; j < gstate_data->where->length; ++j)
1100     {
1101         gstate_where_ty *addr;
1102 
1103         addr = gstate_data->where->list[j];
1104         if (addr->alias_for)
1105             continue;
1106         if (str_equal(addr->project_name, project_name))
1107         {
1108             result = 1;
1109             break;
1110         }
1111     }
1112     trace(("return %d;\n", result));
1113     trace(("}\n"));
1114     return result;
1115 }
1116 
1117 
1118 nstring
gonzo_ustate_path(const nstring & project_name,const nstring & login_name)1119 gonzo_ustate_path(const nstring &project_name, const nstring &login_name)
1120 {
1121     //
1122     // find the project in the gstate list
1123     //      the user state file contains an index into the project files
1124     //      and is thus kept in the same directory
1125     //
1126     trace(("gonzo_ustate_path(project_name = %s, login_name = %s)\n{\n",
1127         project_name.quote_c().c_str(), login_name.quote_c().c_str()));
1128     gonzo_ty *gp = 0;
1129     for (size_t j = 0;; ++j)
1130     {
1131         gp = gonzo_nth(j);
1132         if (!gp)
1133         {
1134             fatal_raw("project %s unknown (bug)",
1135                 project_name.quote_c().c_str());
1136         }
1137         if (gonzo_ustate_path_sub(gp, project_name.get_ref()))
1138             break;
1139     }
1140 
1141     //
1142     // make sure that the directory for the user state files exists
1143     //      (must be world writable in testing situations)
1144     //
1145     gonzo_become();
1146     assert(os_exists(gp->dir));
1147     nstring dir = nstring::format("%s/user", gp->dir->str_text);
1148     if (!os_exists(dir))
1149     {
1150         os_mkdir(dir, 0755);
1151         if (gp->temporary)
1152             os_chmod(dir, 0777);
1153     }
1154     gonzo_become_undo();
1155 
1156     //
1157     // build the user state file name
1158     //
1159     nstring result =
1160         nstring::format("%s/user/%s", gp->dir->str_text, login_name.c_str());
1161     trace(("return %s;\n", result.quote_c().c_str()));
1162     trace(("}\n"));
1163     return result;
1164 }
1165 
1166 
1167 void
gonzo_become(void)1168 gonzo_become(void)
1169 {
1170     trace(("gonzo_become()\n{\n"));
1171     gonzo_user()->become_begin();
1172     trace(("}\n"));
1173 }
1174 
1175 
1176 void
gonzo_become_undo(void)1177 gonzo_become_undo(void)
1178 {
1179     trace(("gonzo_become_undo()\n{\n"));
1180     gonzo_user()->become_end();
1181     trace(("}\n"));
1182 }
1183 
1184 
1185 void
gonzo_report_path(string_list_ty * p)1186 gonzo_report_path(string_list_ty *p)
1187 {
1188     long            j;
1189     gonzo_ty        *gp;
1190     string_ty       *s;
1191 
1192     p->clear();
1193     for (j = 0;; ++j)
1194     {
1195         gp = gonzo_nth(j);
1196         if (!gp)
1197             break;
1198         p->push_back_unique(gp->dir);
1199     }
1200 
1201     s = str_from_c(configured_datadir());
1202     p->push_back_unique(s);
1203     str_free(s);
1204 }
1205 
1206 
1207 int
gonzo_alias_acceptable(string_ty * name)1208 gonzo_alias_acceptable(string_ty *name)
1209 {
1210     string_ty *s = str_quote_shell(name);
1211     bool ok = str_equal(s, name);
1212     str_free(s);
1213     return ok;
1214 }
1215 
1216 
1217 // vim: set ts=8 sw=4 et :
1218