11e72d8d2Sderaadt /*
21e72d8d2Sderaadt * Copyright (c) 1992, Brian Berliner and Jeff Polk
31e72d8d2Sderaadt * Copyright (c) 1989-1992, Brian Berliner
41e72d8d2Sderaadt *
51e72d8d2Sderaadt * You may distribute under the terms of the GNU General Public License as
62286d8edStholo * specified in the README file that comes with the CVS source distribution.
71e72d8d2Sderaadt */
81e72d8d2Sderaadt
91e72d8d2Sderaadt #include "cvs.h"
101e72d8d2Sderaadt
111e72d8d2Sderaadt #ifdef SERVER_SUPPORT
12b6c02222Stholo static void time_stamp_server PROTO((char *, Vers_TS *, Entnode *));
131e72d8d2Sderaadt #endif
141e72d8d2Sderaadt
152286d8edStholo /* Fill in and return a Vers_TS structure for the file FINFO. TAG and
162286d8edStholo DATE are from the command line. */
172286d8edStholo
181e72d8d2Sderaadt Vers_TS *
Version_TS(finfo,options,tag,date,force_tag_match,set_time)1950bf276cStholo Version_TS (finfo, options, tag, date, force_tag_match, set_time)
2050bf276cStholo struct file_info *finfo;
212286d8edStholo
222286d8edStholo /* Keyword expansion options, I think generally from the command
232286d8edStholo line. Can be either NULL or "" to indicate none are specified
242286d8edStholo here. */
251e72d8d2Sderaadt char *options;
261e72d8d2Sderaadt char *tag;
271e72d8d2Sderaadt char *date;
281e72d8d2Sderaadt int force_tag_match;
291e72d8d2Sderaadt int set_time;
301e72d8d2Sderaadt {
311e72d8d2Sderaadt Node *p;
321e72d8d2Sderaadt RCSNode *rcsdata;
331e72d8d2Sderaadt Vers_TS *vers_ts;
341e72d8d2Sderaadt struct stickydirtag *sdtp;
35b6c02222Stholo Entnode *entdata;
361e72d8d2Sderaadt
37c71bc7e2Stholo #ifdef UTIME_EXPECTS_WRITABLE
38c71bc7e2Stholo int change_it_back = 0;
39c71bc7e2Stholo #endif
40c71bc7e2Stholo
411e72d8d2Sderaadt /* get a new Vers_TS struct */
421e72d8d2Sderaadt vers_ts = (Vers_TS *) xmalloc (sizeof (Vers_TS));
431e72d8d2Sderaadt memset ((char *) vers_ts, 0, sizeof (*vers_ts));
441e72d8d2Sderaadt
451e72d8d2Sderaadt /*
461e72d8d2Sderaadt * look up the entries file entry and fill in the version and timestamp
471e72d8d2Sderaadt * if entries is NULL, there is no entries file so don't bother trying to
481e72d8d2Sderaadt * look it up (used by checkout -P)
491e72d8d2Sderaadt */
5050bf276cStholo if (finfo->entries == NULL)
511e72d8d2Sderaadt {
521e72d8d2Sderaadt sdtp = NULL;
531e72d8d2Sderaadt p = NULL;
541e72d8d2Sderaadt }
551e72d8d2Sderaadt else
561e72d8d2Sderaadt {
5750bf276cStholo p = findnode_fn (finfo->entries, finfo->file);
5850bf276cStholo sdtp = (struct stickydirtag *) finfo->entries->list->data; /* list-private */
591e72d8d2Sderaadt }
601e72d8d2Sderaadt
61b6c02222Stholo entdata = NULL;
621e72d8d2Sderaadt if (p != NULL)
631e72d8d2Sderaadt {
64b6c02222Stholo entdata = (Entnode *) p->data;
651e72d8d2Sderaadt
662286d8edStholo if (entdata->type == ENT_SUBDIR)
672286d8edStholo {
682286d8edStholo /* According to cvs.texinfo, the various fields in the Entries
692286d8edStholo file for a directory (other than the name) do not have a
702286d8edStholo defined meaning. We need to pass them along without getting
712286d8edStholo confused based on what is in them. Therefore we make sure
722286d8edStholo not to set vn_user and the like from Entries, add.c and
732286d8edStholo perhaps other code will expect these fields to be NULL for
742286d8edStholo a directory. */
752286d8edStholo vers_ts->entdata = entdata;
762286d8edStholo }
772286d8edStholo else
78b6c02222Stholo #ifdef SERVER_SUPPORT
79b6c02222Stholo /* An entries line with "D" in the timestamp indicates that the
80b6c02222Stholo client sent Is-modified without sending Entry. So we want to
81b6c02222Stholo use the entries line for the sole purpose of telling
82b6c02222Stholo time_stamp_server what is up; we don't want the rest of CVS
83b6c02222Stholo to think there is an entries line. */
84b6c02222Stholo if (strcmp (entdata->timestamp, "D") != 0)
85b6c02222Stholo #endif
86b6c02222Stholo {
871e72d8d2Sderaadt vers_ts->vn_user = xstrdup (entdata->version);
881e72d8d2Sderaadt vers_ts->ts_rcs = xstrdup (entdata->timestamp);
891e72d8d2Sderaadt vers_ts->ts_conflict = xstrdup (entdata->conflict);
90e77048c1Stholo if (!(tag || date) && !(sdtp && sdtp->aflag))
911e72d8d2Sderaadt {
921e72d8d2Sderaadt vers_ts->tag = xstrdup (entdata->tag);
931e72d8d2Sderaadt vers_ts->date = xstrdup (entdata->date);
941e72d8d2Sderaadt }
952286d8edStholo vers_ts->entdata = entdata;
962286d8edStholo }
972286d8edStholo /* Even if we don't have an "entries line" as such
982286d8edStholo (vers_ts->entdata), we want to pick up options which could
992286d8edStholo have been from a Kopt protocol request. */
100e77048c1Stholo if (!options || *options == '\0')
1011e72d8d2Sderaadt {
1021e72d8d2Sderaadt if (!(sdtp && sdtp->aflag))
1031e72d8d2Sderaadt vers_ts->options = xstrdup (entdata->options);
1041e72d8d2Sderaadt }
105b6c02222Stholo }
1061e72d8d2Sderaadt
1071e72d8d2Sderaadt /*
1081e72d8d2Sderaadt * -k options specified on the command line override (and overwrite)
1091e72d8d2Sderaadt * options stored in the entries file
1101e72d8d2Sderaadt */
1112286d8edStholo if (options && *options != '\0')
1121e72d8d2Sderaadt vers_ts->options = xstrdup (options);
1132286d8edStholo else if (!vers_ts->options || *vers_ts->options == '\0')
1141e72d8d2Sderaadt {
11550bf276cStholo if (finfo->rcs != NULL)
116c2c61682Stholo {
117c2c61682Stholo /* If no keyword expansion was specified on command line,
118c2c61682Stholo use whatever was in the rcs file (if there is one). This
119c2c61682Stholo is how we, if we are the server, tell the client whether
120c2c61682Stholo a file is binary. */
12150bf276cStholo char *rcsexpand = RCS_getexpand (finfo->rcs);
122c2c61682Stholo if (rcsexpand != NULL)
123c2c61682Stholo {
124e77048c1Stholo if (vers_ts->options != NULL)
125e77048c1Stholo free (vers_ts->options);
126c2c61682Stholo vers_ts->options = xmalloc (strlen (rcsexpand) + 3);
127c2c61682Stholo strcpy (vers_ts->options, "-k");
128c2c61682Stholo strcat (vers_ts->options, rcsexpand);
129c2c61682Stholo }
130c2c61682Stholo }
1311e72d8d2Sderaadt }
1321e72d8d2Sderaadt if (!vers_ts->options)
1331e72d8d2Sderaadt vers_ts->options = xstrdup ("");
1341e72d8d2Sderaadt
1351e72d8d2Sderaadt /*
1361e72d8d2Sderaadt * if tags were specified on the command line, they override what is in
1371e72d8d2Sderaadt * the Entries file
1381e72d8d2Sderaadt */
1391e72d8d2Sderaadt if (tag || date)
1401e72d8d2Sderaadt {
1411e72d8d2Sderaadt vers_ts->tag = xstrdup (tag);
1421e72d8d2Sderaadt vers_ts->date = xstrdup (date);
1431e72d8d2Sderaadt }
1441e72d8d2Sderaadt else if (!vers_ts->entdata && (sdtp && sdtp->aflag == 0))
1451e72d8d2Sderaadt {
1461e72d8d2Sderaadt if (!vers_ts->tag)
147b6c02222Stholo {
1481e72d8d2Sderaadt vers_ts->tag = xstrdup (sdtp->tag);
149b6c02222Stholo vers_ts->nonbranch = sdtp->nonbranch;
150b6c02222Stholo }
1511e72d8d2Sderaadt if (!vers_ts->date)
1521e72d8d2Sderaadt vers_ts->date = xstrdup (sdtp->date);
1531e72d8d2Sderaadt }
1541e72d8d2Sderaadt
1551e72d8d2Sderaadt /* Now look up the info on the source controlled file */
15650bf276cStholo if (finfo->rcs != NULL)
1571e72d8d2Sderaadt {
15850bf276cStholo rcsdata = finfo->rcs;
1591e72d8d2Sderaadt rcsdata->refcount++;
1601e72d8d2Sderaadt }
16150bf276cStholo else if (finfo->repository != NULL)
16250bf276cStholo rcsdata = RCS_parse (finfo->file, finfo->repository);
1631e72d8d2Sderaadt else
1641e72d8d2Sderaadt rcsdata = NULL;
1651e72d8d2Sderaadt
1661e72d8d2Sderaadt if (rcsdata != NULL)
1671e72d8d2Sderaadt {
1681e72d8d2Sderaadt /* squirrel away the rcsdata pointer for others */
1691e72d8d2Sderaadt vers_ts->srcfile = rcsdata;
1701e72d8d2Sderaadt
1711e72d8d2Sderaadt if (vers_ts->tag && strcmp (vers_ts->tag, TAG_BASE) == 0)
17213571821Stholo {
1731e72d8d2Sderaadt vers_ts->vn_rcs = xstrdup (vers_ts->vn_user);
17413571821Stholo vers_ts->vn_tag = xstrdup (vers_ts->vn_user);
17513571821Stholo }
1761e72d8d2Sderaadt else
17713571821Stholo {
17850bf276cStholo int simple;
17950bf276cStholo
1801e72d8d2Sderaadt vers_ts->vn_rcs = RCS_getversion (rcsdata, vers_ts->tag,
18150bf276cStholo vers_ts->date, force_tag_match,
18250bf276cStholo &simple);
18313571821Stholo if (vers_ts->vn_rcs == NULL)
18413571821Stholo vers_ts->vn_tag = NULL;
18550bf276cStholo else if (simple)
18650bf276cStholo vers_ts->vn_tag = xstrdup (vers_ts->tag);
18713571821Stholo else
18813571821Stholo vers_ts->vn_tag = xstrdup (vers_ts->vn_rcs);
18913571821Stholo }
1901e72d8d2Sderaadt
1911e72d8d2Sderaadt /*
1921e72d8d2Sderaadt * If the source control file exists and has the requested revision,
1931e72d8d2Sderaadt * get the Date the revision was checked in. If "user" exists, set
1941e72d8d2Sderaadt * its mtime.
1951e72d8d2Sderaadt */
1962286d8edStholo if (set_time && vers_ts->vn_rcs != NULL)
1971e72d8d2Sderaadt {
1982770ece5Stholo #ifdef SERVER_SUPPORT
1992770ece5Stholo if (server_active)
2002770ece5Stholo server_modtime (finfo, vers_ts);
2012770ece5Stholo else
2022770ece5Stholo #endif
2032770ece5Stholo {
2041e72d8d2Sderaadt struct utimbuf t;
2051e72d8d2Sderaadt
2062770ece5Stholo memset (&t, 0, sizeof (t));
2072770ece5Stholo t.modtime =
2082770ece5Stholo RCS_getrevtime (rcsdata, vers_ts->vn_rcs, 0, 0);
2092770ece5Stholo if (t.modtime != (time_t) -1)
2102770ece5Stholo {
2112770ece5Stholo t.actime = t.modtime;
2122286d8edStholo
213c71bc7e2Stholo #ifdef UTIME_EXPECTS_WRITABLE
214c71bc7e2Stholo if (!iswritable (finfo->file))
215c71bc7e2Stholo {
216c71bc7e2Stholo xchmod (finfo->file, 1);
217c71bc7e2Stholo change_it_back = 1;
218c71bc7e2Stholo }
219c71bc7e2Stholo #endif /* UTIME_EXPECTS_WRITABLE */
220c71bc7e2Stholo
2212286d8edStholo /* This used to need to ignore existence_errors
2222286d8edStholo (for cases like where update.c now clears
2232286d8edStholo set_time if noexec, but didn't used to). I
2242286d8edStholo think maybe now it doesn't (server_modtime does
2252286d8edStholo not like those kinds of cases). */
22650bf276cStholo (void) utime (finfo->file, &t);
227c71bc7e2Stholo
228c71bc7e2Stholo #ifdef UTIME_EXPECTS_WRITABLE
229c71bc7e2Stholo if (change_it_back == 1)
230c71bc7e2Stholo {
231c71bc7e2Stholo xchmod (finfo->file, 0);
232c71bc7e2Stholo change_it_back = 0;
233c71bc7e2Stholo }
234c71bc7e2Stholo #endif /* UTIME_EXPECTS_WRITABLE */
2351e72d8d2Sderaadt }
2361e72d8d2Sderaadt }
2372770ece5Stholo }
2382770ece5Stholo }
2391e72d8d2Sderaadt
2401e72d8d2Sderaadt /* get user file time-stamp in ts_user */
24150bf276cStholo if (finfo->entries != (List *) NULL)
2421e72d8d2Sderaadt {
2431e72d8d2Sderaadt #ifdef SERVER_SUPPORT
2441e72d8d2Sderaadt if (server_active)
245b6c02222Stholo time_stamp_server (finfo->file, vers_ts, entdata);
2461e72d8d2Sderaadt else
2471e72d8d2Sderaadt #endif
24850bf276cStholo vers_ts->ts_user = time_stamp (finfo->file);
2491e72d8d2Sderaadt }
2501e72d8d2Sderaadt
2511e72d8d2Sderaadt return (vers_ts);
2521e72d8d2Sderaadt }
2531e72d8d2Sderaadt
2541e72d8d2Sderaadt #ifdef SERVER_SUPPORT
2551e72d8d2Sderaadt
2561e72d8d2Sderaadt /* Set VERS_TS->TS_USER to time stamp for FILE. */
2571e72d8d2Sderaadt
2581e72d8d2Sderaadt /* Separate these out to keep the logic below clearer. */
2591e72d8d2Sderaadt #define mark_lost(V) ((V)->ts_user = 0)
2601e72d8d2Sderaadt #define mark_unchanged(V) ((V)->ts_user = xstrdup ((V)->ts_rcs))
2611e72d8d2Sderaadt
2621e72d8d2Sderaadt static void
time_stamp_server(file,vers_ts,entdata)263b6c02222Stholo time_stamp_server (file, vers_ts, entdata)
2641e72d8d2Sderaadt char *file;
2651e72d8d2Sderaadt Vers_TS *vers_ts;
266b6c02222Stholo Entnode *entdata;
2671e72d8d2Sderaadt {
2681e72d8d2Sderaadt struct stat sb;
2691e72d8d2Sderaadt char *cp;
2701e72d8d2Sderaadt
2715e617892Stholo if (CVS_LSTAT (file, &sb) < 0)
2721e72d8d2Sderaadt {
27313571821Stholo if (! existence_error (errno))
2741e72d8d2Sderaadt error (1, errno, "cannot stat temp file");
275461cc63eStholo
2761e72d8d2Sderaadt /* Missing file means lost or unmodified; check entries
2771e72d8d2Sderaadt file to see which.
2781e72d8d2Sderaadt
2791e72d8d2Sderaadt XXX FIXME - If there's no entries file line, we
2801e72d8d2Sderaadt wouldn't be getting the file at all, so consider it
2811e72d8d2Sderaadt lost. I don't know that that's right, but it's not
2821e72d8d2Sderaadt clear to me that either choice is. Besides, would we
2831e72d8d2Sderaadt have an RCS string in that case anyways? */
284b6c02222Stholo if (entdata == NULL)
2851e72d8d2Sderaadt mark_lost (vers_ts);
286b6c02222Stholo else if (entdata->timestamp
287b6c02222Stholo && entdata->timestamp[0] == '=')
2881e72d8d2Sderaadt mark_unchanged (vers_ts);
289b6c02222Stholo else if (entdata->timestamp != NULL
290b6c02222Stholo && (entdata->timestamp[0] == 'M'
291b6c02222Stholo || entdata->timestamp[0] == 'D')
292b6c02222Stholo && entdata->timestamp[1] == '\0')
293b6c02222Stholo vers_ts->ts_user = xstrdup ("Is-modified");
2941e72d8d2Sderaadt else
2951e72d8d2Sderaadt mark_lost (vers_ts);
2961e72d8d2Sderaadt }
2971e72d8d2Sderaadt else if (sb.st_mtime == 0)
2981e72d8d2Sderaadt {
2991e72d8d2Sderaadt /* We shouldn't reach this case any more! */
3001e72d8d2Sderaadt abort ();
3011e72d8d2Sderaadt }
3021e72d8d2Sderaadt else
3031e72d8d2Sderaadt {
304c2c61682Stholo struct tm *tm_p;
305c2c61682Stholo struct tm local_tm;
306c2c61682Stholo
3071e72d8d2Sderaadt vers_ts->ts_user = xmalloc (25);
308c2c61682Stholo /* We want to use the same timestamp format as is stored in the
309c2c61682Stholo st_mtime. For unix (and NT I think) this *must* be universal
310c2c61682Stholo time (UT), so that files don't appear to be modified merely
311c2c61682Stholo because the timezone has changed. For VMS, or hopefully other
312c2c61682Stholo systems where gmtime returns NULL, the modification time is
313c2c61682Stholo stored in local time, and therefore it is not possible to cause
314c2c61682Stholo st_mtime to be out of sync by changing the timezone. */
315c2c61682Stholo tm_p = gmtime (&sb.st_mtime);
316c2c61682Stholo if (tm_p)
317c2c61682Stholo {
318c2c61682Stholo memcpy (&local_tm, tm_p, sizeof (local_tm));
319c2c61682Stholo cp = asctime (&local_tm); /* copy in the modify time */
320c2c61682Stholo }
321c2c61682Stholo else
322c2c61682Stholo cp = ctime (&sb.st_mtime);
323c2c61682Stholo
3241e72d8d2Sderaadt cp[24] = 0;
325*43c1707eStholo /* Fix non-standard format. */
326*43c1707eStholo if (cp[8] == '0') cp[8] = ' ';
3271e72d8d2Sderaadt (void) strcpy (vers_ts->ts_user, cp);
3281e72d8d2Sderaadt }
3291e72d8d2Sderaadt }
3301e72d8d2Sderaadt
3311e72d8d2Sderaadt #endif /* SERVER_SUPPORT */
3321e72d8d2Sderaadt /*
3331e72d8d2Sderaadt * Gets the time-stamp for the file "file" and returns it in space it
3341e72d8d2Sderaadt * allocates
3351e72d8d2Sderaadt */
3361e72d8d2Sderaadt char *
time_stamp(file)3371e72d8d2Sderaadt time_stamp (file)
3381e72d8d2Sderaadt char *file;
3391e72d8d2Sderaadt {
3401e72d8d2Sderaadt struct stat sb;
3411e72d8d2Sderaadt char *cp;
3421e72d8d2Sderaadt char *ts;
3431e72d8d2Sderaadt
3445e617892Stholo if (CVS_LSTAT (file, &sb) < 0)
3451e72d8d2Sderaadt {
3461e72d8d2Sderaadt ts = NULL;
3471e72d8d2Sderaadt }
3481e72d8d2Sderaadt else
3491e72d8d2Sderaadt {
350c2c61682Stholo struct tm *tm_p;
351c2c61682Stholo struct tm local_tm;
3521e72d8d2Sderaadt ts = xmalloc (25);
353c2c61682Stholo /* We want to use the same timestamp format as is stored in the
354c2c61682Stholo st_mtime. For unix (and NT I think) this *must* be universal
355c2c61682Stholo time (UT), so that files don't appear to be modified merely
356c2c61682Stholo because the timezone has changed. For VMS, or hopefully other
357c2c61682Stholo systems where gmtime returns NULL, the modification time is
358c2c61682Stholo stored in local time, and therefore it is not possible to cause
359c2c61682Stholo st_mtime to be out of sync by changing the timezone. */
360c2c61682Stholo tm_p = gmtime (&sb.st_mtime);
361c2c61682Stholo if (tm_p)
362c2c61682Stholo {
363c2c61682Stholo memcpy (&local_tm, tm_p, sizeof (local_tm));
364c2c61682Stholo cp = asctime (&local_tm); /* copy in the modify time */
365c2c61682Stholo }
366c2c61682Stholo else
367c2c61682Stholo cp = ctime(&sb.st_mtime);
368c2c61682Stholo
3691e72d8d2Sderaadt cp[24] = 0;
370*43c1707eStholo /* Fix non-standard format. */
371*43c1707eStholo if (cp[8] == '0') cp[8] = ' ';
3721e72d8d2Sderaadt (void) strcpy (ts, cp);
3731e72d8d2Sderaadt }
3741e72d8d2Sderaadt
3751e72d8d2Sderaadt return (ts);
3761e72d8d2Sderaadt }
3771e72d8d2Sderaadt
3781e72d8d2Sderaadt /*
3791e72d8d2Sderaadt * free up a Vers_TS struct
3801e72d8d2Sderaadt */
3811e72d8d2Sderaadt void
freevers_ts(versp)3821e72d8d2Sderaadt freevers_ts (versp)
3831e72d8d2Sderaadt Vers_TS **versp;
3841e72d8d2Sderaadt {
3851e72d8d2Sderaadt if ((*versp)->srcfile)
3861e72d8d2Sderaadt freercsnode (&((*versp)->srcfile));
3871e72d8d2Sderaadt if ((*versp)->vn_user)
3881e72d8d2Sderaadt free ((*versp)->vn_user);
3891e72d8d2Sderaadt if ((*versp)->vn_rcs)
3901e72d8d2Sderaadt free ((*versp)->vn_rcs);
39113571821Stholo if ((*versp)->vn_tag)
39213571821Stholo free ((*versp)->vn_tag);
3931e72d8d2Sderaadt if ((*versp)->ts_user)
3941e72d8d2Sderaadt free ((*versp)->ts_user);
3951e72d8d2Sderaadt if ((*versp)->ts_rcs)
3961e72d8d2Sderaadt free ((*versp)->ts_rcs);
3971e72d8d2Sderaadt if ((*versp)->options)
3981e72d8d2Sderaadt free ((*versp)->options);
3991e72d8d2Sderaadt if ((*versp)->tag)
4001e72d8d2Sderaadt free ((*versp)->tag);
4011e72d8d2Sderaadt if ((*versp)->date)
4021e72d8d2Sderaadt free ((*versp)->date);
4031e72d8d2Sderaadt if ((*versp)->ts_conflict)
4041e72d8d2Sderaadt free ((*versp)->ts_conflict);
4051e72d8d2Sderaadt free ((char *) *versp);
4061e72d8d2Sderaadt *versp = (Vers_TS *) NULL;
4071e72d8d2Sderaadt }
408