update from MirBSD; for us relevant:
[alioth/cvs.git] / src / vers_ts.c
1 /*
2  * Copyright (C) 1986-2005 The Free Software Foundation, Inc.
3  *
4  * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>,
5  *                                  and others.
6  *
7  * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk
8  * Portions Copyright (C) 1989-1992, Brian Berliner
9  * 
10  * You may distribute under the terms of the GNU General Public License as
11  * specified in the README file that comes with the CVS source distribution.
12  */
13
14 #include "cvs.h"
15 #include "lstat.h"
16
17 __RCSID("$MirOS: src/gnu/usr.bin/cvs/src/vers_ts.c,v 1.3 2016/10/21 20:01:55 tg Exp $");
18
19 #ifdef SERVER_SUPPORT
20 static void time_stamp_server (const char *, Vers_TS *, Entnode *);
21 #endif
22
23 /* Fill in and return a Vers_TS structure for the file FINFO.
24  *
25  * INPUTS
26  *   finfo              struct file_info data about the file to be examined.
27  *   options            Keyword expansion options, I think generally from the
28  *                      command line.  Can be either NULL or "" to indicate
29  *                      none are specified here.
30  *   tag                Tag specified by user on the command line (via -r).
31  *   date               Date specified by user on the command line (via -D).
32  *   force_tag_match    If set and TAG is specified, will only set RET->vn_rcs
33  *                      based on TAG.  Otherwise, if TAG is specified and does
34  *                      not exist in the file, RET->vn_rcs will be set to the
35  *                      head revision.
36  *   set_time           If set, set the last modification time of the user file
37  *                      specified by FINFO to the checkin time of RET->vn_rcs.
38  *
39  * RETURNS
40  *   Vers_TS structure for FINFO.
41  */
42 Vers_TS *
43 Version_TS (struct file_info *finfo, char *options, char *tag, char *date,
44             int force_tag_match, int set_time)
45 {
46     Node *p;
47     RCSNode *rcsdata;
48     Vers_TS *vers_ts;
49     struct stickydirtag *sdtp;
50     Entnode *entdata;
51     char *rcsexpand = NULL;
52
53     /* get a new Vers_TS struct */
54
55     vers_ts = xmalloc (sizeof (Vers_TS));
56     memset (vers_ts, 0, sizeof (*vers_ts));
57
58     /*
59      * look up the entries file entry and fill in the version and timestamp
60      * if entries is NULL, there is no entries file so don't bother trying to
61      * look it up (used by checkout -P)
62      */
63     if (finfo->entries == NULL)
64     {
65         sdtp = NULL;
66         p = NULL;
67     }
68     else
69     {
70         p = findnode_fn (finfo->entries, finfo->file);
71         sdtp = finfo->entries->list->data; /* list-private */
72     }
73
74     if (p == NULL)
75     {
76         entdata = NULL;
77     }
78     else
79     {
80         entdata = p->data;
81
82         if (entdata->type == ENT_SUBDIR)
83         {
84             /* According to cvs.texinfo, the various fields in the Entries
85                file for a directory (other than the name) do not have a
86                defined meaning.  We need to pass them along without getting
87                confused based on what is in them.  Therefore we make sure
88                not to set vn_user and the like from Entries, add.c and
89                perhaps other code will expect these fields to be NULL for
90                a directory.  */
91             vers_ts->entdata = entdata;
92         }
93         else
94 #ifdef SERVER_SUPPORT
95         /* An entries line with "D" in the timestamp indicates that the
96            client sent Is-modified without sending Entry.  So we want to
97            use the entries line for the sole purpose of telling
98            time_stamp_server what is up; we don't want the rest of CVS
99            to think there is an entries line.  */
100         if (strcmp (entdata->timestamp, "D") != 0)
101 #endif
102         {
103             vers_ts->vn_user = xstrdup (entdata->version);
104             vers_ts->ts_rcs = xstrdup (entdata->timestamp);
105             vers_ts->ts_conflict = xstrdup (entdata->conflict);
106             if (!(tag || date) && !(sdtp && sdtp->aflag))
107             {
108                 vers_ts->tag = xstrdup (entdata->tag);
109                 vers_ts->date = xstrdup (entdata->date);
110             }
111             vers_ts->entdata = entdata;
112         }
113         /* Even if we don't have an "entries line" as such
114            (vers_ts->entdata), we want to pick up options which could
115            have been from a Kopt protocol request.  */
116         if (!options || *options == '\0')
117         {
118             if (!(sdtp && sdtp->aflag))
119                 vers_ts->options = xstrdup (entdata->options);
120         }
121     }
122
123     /* Always look up the RCS keyword mode when we have an RCS archive.  It
124      * will either be needed as a default or to avoid allowing the -k options
125      * specified on the command line from overriding binary mode (-kb).
126      */
127     if (finfo->rcs != NULL)
128         rcsexpand = RCS_getexpand (finfo->rcs);
129
130     /*
131      * -k options specified on the command line override (and overwrite)
132      * options stored in the entries file and default options from the RCS
133      * archive, except for binary mode (-kb).
134      */
135     if (options && *options != '\0')
136     {
137         if (vers_ts->options != NULL)
138             free (vers_ts->options);
139         if (rcsexpand != NULL && strcmp (rcsexpand, "b") == 0)
140             vers_ts->options = xstrdup ("-kb");
141         else
142             vers_ts->options = xstrdup (options);
143     }
144     else if ((!vers_ts->options || *vers_ts->options == '\0')
145              && rcsexpand != NULL)
146     {
147         /* If no keyword expansion was specified on command line,
148            use whatever was in the rcs file (if there is one).  This
149            is how we, if we are the server, tell the client whether
150            a file is binary.  */
151         if (vers_ts->options != NULL)
152             free (vers_ts->options);
153         vers_ts->options = xmalloc (strlen (rcsexpand) + 3);
154         strcpy (vers_ts->options, "-k");
155         strcat (vers_ts->options, rcsexpand);
156     }
157     if (!vers_ts->options)
158         vers_ts->options = xstrdup ("");
159
160     /*
161      * if tags were specified on the command line, they override what is in
162      * the Entries file
163      */
164     if (tag || date)
165     {
166         vers_ts->tag = xstrdup (tag);
167         vers_ts->date = xstrdup (date);
168     }
169     else if (!vers_ts->entdata && (sdtp && sdtp->aflag == 0))
170     {
171         if (!vers_ts->tag)
172         {
173             vers_ts->tag = xstrdup (sdtp->tag);
174             vers_ts->nonbranch = sdtp->nonbranch;
175         }
176         if (!vers_ts->date)
177             vers_ts->date = xstrdup (sdtp->date);
178     }
179
180     /* Now look up the info on the source controlled file */
181     if (finfo->rcs != NULL)
182     {
183         rcsdata = finfo->rcs;
184         rcsdata->refcount++;
185     }
186     else if (finfo->repository != NULL)
187         rcsdata = RCS_parse (finfo->file, finfo->repository);
188     else
189         rcsdata = NULL;
190
191     if (rcsdata != NULL)
192     {
193         /* squirrel away the rcsdata pointer for others */
194         vers_ts->srcfile = rcsdata;
195
196         if (vers_ts->tag && strcmp (vers_ts->tag, TAG_BASE) == 0)
197         {
198             vers_ts->vn_rcs = xstrdup (vers_ts->vn_user);
199             vers_ts->vn_tag = xstrdup (vers_ts->vn_user);
200         }
201         else
202         {
203             int simple;
204
205             vers_ts->vn_rcs = RCS_getversion (rcsdata, vers_ts->tag,
206                                               vers_ts->date, force_tag_match,
207                                               &simple);
208             if (vers_ts->vn_rcs == NULL)
209                 vers_ts->vn_tag = NULL;
210             else if (simple)
211                 vers_ts->vn_tag = xstrdup (vers_ts->tag);
212             else
213                 vers_ts->vn_tag = xstrdup (vers_ts->vn_rcs);
214         }
215
216         /*
217          * If the source control file exists and has the requested revision,
218          * get the Date the revision was checked in.  If "user" exists, set
219          * its mtime.
220          */
221         if (set_time && vers_ts->vn_rcs != NULL)
222         {
223 #ifdef SERVER_SUPPORT
224             if (server_active)
225                 server_modtime (finfo, vers_ts);
226             else
227 #endif
228             {
229                 struct utimbuf t;
230
231                 memset (&t, 0, sizeof (t));
232                 t.modtime = RCS_getrevtime (rcsdata, vers_ts->vn_rcs, 0, 0);
233                 if (t.modtime != (time_t) -1)
234                 {
235 #ifdef UTIME_EXPECTS_WRITABLE
236                     int change_it_back = 0;
237 #endif
238
239                     (void) time (&t.actime);
240
241 #ifdef UTIME_EXPECTS_WRITABLE
242                     if (!iswritable (finfo->file))
243                     {
244                         xchmod (finfo->file, 1);
245                         change_it_back = 1;
246                     }
247 #endif  /* UTIME_EXPECTS_WRITABLE  */
248
249                     /* This used to need to ignore existence_errors
250                        (for cases like where update.c now clears
251                        set_time if noexec, but didn't used to).  I
252                        think maybe now it doesn't (server_modtime does
253                        not like those kinds of cases).  */
254                     (void) utime (finfo->file, &t);
255
256 #ifdef UTIME_EXPECTS_WRITABLE
257                     if (change_it_back)
258                         xchmod (finfo->file, 0);
259 #endif  /*  UTIME_EXPECTS_WRITABLE  */
260                 }
261             }
262         }
263     }
264
265     /* get user file time-stamp in ts_user */
266     if (finfo->entries != NULL)
267     {
268 #ifdef SERVER_SUPPORT
269         if (server_active)
270             time_stamp_server (finfo->file, vers_ts, entdata);
271         else
272 #endif
273           {
274             vers_ts->ts_user = time_stamp (finfo->file);
275             vers_ts->ts_user_ists = 1;
276           }
277     }
278
279     return (vers_ts);
280 }
281
282
283
284 #ifdef SERVER_SUPPORT
285
286 /* Set VERS_TS->TS_USER to time stamp for FILE.  */
287
288 /* Separate these out to keep the logic below clearer.  */
289 #define mark_lost(V)            ((V)->ts_user = 0)
290 #define mark_unchanged(V)       ((V)->ts_user = xstrdup ((V)->ts_rcs))
291
292 static void
293 time_stamp_server (const char *file, Vers_TS *vers_ts, Entnode *entdata)
294 {
295     struct stat sb;
296     char *cp;
297
298     TRACE (TRACE_FUNCTION, "time_stamp_server (%s, %s, %s, %s)",
299            file,
300            entdata && entdata->version ? entdata->version : "(null)",
301            entdata && entdata->timestamp ? entdata->timestamp : "(null)",
302            entdata && entdata->conflict ? entdata->conflict : "(null)");
303
304     if (lstat (file, &sb) < 0)
305     {
306         if (! existence_error (errno))
307             error (1, errno, "cannot stat temp file");
308
309         /* Missing file means lost or unmodified; check entries
310            file to see which.
311
312            XXX FIXME - If there's no entries file line, we
313            wouldn't be getting the file at all, so consider it
314            lost.  I don't know that that's right, but it's not
315            clear to me that either choice is.  Besides, would we
316            have an RCS string in that case anyways?  */
317         if (entdata == NULL)
318             mark_lost (vers_ts);
319         else if (entdata->timestamp
320                  && entdata->timestamp[0] == '='
321                  && entdata->timestamp[1] == '\0')
322             mark_unchanged (vers_ts);
323         else if (entdata->conflict
324                  && entdata->conflict[0] == '=')
325         {
326             /* These just need matching content.  Might as well minimize it.  */
327             vers_ts->ts_user = xstrdup ("");
328             vers_ts->ts_conflict = xstrdup ("");
329         }
330         else if (entdata->timestamp
331                  && (entdata->timestamp[0] == 'M'
332                      || entdata->timestamp[0] == 'D')
333                  && entdata->timestamp[1] == '\0')
334             vers_ts->ts_user = xstrdup ("Is-modified");
335         else
336             mark_lost (vers_ts);
337     }
338     else if (sb.st_mtime == 0)
339     {
340         /* We shouldn't reach this case any more!  */
341         abort ();
342     }
343     else
344     {
345         struct tm *tm_p;
346
347         vers_ts->ts_user_ists = 1;
348         vers_ts->ts_user = xmalloc (25);
349         /* We want to use the same timestamp format as is stored in the
350            st_mtime.  For unix (and NT I think) this *must* be universal
351            time (UT), so that files don't appear to be modified merely
352            because the timezone has changed.  For VMS, or hopefully other
353            systems where gmtime returns NULL, the modification time is
354            stored in local time, and therefore it is not possible to cause
355            st_mtime to be out of sync by changing the timezone.  */
356         tm_p = gmtime (&sb.st_mtime);
357         cp = tm_p ? asctime (tm_p) : ctime (&sb.st_mtime);
358         cp[24] = 0;
359         /* Fix non-standard format.  */
360         if (cp[8] == '0') cp[8] = ' ';
361         (void) strcpy (vers_ts->ts_user, cp);
362     }
363 }
364
365 #endif /* SERVER_SUPPORT */
366
367
368
369 /* Given a UNIX seconds since the epoch, return a string in the format used by
370  * the Entries file.
371  *
372  *
373  * INPUTS
374  *   UNIXTIME   The timestamp to be formatted.
375  *
376  * RETURNS
377  *   A freshly allocated string the caller is responsible for disposing of.
378  */
379 char *
380 entries_time (time_t unixtime)
381 {
382     struct tm *tm_p;
383     char *cp;
384
385     /* We want to use the same timestamp format as is stored in the
386        st_mtime.  For unix (and NT I think) this *must* be universal
387        time (UT), so that files don't appear to be modified merely
388        because the timezone has changed.  For VMS, or hopefully other
389        systems where gmtime returns NULL, the modification time is
390        stored in local time, and therefore it is not possible to cause
391        st_mtime to be out of sync by changing the timezone.  */
392     tm_p = gmtime (&unixtime);
393     cp = tm_p ? asctime (tm_p) : ctime (&unixtime);
394     /* Get rid of the EOL */
395     cp[24] = '\0';
396     /* Fix non-standard format.  */
397     if (cp[8] == '0') cp[8] = ' ';
398
399     return Xasprintf ("%s", cp);
400 }
401
402
403
404 time_t
405 unix_time_stamp (const char *file)
406 {
407     struct stat sb;
408     time_t mtime = 0L;
409
410     if (!lstat (file, &sb))
411     {
412         mtime = sb.st_mtime;
413     }
414
415     /* If it's a symlink, return whichever is the newest mtime of
416        the link and its target, for safety.
417     */
418     if (!stat (file, &sb))
419     {
420         if (mtime < sb.st_mtime)
421             mtime = sb.st_mtime;
422     }
423
424     return mtime;
425 }
426
427
428
429 /*
430  * Gets the time-stamp for the file "file" and returns it in space it
431  * allocates
432  */
433 char *
434 time_stamp (const char *file)
435 {
436     time_t mtime = unix_time_stamp (file);
437     return mtime ? entries_time (mtime) : NULL;
438 }
439
440
441
442 /*
443  * free up a Vers_TS struct
444  */
445 void
446 freevers_ts (Vers_TS **versp)
447 {
448     if ((*versp)->srcfile)
449         freercsnode (&((*versp)->srcfile));
450     if ((*versp)->vn_user)
451         free ((*versp)->vn_user);
452     if ((*versp)->vn_rcs)
453         free ((*versp)->vn_rcs);
454     if ((*versp)->vn_tag)
455         free ((*versp)->vn_tag);
456     if ((*versp)->ts_user)
457         free ((*versp)->ts_user);
458     if ((*versp)->ts_rcs)
459         free ((*versp)->ts_rcs);
460     if ((*versp)->options)
461         free ((*versp)->options);
462     if ((*versp)->tag)
463         free ((*versp)->tag);
464     if ((*versp)->date)
465         free ((*versp)->date);
466     if ((*versp)->ts_conflict)
467         free ((*versp)->ts_conflict);
468     free ((char *) *versp);
469     *versp = NULL;
470 }