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