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