xref: /dragonfly/contrib/cvs-1.12/src/vers_ts.c (revision d4ef6694)
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 	    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 
287 static void
288 time_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  */
373 char *
374 entries_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 
398 time_t
399 unix_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  */
427 char *
428 time_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  */
439 void
440 freevers_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