source: trunk/athena/bin/discuss/server/core.c @ 3472

Revision 3472, 38.8 KB checked in by srz, 34 years ago (diff)
Added per-meeting Zephyr flag (to be patched for the moment).
Line 
1/*
2 *
3 *      Copyright (C) 1988, 1989 by the Massachusetts Institute of Technology
4 *      Developed by the MIT Student Information Processing Board (SIPB).
5 *      For copying information, see the file mit-copyright.h in this release.
6 *
7 */
8/*
9 *
10 *      $Source: /afs/dev.mit.edu/source/repository/athena/bin/discuss/server/core.c,v $
11 *      $Header: /afs/dev.mit.edu/source/repository/athena/bin/discuss/server/core.c,v 1.30 1990-09-11 19:02:59 srz Exp $
12 *
13 *
14 * core.c --    Routines that are the meat of discuss.  These provide user
15 *              callable routines.
16 *
17 *      $Log: not supported by cvs2svn $
18 * Revision 1.29  90/03/19  13:38:51  srz
19 * Fixed bug where rmds would cause reset connections as a sub-process.
20 *
21 * Revision 1.28  90/02/24  18:58:08  srz
22 * Added signatures to meetings.  This involves adding two routines,
23 * add_trn2 and get_trn_info3, for storing and saving this information.
24 * Signatures are glommed onto the end of the author, so we don't
25 * have to change the meeting format.
26 *
27 * Revision 1.27  89/08/09  22:39:05  srz
28 * Added meeting forwarding.
29 *
30 * Revision 1.26  89/06/03  00:42:11  srz
31 * Added standard copyright notice.
32 *
33 * Revision 1.25  89/01/29  17:17:12  srz
34 * Added flag routines.
35 *
36 * Revision 1.24  89/01/04  23:32:51  raeburn
37 * fixed include paths
38 *
39 * Revision 1.23  88/10/08  01:27:54  srz
40 * Changes for new expunge.
41 *
42 * Revision 1.22  88/09/23  17:15:21  raeburn
43 * Needs internal.h too.
44 *
45 * Revision 1.21  88/09/23  17:05:04  raeburn
46 * Changed type names in accordance with acl.h.
47 *
48 * Revision 1.20  88/06/17  23:12:43  srz
49 * Change update_mtg to return an error if the meeting appears to have
50 * lost transactions.
51 *
52 * Revision 1.19  88/01/05  01:08:44  rfrench
53 * #ifdef'd ZEPHYR stuff
54 *
55 * Revision 1.18  87/10/24  00:46:31  wesommer
56 * Change acl name to "acl" instead of ".ds_acl"
57 * Change default (if no acl file there) to be deny permission.
58 *
59 * Revision 1.17  87/10/23  23:49:15  wesommer
60 * Implemented access control on meeting creation.
61 *
62 * Revision 1.16  87/10/23  21:42:40  wesommer
63 * This isn't Multics.
64 *
65 * Revision 1.15  87/08/22  22:36:28  rfrench
66 * Moved calling location of mtg_znotify -- write_super frees things!
67 *
68 * Revision 1.14  87/08/22  18:12:51  rfrench
69 * Added Zephyr notifications
70 *
71 * Revision 1.13  87/07/16  19:30:46  srz
72 * Changed deleted flag to general flags structure on the server.
73 *
74 * Revision 1.12  87/04/11  23:48:31  spook
75 * Removed unused variable.
76 *
77 * Revision 1.11  87/04/11  00:10:00  srz
78 * Added RCS junk
79 *
80 *
81 */
82#ifndef lint
83static char rcsid_core_c[] =
84    "$Header: /afs/dev.mit.edu/source/repository/athena/bin/discuss/server/core.c,v 1.30 1990-09-11 19:02:59 srz Exp $";
85#endif lint
86
87
88/* Derived from CORE.PAS 06/21/86 by SRZ */
89
90#include <discuss/types.h>
91#include <discuss/dsc_et.h>
92#include <discuss/interface.h>
93#include "mtg.h"
94#include <discuss/tfile.h>
95#include "atom.h"
96#include <discuss/acl.h>
97#include "internal.h"
98#include <errno.h>
99#include <sys/file.h>
100#include <sys/types.h>
101#include <sys/stat.h>
102#include <sys/param.h>
103#include <string.h>
104
105#define min(a, b) (a < b ? a : b)
106#define NULL 0
107extern char *malloc(),*index();
108extern char *new_string();
109extern long time();
110extern off_t lseek();
111
112extern mtg_super super;
113extern char *super_chairman;
114extern char *super_long_name;
115extern char current_mtg [];
116extern int u_trn_f,u_control_f,u_acl_f;
117extern bool nuclear;
118extern afile a_control_f;
119extern char rpc_caller [];
120extern int errno;
121extern int has_privs;
122extern int no_nuke;
123extern tfile abort_file;
124extern dsc_acl *mtg_acl;
125
126
127/*
128 *
129 * add_trn () --
130 * adds a transaction to the given meeting, either as a reply or an
131 * original transaction.  Returns an error code, and the transaction number
132 * given to the transaction
133 *
134 */
135add_trn (mtg_name, source_file, subject, reply_trn, result_trn, result)
136char *mtg_name;
137tfile source_file;
138char *subject;
139trn_nums reply_trn;             /* trn replying to;  0 if original */
140trn_nums *result_trn;           /* trn number given to added trn */
141int *result;
142{
143     add_trn_priv (mtg_name, source_file, subject, NULL, reply_trn, 0,
144                   rpc_caller, (date_times) time ((long *)0), 0,
145                   result_trn, result);
146}
147
148/*
149 *
150 * add_trn2 () --
151 * adds a transaction to the given meeting, either as a reply or an
152 * original transaction.  Returns an error code, and the transaction number
153 * given to the transaction.  Also allows a signature for the author.
154 *
155 */
156add_trn2 (mtg_name, source_file, subject, signature, reply_trn, result_trn, result)
157char *mtg_name;
158tfile source_file;
159char *subject, *signature;
160trn_nums reply_trn;             /* trn replying to;  0 if original */
161trn_nums *result_trn;           /* trn number given to added trn */
162int *result;
163{
164     add_trn_priv (mtg_name, source_file, subject, signature, reply_trn, 0,
165                   rpc_caller, (date_times) time ((long *)0), 0,
166                   result_trn, result);
167}
168
169
170/* add_trn_priv:  For those who know exactly what they want and who they are */
171
172add_trn_priv (mtg_name, source_file, subject, signature, reply_trn, desired_trn, author, date_entered, flags, result_trn, result)
173char *mtg_name;
174tfile source_file;
175char *subject;
176char *signature;
177trn_nums reply_trn;             /* trn replying to;  0 if original */
178trn_nums desired_trn;           /* trn num desired */
179char *author;
180date_times date_entered;
181int flags;
182trn_nums *result_trn;           /* trn number given to added trn */
183int *result;
184{
185     chain_blk reply_cb, cb, spare_cb;
186     int tfs,tocopy,i,len;
187     char buffer[512],*bptr;
188     trn_hdr th;
189     
190
191/*   printf ("add_trn:  mtg %s, subject %s, reply %d\n",
192             mtg_name, subject, reply_trn); */
193     topen (source_file, "r", result);
194     if (*result) return;
195
196     abort_file = source_file;                  /* for abort's sake */
197
198     *result = open_mtg (mtg_name);
199     if (*result) { core_abort (); return; }
200
201     if (reply_trn) {
202          if (!has_mtg_access('a')) {
203               *result = NO_ACCESS;
204               core_abort (); return;
205          }
206     } else {
207          if (!has_mtg_access('w')) {
208               *result = NO_ACCESS;
209               core_abort (); return;
210          }
211     }
212
213     if (!no_nuke) {
214          a_control_f = aopen (u_control_f);
215          nuclear = TRUE;
216     }
217
218     *result = read_super ();
219     if (*result) { core_abort(); return; }
220
221     if (super.date_created == 0) {     /* Meeting has been expunged */
222          write_super();
223
224          if (!no_nuke) {
225               aclose(a_control_f);
226               nuclear = 0;
227          }
228
229          *result = open_mtg (mtg_name);
230          if (*result) { core_abort (); return; }
231
232          if (!no_nuke) {
233               a_control_f = aopen (u_control_f);
234               nuclear = TRUE;
235          }
236
237          *result = read_super ();
238          if (*result) { core_abort(); return; }
239     }   
240
241     /* check reply_trn */
242     if (reply_trn != 0) {
243          *result = read_chain (reply_trn, &reply_cb);
244          if (*result) { core_abort(); return; }
245          if (reply_cb.flags & CB_DELETED) {
246               *result = DELETED_TRN;
247               core_abort (); return;
248          }
249     }
250
251     if (desired_trn == 0 || desired_trn <= super.highest)
252          super.highest++;
253     else
254          super.highest = desired_trn;
255     
256     /* Initialize chain block */
257     cb.version = CHAIN_BLK_1;
258     cb.unique = CHAIN_BLK_UNIQUE;
259     cb.current = super.highest;
260     cb.prev = super.last;
261     cb.next = 0;
262     cb.nref = 0;
263     cb.chain_fref = 0;
264     cb.chain_lref = 0;
265     cb.flags = flags & ~CB_DELETED;
266     cb.filler = 0;
267     cb.trn_addr = fsize (u_trn_f);
268
269     if (reply_trn != 0) {                              /* info from pref */
270          cb.trn_chain = reply_cb.trn_chain;
271          read_chain (cb.trn_chain, &spare_cb);
272          cb.pref = spare_cb.chain_lref;
273     } else {
274          cb.pref = 0;                                  /* this is fref */
275          cb.trn_chain = ++super.highest_chain;
276     }
277
278     if (write_chain (&cb) != 0)                        /* write it out */
279          goto werror;
280
281     /* update fref & lref of chain */
282     read_chain (cb.trn_chain, &spare_cb);
283     spare_cb.chain_lref = cb.current;                  /* update lref */
284     if (cb.pref == 0)
285          spare_cb.chain_fref = cb.current;
286
287     if (write_chain (&spare_cb) != 0)
288          goto werror;
289
290     /* update nref of pref */
291     if (reply_trn != 0) {
292          read_chain (cb.pref, &spare_cb);
293          spare_cb.nref = cb.current;
294          write_chain (&spare_cb);
295     }
296
297     /* update next of prev */
298     if (cb.prev != 0) {
299          read_chain (cb.prev, &spare_cb);
300          spare_cb.next = cb.current;
301          write_chain (&spare_cb);
302     }
303
304     /* and finish up the super block info */
305     super.last = cb.current;
306     if (super.first == 0)
307          super.first = cb.current;
308     super.date_modified = date_entered;
309     super.high_water += sizeof (chain_blk);
310
311     if (signature != NULL && (*signature == '\0' || !strcmp(author, signature)))
312          signature = NULL;                     /* Signature is empty */
313
314     /* now write out the transaction to the trn file */
315     th.version = TRN_HDR_1;
316     th.unique = TRN_HDR_UNIQUE;
317     th.current = cb.current;
318     th.orig_pref = cb.pref;
319     th.date_entered = super.date_modified;
320     th.num_lines = 0;                  /* count these later */
321     th.num_chars = tfsize (source_file);
322     th.prev_trn = super.highest_trn_addr;
323     super.highest_trn_addr = cb.trn_addr;
324     th.subject_len = strlen (subject) + 1;
325     th.author_len = strlen (author) + 1;
326     if (signature != NULL)
327          th.author_len += strlen (signature) + 1;
328     th.subject_addr = cb.trn_addr + sizeof(trn_hdr);
329     th.author_addr = th.subject_addr + th.subject_len;
330     th.text_addr = th.author_addr + th.author_len;
331
332     lseek (u_trn_f, (long)0, 2);
333     if (write (u_trn_f, (char *) &th, sizeof (th)) != sizeof (th)) goto werror;
334
335     if (write (u_trn_f, subject, th.subject_len) != th.subject_len) goto werror;
336     if (signature == NULL) {
337          if (write (u_trn_f, author, th.author_len) != th.author_len) goto werror;
338     } else {
339          len = strlen(author)+1;
340          if (write (u_trn_f, author, len) != len) goto werror;
341          len = th.author_len - len;
342          if (write (u_trn_f, signature, len) != len) goto werror;
343     }
344
345     /* copy transaction from source_file, counting NL's. */
346     tfs = th.num_chars;
347     while (tfs > 0) {
348          tocopy = min (512, tfs);
349          tocopy = tread (source_file, buffer, tocopy, result);
350          if (*result) { core_abort (); return; }
351          for (bptr = buffer, i = 0; i < tocopy; bptr++,i++)
352               if (*bptr == '\n')
353                    th.num_lines++;
354          if (write (u_trn_f, buffer, tocopy) != tocopy) goto werror;
355          tfs -= tocopy;
356     }
357
358     tclose(source_file,result);
359     abort_file = NULL;
360
361     lseek(u_trn_f, (long)(cb.trn_addr), 0);
362     if (write (u_trn_f, (char *) &th, sizeof (trn_hdr)) != sizeof (trn_hdr)) goto werror;      /* update num_lines */
363
364     super.trn_fsize = fsize (u_trn_f);
365
366#ifdef ZEPHYR
367
368     /* Send this out...we want to do this BEFORE calling write_super
369      * because things get freed...
370      */
371     if (!(super.flags & MTG_NOZEPHYR))
372          mtg_znotify(mtg_name, subject, author, signature);
373#endif ZEPHYR
374     
375     /* all done, start winding down */
376     write_super();
377
378     if (!no_nuke) {
379          fsync(u_trn_f);
380          aclose(a_control_f);
381          nuclear = 0;
382     }
383
384     *result = 0;
385     *result_trn = cb.current;
386     return;
387
388werror:
389     core_abort();
390     *result = NO_WRITE;
391     return;
392}
393/*
394 *
395 * expunge_trn () -- Entry to mark a given transaction as expunged.
396 *                   This makes a kosher chain_blk, except there's
397 *                   no transaction info associated with this.
398 *
399 */
400expunge_trn(mtg_name, desired_trn, result)
401char *mtg_name;
402trn_nums desired_trn;
403int *result;
404{
405     chain_blk cb;
406
407     *result = open_mtg (mtg_name);
408     if (*result) { core_abort (); return; }
409
410     if (!has_mtg_access('c')) {
411          *result = NO_ACCESS;
412          core_abort (); return;
413     }
414
415     if (!no_nuke) {
416          a_control_f = aopen (u_control_f);
417          nuclear = TRUE;
418     }
419
420     *result = read_super ();
421     if (*result) { core_abort(); return; }
422
423     if (desired_trn == 0 || desired_trn <= super.highest)
424          super.highest++;
425     else
426          super.highest = desired_trn;
427     
428     /* Initialize chain block */
429     cb.version = CHAIN_BLK_1;
430     cb.unique = CHAIN_BLK_UNIQUE;
431     cb.current = super.highest;
432     cb.prev = 0;
433     cb.next = 0;
434     cb.nref = 0;
435     cb.chain_fref = 0;
436     cb.chain_lref = 0;
437     cb.flags |= CB_DELETED;
438     cb.filler = 0;
439     cb.trn_addr = 0;
440
441     if (write_chain (&cb) != 0)                        /* write it out */
442          goto werror;
443
444     super.date_modified = time(0);
445     super.high_water += sizeof (chain_blk);
446
447     write_super();
448
449     if (!no_nuke) {
450          aclose(a_control_f);
451          nuclear = 0;
452     }
453
454     *result = 0;
455     return;
456
457werror:
458     core_abort();
459     *result = NO_WRITE;
460     return;
461}
462
463/*
464 *
465 * get_trn_info () --
466 * returns information about the given transaction in info, with an error
467 * code as its return argument
468 *
469 */
470get_trn_info (mtg_name, trn, info, result)
471char *mtg_name;
472trn_nums trn;
473trn_info *info;
474int *result;
475{
476     chain_blk cb,spare_cb;
477     trn_hdr th;
478     char *th_subject,*th_author;
479
480/*   printf ("get_trn_info: mtg %s, trn %d\n",
481             mtg_name, trn);*/
482
483     /* safety -- set info up right */
484     info -> version = 0;
485     info -> current = 0;
486     info -> prev = 0;
487     info -> next = 0;
488     info -> pref = 0;
489     info -> nref = 0;
490     info -> fref = 0;
491     info -> lref = 0;
492     info -> chain_index = 0;
493     info -> date_entered = 0;
494     info -> num_lines = 0;
495     info -> num_chars = 0;
496     info -> subject = new_string ("");
497     info -> author = new_string ("");
498
499
500     *result = open_mtg (mtg_name);
501     if (*result) return;
502
503     start_read();                              /* starting to read */
504
505     *result = read_super ();
506     if (*result) { core_abort(); return; }
507
508     *result = read_chain (trn, &cb);
509     if (*result) { core_abort(); return; }
510
511     if (cb.trn_addr == 0) {
512          *result = DELETED_TRN;
513          core_abort();
514          return;
515     }
516
517     *result = read_chain (cb.trn_chain, &spare_cb);
518     if (*result) { core_abort(); return; }
519
520     *result = read_trn (cb.trn_addr, &th, &th_subject, &th_author, NULL);
521     if (*result) { core_abort(); return; }
522
523     finish_read();
524
525     if (!has_trn_access(th_author, 'r')) {
526          *result = NO_ACCESS;
527          goto null_info;
528     }
529
530     if ((cb.flags & CB_DELETED) && !has_trn_access(th_author, 'd')) {
531          *result = DELETED_TRN;
532          goto null_info;
533     }
534
535     info -> version = 1;
536     info -> current = cb.current;
537     info -> prev = cb.prev;
538     info -> next = cb.next;
539     info -> pref = cb.pref;
540     info -> nref = cb.nref;
541     info -> fref = spare_cb.chain_fref;
542     info -> lref = spare_cb.chain_lref;
543     info -> chain_index = cb.trn_chain;
544
545     info -> date_entered = th.date_entered;
546     info -> num_lines = th.num_lines;
547     info -> num_chars = th.num_chars;
548     free (info -> subject);
549     info -> subject = th_subject;
550     free (info -> author);
551     info -> author = th_author;
552
553     forget_super();
554
555     if (cb.flags & CB_DELETED)
556          *result = DELETED_TRN;
557     else
558          *result = 0;
559null_info:
560     return;
561}
562
563/*
564 *
565 * get_trn_info2 () --
566 * returns information about the given transaction in info, with an error
567 * code as its return argument.  This call returns expanded information,
568 * such as the flags.
569 *
570 */
571get_trn_info2 (mtg_name, trn, info, result)
572char *mtg_name;
573trn_nums trn;
574trn_info2 *info;
575int *result;
576{
577     chain_blk cb,spare_cb;
578     trn_hdr th;
579     char *th_subject,*th_author;
580
581/*   printf ("get_trn_info: mtg %s, trn %d\n",
582             mtg_name, trn);*/
583
584     /* safety -- set info up right */
585     info -> version = 0;
586     info -> current = 0;
587     info -> prev = 0;
588     info -> next = 0;
589     info -> pref = 0;
590     info -> nref = 0;
591     info -> fref = 0;
592     info -> lref = 0;
593     info -> chain_index = 0;
594     info -> date_entered = 0;
595     info -> num_lines = 0;
596     info -> num_chars = 0;
597     info -> subject = new_string ("");
598     info -> author = new_string ("");
599     info -> flags = 0;
600
601     *result = open_mtg (mtg_name);
602     if (*result) return;
603
604     start_read();                              /* starting to read */
605
606     *result = read_super ();
607     if (*result) { core_abort(); return; }
608
609     *result = read_chain (trn, &cb);
610     if (*result) { core_abort(); return; }
611
612     if (cb.trn_addr == 0) {
613          *result = DELETED_TRN;
614          core_abort();
615          return;
616     }
617
618     *result = read_chain (cb.trn_chain, &spare_cb);
619     if (*result) { core_abort(); return; }
620
621     *result = read_trn (cb.trn_addr, &th, &th_subject, &th_author, NULL);
622     if (*result) { core_abort(); return; }
623
624     finish_read();
625
626     if (!has_trn_access(th_author, 'r')) {
627          *result = NO_ACCESS;
628          goto null_info;
629     }
630
631     if ((cb.flags & CB_DELETED) && !has_trn_access(th_author, 'd')) {
632          *result = DELETED_TRN;
633          goto null_info;
634     }
635
636     info -> version = 1;
637     info -> current = cb.current;
638     info -> prev = cb.prev;
639     info -> next = cb.next;
640     info -> pref = cb.pref;
641     info -> nref = cb.nref;
642     info -> fref = spare_cb.chain_fref;
643     info -> lref = spare_cb.chain_lref;
644     info -> chain_index = cb.trn_chain;
645
646     info -> date_entered = th.date_entered;
647     info -> num_lines = th.num_lines;
648     info -> num_chars = th.num_chars;
649     free (info -> subject);
650     info -> subject = th_subject;
651     free (info -> author);
652     info -> author = th_author;
653     info -> flags = cb.flags;
654
655     forget_super();
656
657     if (cb.flags & CB_DELETED)
658          *result = DELETED_TRN;
659     else
660          *result = 0;
661null_info:
662     return;
663}
664
665/*
666 *
667 * get_trn_info3 () --
668 * returns information about the given transaction in info, with an error
669 * code as its return argument.  This call returns expanded information,
670 * such as the flags and signature.
671 *
672 */
673get_trn_info3 (mtg_name, trn, info, result)
674char *mtg_name;
675trn_nums trn;
676trn_info3 *info;
677int *result;
678{
679     chain_blk cb,spare_cb;
680     trn_hdr th;
681     char *th_subject,*th_author, *th_signature;
682
683/*   printf ("get_trn_info: mtg %s, trn %d\n",
684             mtg_name, trn);*/
685
686     /* safety -- set info up right */
687     info -> version = 0;
688     info -> current = 0;
689     info -> prev = 0;
690     info -> next = 0;
691     info -> pref = 0;
692     info -> nref = 0;
693     info -> fref = 0;
694     info -> lref = 0;
695     info -> chain_index = 0;
696     info -> date_entered = 0;
697     info -> num_lines = 0;
698     info -> num_chars = 0;
699     info -> subject = new_string ("");
700     info -> author = new_string ("");
701     info -> signature = new_string ("");
702     info -> flags = 0;
703
704     *result = open_mtg (mtg_name);
705     if (*result) return;
706
707     start_read();                              /* starting to read */
708
709     *result = read_super ();
710     if (*result) { core_abort(); return; }
711
712     *result = read_chain (trn, &cb);
713     if (*result) { core_abort(); return; }
714
715     if (cb.trn_addr == 0) {
716          *result = DELETED_TRN;
717          core_abort();
718          return;
719     }
720
721     *result = read_chain (cb.trn_chain, &spare_cb);
722     if (*result) { core_abort(); return; }
723
724     *result = read_trn (cb.trn_addr, &th, &th_subject, &th_author, &th_signature);
725     if (*result) { core_abort(); return; }
726
727     finish_read();
728
729     if (!has_trn_access(th_author, 'r')) {
730          *result = NO_ACCESS;
731          goto null_info;
732     }
733
734     if ((cb.flags & CB_DELETED) && !has_trn_access(th_author, 'd')) {
735          *result = DELETED_TRN;
736          goto null_info;
737     }
738
739     info -> version = 1;
740     info -> current = cb.current;
741     info -> prev = cb.prev;
742     info -> next = cb.next;
743     info -> pref = cb.pref;
744     info -> nref = cb.nref;
745     info -> fref = spare_cb.chain_fref;
746     info -> lref = spare_cb.chain_lref;
747     info -> chain_index = cb.trn_chain;
748
749     info -> date_entered = th.date_entered;
750     info -> num_lines = th.num_lines;
751     info -> num_chars = th.num_chars;
752     free (info -> subject);
753     info -> subject = th_subject;
754     free (info -> author);
755     info -> author = th_author;
756     free (info -> signature);
757     info -> signature = th_signature;
758     info -> flags = cb.flags;
759
760     forget_super();
761
762     if (cb.flags & CB_DELETED)
763          *result = DELETED_TRN;
764     else
765          *result = 0;
766null_info:
767     return;
768}
769
770
771/*
772 *
773 * set_trn_flags () -- Routine to set the flags (except DELETED) on a
774 *                     given transaction.
775 *
776 */
777set_trn_flags (mtg_name, trn, flags, result)
778char *mtg_name;
779trn_nums trn;
780short flags;
781int *result;
782{
783     chain_blk cb;
784     trn_hdr th;
785     char *th_author;
786
787     *result = open_mtg (mtg_name);
788     if (*result) return;
789
790     if (!no_nuke) {
791          a_control_f = aopen (u_control_f);
792          nuclear = TRUE;
793     }
794
795     *result = read_super ();
796     if (*result) { core_abort(); return; }
797
798     *result = read_chain (trn, &cb);
799     if (*result) { core_abort(); return; }
800
801     if ((cb.flags & CB_DELETED) || cb.trn_addr == 0) {
802          *result = DELETED_TRN;
803          core_abort (); return;
804     }
805
806     *result = read_trn (cb.trn_addr, &th, (char **)0, &th_author, NULL);
807     if (*result) { core_abort(); return; }
808
809     if (!has_trn_access(th_author,'d')) {
810          *result = NO_ACCESS;
811          free(th_author);
812          core_abort(); return;
813     }
814
815     free(th_author);
816
817     cb.flags = (cb.flags & CB_DELETED) | (flags & ~CB_DELETED);
818     write_chain (&cb);
819
820     forget_super();
821
822     if (!no_nuke) {
823          aclose (a_control_f);
824          nuclear = FALSE;
825     }
826
827     *result = 0;
828     return;
829}
830
831
832
833/*
834 *
835 * delete_trn () --
836 * deletes the given transaction from the current meeting.  Returns an
837 * error code
838 *
839 */
840delete_trn (mtg_name, trn, result)
841char *mtg_name;
842trn_nums trn;
843int *result;
844{
845     chain_blk cb, spare_cb;
846     char *th_author;
847     trn_hdr th;
848
849/*   printf ("delete_trn: mtg %s, trn %d\n",
850             mtg_name, trn);*/
851
852     *result = open_mtg (mtg_name);
853     if (*result) return;
854
855     if (!no_nuke) {
856          a_control_f = aopen (u_control_f);
857          nuclear = TRUE;
858     }
859
860     *result = read_super ();
861     if (*result) { core_abort(); return; }
862
863     *result = read_chain (trn, &cb);
864     if (*result) { core_abort(); return; }
865
866     if ((cb.flags & CB_DELETED) || cb.trn_addr == 0) {
867          *result = DELETED_TRN;
868          core_abort (); return;
869     }
870
871     *result = read_trn (cb.trn_addr, &th, (char **)0, &th_author, NULL);
872     if (*result) { core_abort(); return; }
873
874     if (!has_trn_access(th_author,'d')) {
875          *result = NO_ACCESS;
876          free(th_author);
877          core_abort(); return;
878     }
879
880     free(th_author);
881     
882     cb.flags |= CB_DELETED;
883     write_chain (&cb);
884
885     /* update next of prev */
886     if (cb.prev != 0) {
887          *result = read_chain (cb.prev, &spare_cb);
888          if (*result) { core_abort(); return; }
889
890          spare_cb.next = cb.next;
891          write_chain (&spare_cb);
892     }
893
894     /* update prev of next */
895     if (cb.next != 0) {
896          *result = read_chain (cb.next, &spare_cb);
897          if (*result) { core_abort(); return; }
898
899          spare_cb.prev = cb.prev;
900          write_chain (&spare_cb);
901     }
902
903     /* update nref of pref */
904     if (cb.pref != 0) {
905          *result = read_chain (cb.pref, &spare_cb);
906          if (*result) { core_abort(); return; }
907
908          spare_cb.nref = cb.nref;
909          write_chain (&spare_cb);
910     }
911
912     /* update pref of nref */
913     if (cb.nref != 0) {
914          *result = read_chain (cb.nref, &spare_cb);
915          if (*result) { core_abort(); return; }
916
917          spare_cb.pref = cb.pref;
918          write_chain (&spare_cb);
919     }
920
921     /* and update fref & lref of chain */
922     if (cb.nref == 0 || cb.pref == 0) {
923          *result = read_chain (cb.trn_chain, &spare_cb);
924          if (*result) { core_abort(); return; }
925
926          if (cb.nref == 0)
927               spare_cb.chain_lref = cb.pref;
928          if (cb.pref == 0)
929               spare_cb.chain_fref = cb.nref;
930          write_chain (&spare_cb);
931     }
932
933     /* and update, first and last of meeting */
934     if (cb.prev == 0)
935          super.first = cb.next;
936     if (cb.next == 0)
937          super.last = cb.prev;
938
939     write_super ();
940     if (!no_nuke) {
941          aclose (a_control_f);
942          nuclear = FALSE;
943     }
944
945     *result = 0;
946     return;
947}
948
949/*
950 *
951 * retrieve_trn () --
952 * retrieves a previously deleted transaction from the current meeting, if
953 * possible.  trn must refer to a deleted transaction.  An error code is
954 * returned
955 *
956 */
957retrieve_trn (mtg_name, trn, result)
958char *mtg_name;
959trn_nums trn;
960int *result;
961{
962     chain_blk cb, spare_cb, chain_cb;
963     trn_hdr th;
964     char *th_author;
965
966
967/*   printf ("retrieve_trn: mtg %s, trn %d\n",
968             mtg_name, trn);*/
969
970
971     *result = open_mtg (mtg_name);
972     if (*result) return;
973
974     if (!no_nuke) {
975          a_control_f = aopen (u_control_f);
976          nuclear = TRUE;
977     }
978
979     *result = read_super ();
980     if (*result) { core_abort(); return; }
981
982     *result = read_chain (trn, &cb);
983     if (*result) { core_abort(); return; }
984
985     if (!(cb.flags & CB_DELETED)) {
986          *result = TRN_NOT_DELETED;
987          core_abort (); return;
988     }
989
990     if (cb.trn_addr == 0) {
991          *result = EXPUNGED_TRN;
992          core_abort (); return;
993     }
994
995     /* for paranoia, read transaction */
996     *result = read_trn (cb.trn_addr, &th, (char **)0, &th_author, NULL);
997     if (*result) { core_abort(); return; }
998
999     if (!has_trn_access(th_author,'d')) {
1000          *result = NO_ACCESS;
1001          free(th_author);
1002          core_abort(); return;
1003     }
1004
1005     free(th_author);
1006
1007     /* now retrieving a transaction is hairier than deleting it, since
1008        the previous and next, pref and nref could also have been deleted
1009        since.  Also, intermediate ones could have been retrieved in the
1010        interim.  So we go to our reference points (fref & lref), and
1011        start from there.  There are three cases -- we are the new fref,
1012        we are the new lref, or we are in the middle.  For prev & next,
1013        we just start decrementing and incrementing until we get a
1014        non-deleted transaction */
1015     
1016     *result = read_chain (cb.trn_chain, &chain_cb);
1017     if (*result) { core_abort(); return; }
1018
1019     if (chain_cb.chain_fref > cb.current || chain_cb.chain_fref == 0) /* we are fref */
1020          cb.pref = 0;
1021     else
1022          cb.pref = chain_cb.chain_fref;
1023
1024     if (chain_cb.chain_lref < cb.current)
1025          cb.nref = 0;
1026     else
1027          cb.nref = chain_cb.chain_lref;
1028
1029     /* advance until we get past us */
1030     while (cb.pref != 0) {
1031          *result = read_chain (cb.pref, &spare_cb);
1032          if (*result) { core_abort(); return; }
1033
1034          if (spare_cb.nref > cb.current || spare_cb.nref == 0)
1035               break;
1036          cb.pref = spare_cb.nref;
1037     }
1038
1039     while (cb.nref != 0) {
1040          *result = read_chain (cb.nref, &spare_cb);
1041          if (*result) { core_abort(); return; }
1042
1043          if (spare_cb.pref < cb.current)
1044               break;
1045          cb.nref = spare_cb.pref;
1046     }
1047
1048     if (super.first > cb.current || super.first == 0) /* we are first */
1049          cb.prev = 0;
1050     else
1051          cb.prev = cb.current - 1;
1052
1053     if (super.last < cb.current)
1054          cb.next = 0;
1055     else
1056          cb.next = cb.current + 1;
1057
1058     while (cb.prev != 0) {
1059          *result = read_chain (cb.prev, &spare_cb);
1060          if (*result) { core_abort(); return; }
1061
1062          if (!(spare_cb.flags & CB_DELETED))
1063               break;
1064          cb.prev--;
1065     }
1066     while (cb.next != 0) {
1067          *result = read_chain (cb.next, &spare_cb);
1068          if (*result) { core_abort(); return; }
1069
1070          if (!(spare_cb.flags & CB_DELETED))
1071               break;
1072          cb.next++;
1073     }
1074
1075     /* invariant -- current_block is all set (except for deleted) */
1076     cb.flags &= ~(CB_DELETED);
1077     write_chain (&cb);
1078
1079     /* update next of prev */
1080     if (cb.prev != 0) {
1081          *result = read_chain (cb.prev, &spare_cb);
1082          if (*result) { core_abort(); return; }
1083
1084          spare_cb.next = cb.current;
1085          write_chain (&spare_cb);
1086     }
1087
1088     /* update prev of next */
1089     if (cb.next != 0) {
1090          *result = read_chain (cb.next, &spare_cb);
1091          if (*result) { core_abort(); return; }
1092
1093          spare_cb.prev = cb.current;
1094          write_chain (&spare_cb);
1095     }
1096
1097     /* update nref of pref */
1098     if (cb.pref != 0) {
1099          *result = read_chain (cb.pref, &spare_cb);
1100          if (*result) { core_abort(); return; }
1101
1102          spare_cb.nref = cb.current;
1103          write_chain (&spare_cb);
1104     }
1105
1106     /* update pref of nref */
1107     if (cb.nref != 0) {
1108          *result = read_chain (cb.nref, &spare_cb);
1109          if (*result) { core_abort(); return; }
1110
1111          spare_cb.pref = cb.current;
1112          write_chain (&spare_cb);
1113     }
1114
1115     /* and update fref & lref of chain */
1116     if (cb.nref == 0 || cb.pref == 0) {
1117          *result = read_chain (cb.trn_chain, &spare_cb);
1118          if (*result) { core_abort(); return; }
1119
1120          if (cb.nref == 0)
1121               spare_cb.chain_lref = cb.current;
1122          if (cb.pref == 0)
1123               spare_cb.chain_fref = cb.current;
1124          write_chain (&spare_cb);
1125     }
1126
1127     /* and update, first and last of meeting */
1128     if (cb.prev == 0)
1129          super.first = cb.current;
1130     if (cb.next == 0)
1131          super.last = cb.current;
1132
1133     write_super ();
1134     if (!no_nuke) {
1135          aclose (a_control_f);
1136          nuclear = FALSE;
1137     }
1138
1139     *result = 0;
1140     return;
1141}
1142
1143
1144
1145/*
1146 *
1147 * create_mtg () --
1148 * Creates a new meeting with the given long_mtg name, where location is the
1149 * it's place in the hierarchy, and the long_mtg_name is its canonical name.
1150 * The chairman of the new meeting is the current user.
1151 *
1152 */
1153create_mtg (location, long_mtg_name, public, result)
1154char *location,*long_mtg_name;
1155bool public;
1156int *result;
1157{
1158     create_mtg_priv (location, long_mtg_name, public, (date_times) time ((long *)0), rpc_caller, NULL, result);
1159}
1160
1161/* create_mtg_priv -- for people who know the chairman and date_created */
1162create_mtg_priv (location, long_mtg_name, public, date_created, chairman, new_acl, result)
1163char *location,*long_mtg_name,*chairman;
1164bool public;
1165date_times date_created;
1166dsc_acl *new_acl;
1167int *result;
1168{
1169     char str[256];
1170     trn_base tb;
1171     int loclen;
1172
1173/*   printf("create_mtg: long mtg %s, location %s, public %d\n",
1174            long_mtg_name, location, public);*/
1175
1176     loclen = strlen (location);
1177     if (location[0] != '/' || loclen == 0 || loclen >= MAXPATHLEN || location [loclen-1] == '/') {
1178          *result = BAD_PATH;
1179          return;
1180     }
1181
1182     if (long_mtg_name [0] == '\0') {
1183          *result = BAD_MTG_NAME;
1184          return;
1185     }
1186
1187     /* First, create meeting directory */
1188     umask (077);                               /* Set access for sure */
1189     if (mkdir (location, 0700) < 0) {          /* rwx------ */
1190          if (errno == EEXIST)
1191               *result = DUP_MTG_NAME;
1192          else
1193               *result = BAD_PATH;
1194          return;
1195     }
1196
1197     /*
1198      * Then see if we should have access to build it..  Yes, this is
1199      * a crock, but UNIX doesn't have an easy way to
1200      * canonicalize a pathname
1201      */
1202
1203     *result = 0;
1204     
1205     if (!has_privs) {
1206          int aclfd;
1207          strcpy (str, location);
1208          strcat (str, "/../acl");
1209
1210          if ((aclfd = open(str, O_RDONLY, 0700)) < 0) {
1211               *result = NO_ACCESS;
1212          } else {
1213               dsc_acl *tmp_acl = acl_read(aclfd);
1214               (void) close(aclfd);
1215
1216               if (tmp_acl == NULL ||
1217                   !acl_check(tmp_acl, rpc_caller, "a"))
1218                       *result = NO_ACCESS;
1219               (void) acl_destroy(tmp_acl);
1220          }
1221          if (*result) {
1222               (void) rmdir(location); /* we don't care if this fails; */
1223                                       /* we can't do anything about it */
1224               return;
1225          }
1226     }
1227     
1228     strcpy (str, location);
1229     strcat (str, "/control");
1230
1231     if ((u_control_f = open(str, O_RDWR | O_CREAT | O_EXCL, 0700)) < 0) {
1232          if (errno == EEXIST)
1233               *result = DUP_MTG_NAME;
1234          else if (errno == EACCES)
1235               *result = NO_ACCESS;
1236          else
1237               *result = BAD_PATH;
1238          return;
1239     }
1240     
1241     strcpy (str, location);
1242     strcat (str, "/transactions");
1243     
1244     if ((u_trn_f = open(str, O_RDWR | O_CREAT | O_EXCL, 0700)) < 0) {
1245          if (errno == EEXIST)
1246               *result = DUP_MTG_NAME;
1247          else if (errno == EACCES)
1248               *result = NO_ACCESS;
1249          else
1250               *result = BAD_PATH;
1251          close (u_control_f);
1252          return;
1253     }
1254
1255     /* Initialize super-block */
1256     super.version = MTG_SUPER_1;
1257     super.unique = MTG_SUPER_UNIQUE;
1258     super.first = 0;
1259     super.last = 0;
1260     super.lowest = 1;
1261     super.highest = 0;
1262     super.highest_chain = 0;
1263     super.date_created = super.date_modified = date_created;
1264     super.long_name_addr = 0;
1265     super.chairman_addr = 0;
1266     super.long_name_len = 0;
1267     super.chairman_len = 0;
1268
1269     super_long_name = new_string (long_mtg_name);
1270     super_chairman = new_string (chairman);
1271     super.flags = public ? MTG_PUBLIC : 0;
1272     super.chain_start = 1024;
1273     super.high_water = super.chain_start;
1274     super.trn_fsize = 0;
1275     super.highest_trn_addr = 0;
1276
1277     /* initialize trn_base */
1278     tb.version = TRN_BASE_1;
1279     tb.unique = TRN_BASE_UNIQUE;
1280     tb.date_created = super.date_created;
1281     tb.public_flag = public;
1282
1283     /* calculate address & lens of variable length fields */
1284     tb.long_name_addr = sizeof (tb);
1285     tb.long_name_len = strlen (super_long_name) + 1;
1286     tb.chairman_addr = tb.long_name_addr + tb.long_name_len;
1287     tb.chairman_len = strlen (super_chairman) + 1;
1288     write (u_trn_f, (char *) &tb, sizeof (tb));        /* trn base */
1289     write (u_trn_f, super_long_name, tb.long_name_len);
1290     write (u_trn_f, super_chairman, tb.chairman_len);
1291
1292     super.trn_fsize = sizeof (tb) + tb.long_name_len + tb.chairman_len;
1293
1294     write_super();
1295     
1296     strcpy (current_mtg, location);                    /* it's legal */
1297     if (mtg_acl != NULL)
1298          acl_destroy(mtg_acl);
1299     if (new_acl == NULL) {
1300          mtg_acl = acl_create ();
1301          acl_add_access(mtg_acl, chairman, "acdorsw"); /* add chairman */
1302          if (public == 1)
1303               acl_add_access(mtg_acl, "*", "a  orsw"); /* public mtg */
1304     } else
1305          mtg_acl = acl_copy(new_acl);
1306
1307
1308     strcpy (str, location);
1309     strcat (str, "/acl");
1310
1311     if ((u_acl_f = open (str, O_RDWR | O_CREAT | O_EXCL, 0700)) < 0) {
1312          *result = BAD_PATH;
1313          return;
1314     }
1315     acl_write (u_acl_f, mtg_acl);
1316     close (u_acl_f);
1317
1318     *result = 0;
1319     return;
1320}
1321
1322
1323/*
1324 *
1325 * get_mtg_info () --
1326 * returns information about the given meeting.  Return argument is an
1327 * error code
1328 *
1329 */
1330get_mtg_info (mtg_name, info, result)
1331char *mtg_name;
1332mtg_info *info;
1333int *result;
1334{
1335/*   printf ("get_mtg_info: mtg %s\n",
1336             mtg_name);*/
1337
1338     /* safety -- set strings up right */
1339     info -> chairman = new_string ("");
1340     info -> long_name = new_string ("");
1341     info -> location = new_string (mtg_name);
1342     info -> access_modes = new_string ("");
1343     info -> public_flag = TRUE;
1344
1345     *result = open_mtg (mtg_name);
1346     if (*result) {
1347          if (*result == MTG_MOVED) {
1348               char buf[100];
1349               int mf;
1350               char *cp;
1351
1352               strcpy(buf, mtg_name);
1353               strcat(buf, "/forward");
1354               if ((mf = open(buf, O_RDONLY, 0700)) < 0) {
1355                    *result = INCONSISTENT;
1356                    return;
1357               }
1358               if (read(mf, buf, 100) < 0) {
1359                    *result = INCONSISTENT;
1360                    close(mf);
1361                    return;
1362               }
1363               close(mf);
1364               cp = index(buf, '\n');
1365               if (cp == NULL) {
1366                    *result = INCONSISTENT;
1367                    return;
1368               }
1369               *cp = '\0';
1370               cp = index(buf, ':');
1371               if (cp == NULL) {
1372                    *result = INCONSISTENT;
1373                    return;
1374               }
1375               *cp++ = '\0';
1376               free(info -> long_name);
1377               info -> long_name = new_string (buf);
1378               free(info -> location);
1379               info -> location = new_string (cp);
1380          }
1381          return;
1382     }
1383
1384     free(info -> access_modes);
1385     info -> access_modes = new_string (acl_get_access(mtg_acl, rpc_caller));
1386
1387     if (!has_mtg_access('s') && !has_mtg_access('r')) {
1388          *result = NO_ACCESS;
1389          return;
1390     }
1391
1392     start_read();                              /* starting to read */
1393
1394     *result = read_super ();
1395     if (*result) { core_abort(); return; }
1396
1397     finish_read();
1398
1399     info -> version = 2;
1400     free(info -> long_name);
1401     free(info -> chairman);
1402
1403     info -> long_name = new_string (super_long_name);
1404     info -> chairman = new_string (super_chairman);
1405     info -> first = super.first;
1406     info -> last = super.last;
1407     info -> lowest = super.lowest;
1408     info -> highest = super.highest;
1409     info -> date_created = super.date_created;
1410     info -> date_modified = super.date_modified;
1411     info -> public_flag = (super.flags & MTG_PUBLIC);
1412
1413     forget_super();
1414
1415     *result = 0;
1416     return;
1417}
1418
1419/*
1420 *
1421 * get_trn () --
1422 * gets the given transaction, and feeds it through dest_file.  Returns an
1423 * error code
1424 *
1425 */
1426get_trn (mtg_name, trn, dest_file, result)
1427char *mtg_name;
1428trn_nums trn;
1429tfile dest_file;
1430int *result;
1431{
1432     chain_blk cb;
1433     trn_hdr th;
1434     char buffer [512];
1435     int tocopy, tfs;
1436     char *th_author;
1437
1438/*   printf ("get_trn: mtg %s, trn %d\n",
1439             mtg_name, trn);*/
1440
1441     topen (dest_file, "w", result);
1442     abort_file = dest_file;
1443
1444     *result = open_mtg (mtg_name);
1445     if (*result) { core_abort (); return; }
1446
1447     start_read();                              /* starting to read */
1448
1449     *result = read_super ();
1450     if (*result) { core_abort(); return; }
1451
1452     *result = read_chain (trn, &cb);
1453     if (*result) { core_abort(); return; }
1454
1455     if (cb.trn_addr == 0) {
1456          *result = DELETED_TRN;
1457          core_abort();
1458          return;
1459     }
1460
1461     *result = read_trn (cb.trn_addr, &th, (char **)0, &th_author, NULL);
1462     if (*result) { core_abort(); return; }
1463
1464     finish_read();
1465
1466     if ((cb.flags & CB_DELETED) && !has_trn_access(th_author, 'd')) {
1467
1468          *result = DELETED_TRN;
1469          free(th_author);
1470          core_abort(); return;
1471     }
1472     if (!has_trn_access(th_author,'r')) {
1473          *result = NO_ACCESS;
1474          free(th_author);
1475          core_abort(); return;
1476     }
1477
1478     (void) free(th_author);
1479
1480     lseek (u_trn_f, (long)(th.text_addr), 0);
1481     tfs = th.num_chars;
1482     while (tfs > 0) {
1483          tocopy = min (512, tfs);
1484          read (u_trn_f, buffer, tocopy);
1485          twrite (dest_file, buffer, tocopy,result);
1486          tfs -= tocopy;
1487     }
1488
1489     tclose (dest_file,result);
1490     abort_file = NULL;
1491
1492     if (cb.flags & CB_DELETED)
1493          *result = DELETED_TRN;
1494     else
1495          *result = 0;
1496     return;
1497}
1498
1499/*
1500 *
1501 * remove_mtg () --
1502 * removes the given meeting  -- the physical contents of the meeting
1503 * are destroyed.
1504 *
1505 */
1506remove_mtg (mtg_name, result)
1507char *mtg_name;
1508int *result;
1509{
1510     char str[256];
1511
1512
1513/*   printf ("remove_mtg: mtg %s\n",
1514             mtg_name);*/
1515     
1516     *result = open_mtg (mtg_name);
1517     if (*result) return;
1518
1519     if (!has_mtg_access('c')) {
1520          *result = NO_ACCESS;
1521          return;
1522     }
1523         
1524     strcpy (str, mtg_name);
1525     strcat (str, "/control");
1526
1527     if (unlink (str) < 0) {
1528          if (errno != ENOENT) {
1529               *result = CANNOT_REMOVE;
1530               return;
1531          }
1532     }
1533
1534     strcpy (str, mtg_name);
1535     strcat (str, "/transactions");
1536
1537     unlink (str);
1538
1539     strcpy (str, mtg_name);
1540     strcat (str, "/acl");
1541
1542     unlink (str);
1543
1544     *result = 0;
1545     if (rmdir (mtg_name) < 0)
1546          *result = CANNOT_REMOVE;
1547
1548     *result = read_super();
1549     super.date_created = 0;
1550     write_super();
1551
1552     close (u_trn_f);                           /* bombs away */
1553     close (u_control_f);
1554     if (u_acl_f)
1555          close (u_acl_f);
1556     acl_destroy(mtg_acl);
1557     mtg_acl = NULL;
1558     current_mtg [0] = '\0';
1559
1560     return;
1561}
1562
1563/*
1564 *
1565 * updated_mtg () -- Quick procedure to check if the meeting is updated
1566 *                   with respect to a given time and transaction number.
1567 *                   An efficient procedure for a common operation -- doesn't
1568 *                   open the meeting unless it has to.
1569 *
1570 */
1571updated_mtg (mtg_name, date_attended, last, updated, result)
1572char *mtg_name;
1573int date_attended, last;
1574bool *updated;
1575int *result;
1576{
1577     char str[256];
1578     int mtg_name_len;
1579     mtg_super mysuper;
1580     struct stat sb;
1581     int uf;
1582
1583     *updated = 0;
1584     *result = 0;                               /* optimist */
1585
1586     mtg_name_len = strlen (mtg_name);
1587     if (mtg_name[0] != '/' || mtg_name_len == 0 || mtg_name_len >= MAXPATHLEN || mtg_name [mtg_name_len-1] == '/') {
1588          *result = BAD_PATH;
1589          return;
1590     }
1591
1592     strcpy (str, mtg_name);
1593     strcat (str, "/forward");
1594     if (!stat(str, &sb)) {             /* Show moved meetings as changed */
1595          *updated = TRUE;
1596          *result = 0;
1597          return;
1598     }
1599
1600     strcpy (str, mtg_name);
1601     strcat (str, "/control");
1602
1603     /* time makes no difference in our books */
1604     if ((uf = open(str, O_RDWR, 0700)) < 0) {
1605          if (errno == ENOENT)
1606               *result = NO_SUCH_MTG;
1607          else if (errno == EACCES)
1608               *result = NO_ACCESS;
1609          else
1610               *result = BAD_PATH;
1611          goto punt;
1612     }
1613
1614     /* forget locking (and stuff) for what we're doing */
1615     lseek (uf, (long)0, 0);
1616     read (uf, (char *) &mysuper, sizeof (mysuper));
1617     close(uf);
1618
1619     *updated = (mysuper.last > last);
1620     if (mysuper.highest < last)
1621          *result = NO_SUCH_TRN;
1622
1623punt:
1624     return;
1625}
Note: See TracBrowser for help on using the repository browser.