xref: /netbsd/external/gpl2/xcvs/dist/src/vers_ts.c (revision 3feae4e4)
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