xref: /openbsd-src/gnu/usr.bin/cvs/src/vers_ts.c (revision b2ea75c1b17e1a9a339660e7ed45cd24946b230e)
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
12 static 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 
18 Vers_TS *
19 Version_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 
262 static void
263 time_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 	(void) strcpy (vers_ts->ts_user, cp);
326     }
327 }
328 
329 #endif /* SERVER_SUPPORT */
330 /*
331  * Gets the time-stamp for the file "file" and returns it in space it
332  * allocates
333  */
334 char *
335 time_stamp (file)
336     char *file;
337 {
338     struct stat sb;
339     char *cp;
340     char *ts;
341 
342     if (CVS_LSTAT (file, &sb) < 0)
343     {
344 	ts = NULL;
345     }
346     else
347     {
348 	struct tm *tm_p;
349         struct tm local_tm;
350 	ts = xmalloc (25);
351 	/* We want to use the same timestamp format as is stored in the
352 	   st_mtime.  For unix (and NT I think) this *must* be universal
353 	   time (UT), so that files don't appear to be modified merely
354 	   because the timezone has changed.  For VMS, or hopefully other
355 	   systems where gmtime returns NULL, the modification time is
356 	   stored in local time, and therefore it is not possible to cause
357 	   st_mtime to be out of sync by changing the timezone.  */
358 	tm_p = gmtime (&sb.st_mtime);
359 	if (tm_p)
360 	{
361 	    memcpy (&local_tm, tm_p, sizeof (local_tm));
362 	    cp = asctime (&local_tm);	/* copy in the modify time */
363 	}
364 	else
365 	    cp = ctime(&sb.st_mtime);
366 
367 	cp[24] = 0;
368 	(void) strcpy (ts, cp);
369     }
370 
371     return (ts);
372 }
373 
374 /*
375  * free up a Vers_TS struct
376  */
377 void
378 freevers_ts (versp)
379     Vers_TS **versp;
380 {
381     if ((*versp)->srcfile)
382 	freercsnode (&((*versp)->srcfile));
383     if ((*versp)->vn_user)
384 	free ((*versp)->vn_user);
385     if ((*versp)->vn_rcs)
386 	free ((*versp)->vn_rcs);
387     if ((*versp)->vn_tag)
388 	free ((*versp)->vn_tag);
389     if ((*versp)->ts_user)
390 	free ((*versp)->ts_user);
391     if ((*versp)->ts_rcs)
392 	free ((*versp)->ts_rcs);
393     if ((*versp)->options)
394 	free ((*versp)->options);
395     if ((*versp)->tag)
396 	free ((*versp)->tag);
397     if ((*versp)->date)
398 	free ((*versp)->date);
399     if ((*versp)->ts_conflict)
400 	free ((*versp)->ts_conflict);
401     free ((char *) *versp);
402     *versp = (Vers_TS *) NULL;
403 }
404