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