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