this should be all
[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           {
272             vers_ts->ts_user = time_stamp (finfo->file);
273             vers_ts->ts_user_ists = 1;
274           }
275     }
276
277     return (vers_ts);
278 }
279
280
281
282 #ifdef SERVER_SUPPORT
283
284 /* Set VERS_TS->TS_USER to time stamp for FILE.  */
285
286 /* Separate these out to keep the logic below clearer.  */
287 #define mark_lost(V)            ((V)->ts_user = 0)
288 #define mark_unchanged(V)       ((V)->ts_user = xstrdup ((V)->ts_rcs))
289
290 static void
291 time_stamp_server (const char *file, Vers_TS *vers_ts, Entnode *entdata)
292 {
293     struct stat sb;
294     char *cp;
295
296     TRACE (TRACE_FUNCTION, "time_stamp_server (%s, %s, %s, %s)",
297            file,
298            entdata && entdata->version ? entdata->version : "(null)",
299            entdata && entdata->timestamp ? entdata->timestamp : "(null)",
300            entdata && entdata->conflict ? entdata->conflict : "(null)");
301
302     if (lstat (file, &sb) < 0)
303     {
304         if (! existence_error (errno))
305             error (1, errno, "cannot stat temp file");
306
307         /* Missing file means lost or unmodified; check entries
308            file to see which.
309
310            XXX FIXME - If there's no entries file line, we
311            wouldn't be getting the file at all, so consider it
312            lost.  I don't know that that's right, but it's not
313            clear to me that either choice is.  Besides, would we
314            have an RCS string in that case anyways?  */
315         if (entdata == NULL)
316             mark_lost (vers_ts);
317         else if (entdata->timestamp
318                  && entdata->timestamp[0] == '='
319                  && entdata->timestamp[1] == '\0')
320             mark_unchanged (vers_ts);
321         else if (entdata->conflict
322                  && entdata->conflict[0] == '=')
323         {
324             /* These just need matching content.  Might as well minimize it.  */
325             vers_ts->ts_user = xstrdup ("");
326             vers_ts->ts_conflict = xstrdup ("");
327         }
328         else if (entdata->timestamp
329                  && (entdata->timestamp[0] == 'M'
330                      || entdata->timestamp[0] == 'D')
331                  && entdata->timestamp[1] == '\0')
332             vers_ts->ts_user = xstrdup ("Is-modified");
333         else
334             mark_lost (vers_ts);
335     }
336     else if (sb.st_mtime == 0)
337     {
338         /* We shouldn't reach this case any more!  */
339         abort ();
340     }
341     else
342     {
343         struct tm *tm_p;
344
345         vers_ts->ts_user_ists = 1;
346         vers_ts->ts_user = xmalloc (25);
347         /* We want to use the same timestamp format as is stored in the
348            st_mtime.  For unix (and NT I think) this *must* be universal
349            time (UT), so that files don't appear to be modified merely
350            because the timezone has changed.  For VMS, or hopefully other
351            systems where gmtime returns NULL, the modification time is
352            stored in local time, and therefore it is not possible to cause
353            st_mtime to be out of sync by changing the timezone.  */
354         tm_p = gmtime (&sb.st_mtime);
355         cp = tm_p ? asctime (tm_p) : ctime (&sb.st_mtime);
356         cp[24] = 0;
357         /* Fix non-standard format.  */
358         if (cp[8] == '0') cp[8] = ' ';
359         (void) strcpy (vers_ts->ts_user, cp);
360     }
361 }
362
363 #endif /* SERVER_SUPPORT */
364
365
366
367 /* Given a UNIX seconds since the epoch, return a string in the format used by
368  * the Entries file.
369  *
370  *
371  * INPUTS
372  *   UNIXTIME   The timestamp to be formatted.
373  *
374  * RETURNS
375  *   A freshly allocated string the caller is responsible for disposing of.
376  */
377 char *
378 entries_time (time_t unixtime)
379 {
380     struct tm *tm_p;
381     char *cp;
382
383     /* We want to use the same timestamp format as is stored in the
384        st_mtime.  For unix (and NT I think) this *must* be universal
385        time (UT), so that files don't appear to be modified merely
386        because the timezone has changed.  For VMS, or hopefully other
387        systems where gmtime returns NULL, the modification time is
388        stored in local time, and therefore it is not possible to cause
389        st_mtime to be out of sync by changing the timezone.  */
390     tm_p = gmtime (&unixtime);
391     cp = tm_p ? asctime (tm_p) : ctime (&unixtime);
392     /* Get rid of the EOL */
393     cp[24] = '\0';
394     /* Fix non-standard format.  */
395     if (cp[8] == '0') cp[8] = ' ';
396
397     return Xasprintf ("%s", cp);
398 }
399
400
401
402 time_t
403 unix_time_stamp (const char *file)
404 {
405     struct stat sb;
406     time_t mtime = 0L;
407
408     if (!lstat (file, &sb))
409     {
410         mtime = sb.st_mtime;
411     }
412
413     /* If it's a symlink, return whichever is the newest mtime of
414        the link and its target, for safety.
415     */
416     if (!stat (file, &sb))
417     {
418         if (mtime < sb.st_mtime)
419             mtime = sb.st_mtime;
420     }
421
422     return mtime;
423 }
424
425
426
427 /*
428  * Gets the time-stamp for the file "file" and returns it in space it
429  * allocates
430  */
431 char *
432 time_stamp (const char *file)
433 {
434     time_t mtime = unix_time_stamp (file);
435     return mtime ? entries_time (mtime) : NULL;
436 }
437
438
439
440 /*
441  * free up a Vers_TS struct
442  */
443 void
444 freevers_ts (Vers_TS **versp)
445 {
446     if ((*versp)->srcfile)
447         freercsnode (&((*versp)->srcfile));
448     if ((*versp)->vn_user)
449         free ((*versp)->vn_user);
450     if ((*versp)->vn_rcs)
451         free ((*versp)->vn_rcs);
452     if ((*versp)->vn_tag)
453         free ((*versp)->vn_tag);
454     if ((*versp)->ts_user)
455         free ((*versp)->ts_user);
456     if ((*versp)->ts_rcs)
457         free ((*versp)->ts_rcs);
458     if ((*versp)->options)
459         free ((*versp)->options);
460     if ((*versp)->tag)
461         free ((*versp)->tag);
462     if ((*versp)->date)
463         free ((*versp)->date);
464     if ((*versp)->ts_conflict)
465         free ((*versp)->ts_conflict);
466     free ((char *) *versp);
467     *versp = NULL;
468 }