vers_ts.c revision 1.1.1.1
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
18static 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 */
40Vers_TS *
41Version_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
287static void
288time_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 */
373char *
374entries_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
398time_t
399unix_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 */
427char *
428time_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 */
439void
440freevers_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}
465