#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "cvs.h"
#include "getline.h"
#include "edit.h"
#include "buffer.h"
#include "save-cwd.h"
#ifdef CLIENT_SUPPORT
# include "log-buffer.h"
# include "md5.h"
#include "socket-client.h"
#include "rsh-client.h"
# ifdef HAVE_GSSAPI
# include "gssapi-client.h"
# endif
# ifdef HAVE_KERBEROS
# include "kerberos4-client.h"
# endif
static List *uppaths;
static void add_prune_candidate (const char *);
int add (int argc, char **argv);
int admin (int argc, char **argv);
int checkout (int argc, char **argv);
int commit (int argc, char **argv);
int diff (int argc, char **argv);
int history (int argc, char **argv);
int import (int argc, char **argv);
int cvslog (int argc, char **argv);
int patch (int argc, char **argv);
int release (int argc, char **argv);
int cvsremove (int argc, char **argv);
int rtag (int argc, char **argv);
int status (int argc, char **argv);
int tag (int argc, char **argv);
int update (int argc, char **argv);
static size_t try_read_from_server (char *, size_t);
static void auth_server (cvsroot_t *, struct buffer *, struct buffer *,
int, int, struct hostent *);
static cvsroot_t *client_referrer;
List *dirs_sent_to_server;
static int
is_arg_a_parent_or_listed_dir (Node *n, void *d)
{
char *directory = n->key;
char *this_argv_elem = d;
if (!strncmp (directory, this_argv_elem, strlen (this_argv_elem)))
return 1;
return 0;
}
static int
arg_should_not_be_sent_to_server (char *arg)
{
if (list_isempty (dirs_sent_to_server))
return 0;
if (!strcmp (arg, "."))
return 0;
if (isdir (arg))
{
if (walklist (dirs_sent_to_server, is_arg_a_parent_or_listed_dir, arg))
return 0;
return 1;
}
{
char *t, *root_string;
cvsroot_t *this_root = NULL;
for (t = arg + strlen (arg) - 1; t >= arg; t--)
{
if (ISSLASH (*t))
break;
}
if (t >= arg)
{
char c = *t;
*t = '\0';
if (walklist (dirs_sent_to_server, is_arg_a_parent_or_listed_dir,
arg))
{
*t = c;
return 0;
}
this_root = Name_Root (arg, NULL);
root_string = this_root->original;
*t = c;
}
else
{
if (CVSroot_cmdline)
root_string = CVSroot_cmdline;
else
{
this_root = Name_Root (NULL, NULL);
root_string = this_root->original;
}
}
if (root_string && current_parsed_root
&& strcmp (root_string, original_parsed_root->original))
{
return 1;
}
}
return 0;
}
#endif
#if defined(CLIENT_SUPPORT) || defined(SERVER_SUPPORT)
char *
mode_to_string (mode_t mode)
{
char u[4], g[4], o[4];
int i;
i = 0;
if (mode & S_IRUSR) u[i++] = 'r';
if (mode & S_IWUSR) u[i++] = 'w';
if (mode & S_IXUSR) u[i++] = 'x';
u[i] = '\0';
i = 0;
if (mode & S_IRGRP) g[i++] = 'r';
if (mode & S_IWGRP) g[i++] = 'w';
if (mode & S_IXGRP) g[i++] = 'x';
g[i] = '\0';
i = 0;
if (mode & S_IROTH) o[i++] = 'r';
if (mode & S_IWOTH) o[i++] = 'w';
if (mode & S_IXOTH) o[i++] = 'x';
o[i] = '\0';
return Xasprintf ("u=%s,g=%s,o=%s", u, g, o);
}
int
change_mode (const char *filename, const char *mode_string, int respect_umask)
{
#ifdef CHMOD_BROKEN
char *p;
int writeable = 0;
p = mode_string;
while (*p != '\0')
{
if ((p[0] == 'u' || p[0] == 'g' || p[0] == 'o') && p[1] == '=')
{
char *q = p + 2;
while (*q != ',' && *q != '\0')
{
if (*q == 'w')
writeable = 1;
++q;
}
}
while (*p != ',' && *p != '\0')
++p;
if (*p == ',')
++p;
}
xchmod (filename, writeable);
return 0;
#else
const char *p;
mode_t mode = 0;
mode_t oumask;
p = mode_string;
while (*p != '\0')
{
if ((p[0] == 'u' || p[0] == 'g' || p[0] == 'o') && p[1] == '=')
{
int can_read = 0, can_write = 0, can_execute = 0;
const char *q = p + 2;
while (*q != ',' && *q != '\0')
{
if (*q == 'r')
can_read = 1;
else if (*q == 'w')
can_write = 1;
else if (*q == 'x')
can_execute = 1;
++q;
}
if (p[0] == 'u')
{
if (can_read)
mode |= S_IRUSR;
if (can_write)
mode |= S_IWUSR;
if (can_execute)
mode |= S_IXUSR;
}
else if (p[0] == 'g')
{
if (can_read)
mode |= S_IRGRP;
if (can_write)
mode |= S_IWGRP;
if (can_execute)
mode |= S_IXGRP;
}
else if (p[0] == 'o')
{
if (can_read)
mode |= S_IROTH;
if (can_write)
mode |= S_IWOTH;
if (can_execute)
mode |= S_IXOTH;
}
}
while (*p != ',' && *p != '\0')
++p;
if (*p == ',')
++p;
}
if (respect_umask)
{
oumask = umask (0);
(void) umask (oumask);
mode &= ~oumask;
}
if (chmod (filename, mode) < 0)
return errno;
return 0;
#endif
}
#endif
#ifdef CLIENT_SUPPORT
int client_prune_dirs;
static List *ignlist = NULL;
static struct buffer *global_to_server;
static struct buffer *global_from_server;
static size_t
read_line_via (struct buffer *via_from_buffer, struct buffer *via_to_buffer,
char **resultp)
{
int status;
char *result;
size_t len;
status = buf_flush (via_to_buffer, 1);
if (status != 0)
error (1, status, "writing to server");
status = buf_read_line (via_from_buffer, &result, &len);
if (status != 0)
{
if (status == -1)
error (1, 0,
"end of file from server (consult above messages if any)");
else if (status == -2)
error (1, 0, "out of memory");
else
error (1, status, "reading from server");
}
if (resultp)
*resultp = result;
else
free (result);
return len;
}
static size_t
read_line (char **resultp)
{
return read_line_via (global_from_server, global_to_server, resultp);
}
#endif
#if defined(CLIENT_SUPPORT) || defined(SERVER_SUPPORT)
int gzip_level;
int file_gzip_level;
#endif
#ifdef CLIENT_SUPPORT
static bool force_gzip;
static char *toplevel_repos;
char *toplevel_wd;
static void
handle_ok (char *args, size_t len)
{
return;
}
static void
handle_error (char *args, size_t len)
{
int something_printed;
char *p = strchr (args, ' ');
if (!p)
{
error (0, 0, "invalid data from cvs server");
return;
}
++p;
len -= p - args;
something_printed = 0;
for (; len > 0; --len)
{
something_printed = 1;
putc (*p++, stderr);
}
if (something_printed)
putc ('\n', stderr);
}
static void
handle_valid_requests (char *args, size_t len)
{
char *p = args;
char *q;
struct request *rq;
do
{
q = strchr (p, ' ');
if (q)
*q++ = '\0';
for (rq = requests; rq->name; ++rq)
{
if (!strcmp (rq->name, p))
break;
}
if (!rq->name)
;
else
{
if (rq->flags & RQ_ENABLEME)
{
send_to_server (rq->name, 0);
send_to_server ("\012", 0);
}
else
rq->flags |= RQ_SUPPORTED;
}
p = q;
} while (q);
for (rq = requests; rq->name; ++rq)
{
if ((rq->flags & RQ_SUPPORTED)
|| (rq->flags & RQ_ENABLEME))
continue;
if (rq->flags & RQ_ESSENTIAL)
error (1, 0, "request `%s' not supported by server", rq->name);
}
}
static void
handle_force_gzip (char *args, size_t len)
{
force_gzip = true;
}
static bool referred_since_last_redirect = false;
static bool free_client_referrer = false;
static void
handle_referrer (char *args, size_t len)
{
TRACE (TRACE_FUNCTION, "handle_referrer (%s)", args);
client_referrer = parse_cvsroot (args);
referred_since_last_redirect = true;
free_client_referrer = true;
}
static void
handle_redirect (char *args, size_t len)
{
static List *redirects = NULL;
TRACE (TRACE_FUNCTION, "handle_redirect (%s)", args);
if (redirects && findnode (redirects, args))
error (1, 0, "`Redirect' loop detected. Server misconfiguration?");
else
{
if (!redirects) redirects = getlist();
push_string (redirects, xstrdup (args));
}
if (referred_since_last_redirect)
referred_since_last_redirect = false;
else
{
if (free_client_referrer) free (client_referrer);
client_referrer = current_parsed_root;
free_client_referrer = false;
}
current_parsed_root = parse_cvsroot (args);
if (!current_parsed_root)
error (1, 0, "Server requested redirect to invalid root: `%s'",
args);
}
static
int path_list_prefixed (Node *p, void *closure)
{
const char *questionable = closure;
const char *prefix = p->key;
if (strncmp (prefix, questionable, strlen (prefix))) return 0;
questionable += strlen (prefix);
while (ISSLASH (*questionable)) questionable++;
if (*questionable == '\0') return 1;
return pathname_levels (questionable);
}
static
int is_valid_client_path (const char *pathname)
{
if (ISABSOLUTE (pathname)) return 0;
if (pathname_levels (pathname) == 0) return 1;
if (!uppaths) return 0;
return walklist (uppaths, path_list_prefixed, (void *)pathname);
}
static void
call_in_directory (const char *pathname,
void (*func) (void *, List *, const char *, const char *),
void *data)
{
List *last_entries = NULL;
char *dir_name;
char *filename;
char *short_pathname;
char *p;
char *reposname;
char *short_repos;
char *reposdirname;
char *rdirp;
int reposdirname_absolute;
int newdir = 0;
assert (pathname);
reposname = NULL;
read_line (&reposname);
assert (reposname);
reposdirname_absolute = 0;
if (strncmp (reposname, toplevel_repos, strlen (toplevel_repos)))
{
reposdirname_absolute = 1;
short_repos = reposname;
}
else
{
short_repos = reposname + strlen (toplevel_repos) + 1;
if (short_repos[-1] != '/')
{
reposdirname_absolute = 1;
short_repos = reposname;
}
}
filename = strrchr (short_repos, '/');
if (!filename)
filename = short_repos;
else
++filename;
short_pathname = xmalloc (strlen (pathname) + strlen (filename) + 5);
strcpy (short_pathname, pathname);
strcat (short_pathname, filename);
if (!is_valid_client_path (short_pathname))
{
error (0, 0,
"Server attempted to update a file via an invalid pathname:");
error (1, 0, "`%s'.", short_pathname);
}
reposdirname = xstrdup (short_repos);
p = strrchr (reposdirname, '/');
if (!p)
{
reposdirname = xrealloc (reposdirname, 2);
reposdirname[0] = '.'; reposdirname[1] = '\0';
}
else
*p = '\0';
dir_name = xstrdup (pathname);
p = strrchr (dir_name, '/');
if (!p)
{
dir_name = xrealloc (dir_name, 2);
dir_name[0] = '.'; dir_name[1] = '\0';
}
else
*p = '\0';
if (client_prune_dirs)
add_prune_candidate (dir_name);
if (!toplevel_wd)
{
toplevel_wd = xgetcwd ();
if (!toplevel_wd)
error (1, errno, "could not get working directory");
}
if (CVS_CHDIR (toplevel_wd) < 0)
error (1, errno, "could not chdir to %s", toplevel_wd);
if (
!reposdirname_absolute
&& !strcmp (dir_name, ".")
&& ! isdir (CVSADM))
{
char *repo;
char *r;
newdir = 1;
assert (*toplevel_repos);
repo = xmalloc (strlen (toplevel_repos)
+ 10);
strcpy (repo, toplevel_repos);
r = repo + strlen (repo);
if (r[-1] != '.' || r[-2] != '/')
strcpy (r, "/.");
Create_Admin (".", ".", repo, NULL, NULL, 0, 1, 1);
free (repo);
}
if (CVS_CHDIR (dir_name) < 0)
{
char *dir;
char *dirp;
if (! existence_error (errno))
error (1, errno, "could not chdir to %s", dir_name);
newdir = 1;
dir = xmalloc (strlen (dir_name) + 1);
dirp = dir_name;
rdirp = reposdirname;
do
{
dirp = strchr (dirp, '/');
if (dirp)
{
strncpy (dir, dir_name, dirp - dir_name);
dir[dirp - dir_name] = '\0';
++dirp;
if (!rdirp)
;
else
rdirp = strchr (rdirp, '/');
}
else
{
rdirp = NULL;
strcpy (dir, dir_name);
}
if (fncmp (dir, CVSADM) == 0)
{
error (0, 0, "cannot create a directory named %s", dir);
error (0, 0, "because CVS uses \"%s\" for its own uses",
CVSADM);
error (1, 0, "rename the directory and try again");
}
if (mkdir_if_needed (dir))
{
}
else if (!strcmp (cvs_cmd_name, "export"))
;
else
{
char *repo;
char *r, *b;
repo = xmalloc (strlen (reposdirname)
+ strlen (toplevel_repos)
+ 80);
if (reposdirname_absolute)
r = repo;
else
{
strcpy (repo, toplevel_repos);
strcat (repo, "/");
r = repo + strlen (repo);
}
if (rdirp)
{
error (0, 0, "\
warning: server is not creating directories one at a time");
strncpy (r, reposdirname, rdirp - reposdirname);
r[rdirp - reposdirname] = '\0';
}
else
strcpy (r, reposdirname);
Create_Admin (dir, dir, repo, NULL, NULL, 0, 0, 1);
free (repo);
b = strrchr (dir, '/');
if (!b)
Subdir_Register (NULL, NULL, dir);
else
{
*b = '\0';
Subdir_Register (NULL, dir, b + 1);
*b = '/';
}
}
if (rdirp)
{
++rdirp;
}
} while (dirp);
free (dir);
if (CVS_CHDIR (dir_name) < 0)
error (1, errno, "could not chdir to %s", dir_name);
}
else if (!strcmp (cvs_cmd_name, "export"))
;
else if (!isdir (CVSADM))
{
char *repo;
if (reposdirname_absolute)
repo = reposdirname;
else
repo = Xasprintf ("%s/%s", toplevel_repos, reposdirname);
Create_Admin (".", ".", repo, NULL, NULL, 0, 1, 1);
if (repo != reposdirname)
free (repo);
}
if (strcmp (cvs_cmd_name, "export"))
{
last_entries = Entries_Open (0, dir_name);
if (newdir)
Subdirs_Known (last_entries);
else
{
List *dirlist;
dirlist = Find_Directories (NULL, W_LOCAL, last_entries);
dellist (&dirlist);
}
}
free (reposdirname);
(*func) (data, last_entries, short_pathname, filename);
if (last_entries)
Entries_Close (last_entries);
free (dir_name);
free (short_pathname);
free (reposname);
}
static void
copy_a_file (void *data, List *ent_list, const char *short_pathname,
const char *filename)
{
char *newname;
read_line (&newname);
#ifdef USE_VMS_FILENAMES
{
char *p;
for(p = newname; *p; p++)
if(*p == '.' || *p == '#') *p = '_';
}
#endif
if (last_component (newname) != newname)
error (1, 0, "protocol error: Copy-file tried to specify directory");
if (unlink_file (newname) && !existence_error (errno))
error (0, errno, "unable to remove %s", newname);
copy_file (filename, newname);
free (newname);
}
static void
handle_copy_file (char *args, size_t len)
{
call_in_directory (args, copy_a_file, NULL);
}
static void
read_counted_file (char *filename, char *fullname)
{
char *size_string;
size_t size;
char *buf;
char *pread;
char *pwrite;
size_t nread;
size_t nwrite;
FILE *fp;
read_line (&size_string);
if (size_string[0] == 'z')
error (1, 0, "\
protocol error: compressed files not supported for that operation");
size = atoi (size_string);
free (size_string);
buf = xmalloc (size);
fp = CVS_FOPEN (filename, "wb");
if (!fp)
error (1, errno, "cannot write %s", fullname);
nread = size;
nwrite = 0;
pread = buf;
pwrite = buf;
while (nread > 0 || nwrite > 0)
{
size_t n;
if (nread > 0)
{
n = try_read_from_server (pread, nread);
nread -= n;
pread += n;
nwrite += n;
}
if (nwrite > 0)
{
n = fwrite (pwrite, sizeof *pwrite, nwrite, fp);
if (ferror (fp))
error (1, errno, "cannot write %s", fullname);
nwrite -= n;
pwrite += n;
}
}
free (buf);
if (fclose (fp) < 0)
error (1, errno, "cannot close %s", fullname);
}
static int updated_seen;
static char *updated_fname;
static struct
{
int seen;
int conflicts;
char *mergetag1;
char *mergetag2;
char *repository;
} importmergecmd;
static bool failure_exit;
static time_t last_register_time;
static int stored_checksum_valid;
static unsigned char stored_checksum[16];
static void
handle_checksum (char *args, size_t len)
{
char *s;
char buf[3];
int i;
if (stored_checksum_valid)
error (1, 0, "Checksum received before last one was used");
s = args;
buf[2] = '\0';
for (i = 0; i < 16; i++)
{
char *bufend;
buf[0] = *s++;
buf[1] = *s++;
stored_checksum[i] = (char) strtol (buf, &bufend, 16);
if (bufend != buf + 2)
break;
}
if (i < 16 || *s != '\0')
error (1, 0, "Invalid Checksum response: `%s'", args);
stored_checksum_valid = 1;
}
static char *stored_mode;
static void
handle_mode (char *args, size_t len)
{
if (stored_mode)
error (1, 0, "protocol error: duplicate Mode");
stored_mode = xstrdup (args);
}
static int stored_modtime_valid;
static time_t stored_modtime;
static void
handle_mod_time (char *args, size_t len)
{
struct timespec newtime;
if (stored_modtime_valid)
error (0, 0, "protocol error: duplicate Mod-time");
if (get_date (&newtime, args, NULL))
{
stored_modtime = newtime.tv_sec;
stored_modtime_valid = 1;
}
else
error (0, 0, "protocol error: cannot parse date %s", args);
}
char **failed_patches;
int failed_patches_count;
struct update_entries_data
{
enum {
UPDATE_ENTRIES_CHECKIN,
UPDATE_ENTRIES_UPDATE,
UPDATE_ENTRIES_PATCH,
UPDATE_ENTRIES_RCS_DIFF
} contents;
enum {
UPDATE_ENTRIES_EXISTING,
UPDATE_ENTRIES_NEW,
UPDATE_ENTRIES_EXISTING_OR_NEW
} existp;
char *timestamp;
};
static void
update_entries (void *data_arg, List *ent_list, const char *short_pathname,
const char *filename)
{
char *entries_line;
struct update_entries_data *data = data_arg;
char *cp;
char *user;
char *vn;
char *ts;
char *options = NULL;
char *tag = NULL;
char *date = NULL;
char *tag_or_date;
char *scratch_entries = NULL;
int bin;
#ifdef UTIME_EXPECTS_WRITABLE
int change_it_back = 0;
#endif
read_line (&entries_line);
scratch_entries = xstrdup (entries_line);
if (scratch_entries[0] != '/')
error (1, 0, "bad entries line `%s' from server", entries_line);
user = scratch_entries + 1;
if (!(cp = strchr (user, '/')))
error (1, 0, "bad entries line `%s' from server", entries_line);
*cp++ = '\0';
vn = cp;
if (!(cp = strchr (vn, '/')))
error (1, 0, "bad entries line `%s' from server", entries_line);
*cp++ = '\0';
ts = cp;
if (!(cp = strchr (ts, '/')))
error (1, 0, "bad entries line `%s' from server", entries_line);
*cp++ = '\0';
options = cp;
if (!(cp = strchr (options, '/')))
error (1, 0, "bad entries line `%s' from server", entries_line);
*cp++ = '\0';
tag_or_date = cp;
cp = strchr (tag_or_date, '/');
if (cp)
*cp = '\0';
if (*tag_or_date == 'T')
tag = tag_or_date + 1;
else if (*tag_or_date == 'D')
date = tag_or_date + 1;
if (data->contents == UPDATE_ENTRIES_UPDATE
|| data->contents == UPDATE_ENTRIES_PATCH
|| data->contents == UPDATE_ENTRIES_RCS_DIFF)
{
char *size_string;
char *mode_string;
int size;
char *buf;
char *temp_filename;
int use_gzip;
int patch_failed;
read_line (&mode_string);
read_line (&size_string);
if (size_string[0] == 'z')
{
use_gzip = 1;
size = atoi (size_string+1);
}
else
{
use_gzip = 0;
size = atoi (size_string);
}
free (size_string);
if (data->existp == UPDATE_ENTRIES_EXISTING
&& !isfile (filename))
error (0, 0, "warning: %s unexpectedly disappeared",
short_pathname);
if (data->existp == UPDATE_ENTRIES_NEW
&& isfile (filename))
{
size_t nread;
size_t toread;
size_t usize;
char buf[8192];
error (0, 0, "move away `%s'; it is in the way", short_pathname);
if (updated_fname)
{
cvs_output ("C ", 0);
cvs_output (updated_fname, 0);
cvs_output ("\n", 1);
}
failure_exit = true;
discard_file_and_return:
usize = size;
nread = 0;
while (nread < usize)
{
toread = usize - nread;
if (toread > sizeof buf)
toread = sizeof buf;
nread += try_read_from_server (buf, toread);
if (nread == usize)
break;
}
free (mode_string);
free (scratch_entries);
free (entries_line);
if (stored_mode)
{
free (stored_mode);
stored_mode = NULL;
}
stored_modtime_valid = 0;
stored_checksum_valid = 0;
if (updated_fname)
{
free (updated_fname);
updated_fname = NULL;
}
return;
}
temp_filename = xmalloc (strlen (filename) + 80);
#ifdef USE_VMS_FILENAMES
sprintf (temp_filename, "%s_new_", filename);
#else
#ifdef _POSIX_NO_TRUNC
sprintf (temp_filename, ".new.XXXXXX");
mktemp (temp_filename);
#else
sprintf (temp_filename, ".new.%s", filename);
#endif
#endif
buf = xmalloc (size);
if (options)
bin = !strcmp (options, "-kb");
else
bin = 0;
if (data->contents == UPDATE_ENTRIES_RCS_DIFF)
{
if (use_gzip)
error (1, 0,
"server error: gzip invalid with RCS change text");
read_from_server (buf, size);
}
else
{
int fd;
fd = CVS_OPEN (temp_filename,
(O_WRONLY | O_CREAT | O_TRUNC
| (bin ? OPEN_BINARY : 0)),
0777);
if (fd < 0)
{
error (0, errno, "cannot write %s", short_pathname);
free (temp_filename);
free (buf);
goto discard_file_and_return;
}
if (size > 0)
{
read_from_server (buf, size);
if (use_gzip)
{
if (gunzip_and_write (fd, short_pathname,
(unsigned char *) buf, size))
error (1, 0, "aborting due to compression error");
}
else if (write (fd, buf, size) != size)
error (1, errno, "writing %s", short_pathname);
}
if (close (fd) < 0)
error (1, errno, "writing %s", short_pathname);
}
if (updated_fname)
{
cvs_output ("U ", 0);
cvs_output (updated_fname, 0);
cvs_output ("\n", 1);
free (updated_fname);
updated_fname = 0;
}
patch_failed = 0;
if (data->contents == UPDATE_ENTRIES_UPDATE)
{
rename_file (temp_filename, filename);
}
else if (data->contents == UPDATE_ENTRIES_PATCH)
{
patch_failed = 1;
}
else
{
char *filebuf;
size_t filebufsize;
size_t nread;
char *patchedbuf;
size_t patchedlen;
if (!isfile (filename))
error (1, 0, "patch original file %s does not exist",
short_pathname);
filebuf = NULL;
filebufsize = 0;
nread = 0;
get_file (filename, short_pathname, bin ? FOPEN_BINARY_READ : "r",
&filebuf, &filebufsize, &nread);
if (! rcs_change_text (short_pathname, filebuf, nread, buf, size,
&patchedbuf, &patchedlen))
patch_failed = 1;
else
{
if (stored_checksum_valid)
{
unsigned char checksum[16];
md5_buffer (patchedbuf, patchedlen, checksum);
if (memcmp (checksum, stored_checksum, 16) != 0)
{
error (0, 0,
"checksum failure after patch to %s; will refetch",
short_pathname);
patch_failed = 1;
}
stored_checksum_valid = 0;
}
if (! patch_failed)
{
FILE *e;
e = xfopen (temp_filename,
bin ? FOPEN_BINARY_WRITE : "w");
if (fwrite (patchedbuf, sizeof *patchedbuf, patchedlen, e)
!= patchedlen)
error (1, errno, "cannot write %s", temp_filename);
if (fclose (e) == EOF)
error (1, errno, "cannot close %s", temp_filename);
rename_file (temp_filename, filename);
}
free (patchedbuf);
}
free (filebuf);
}
free (temp_filename);
if (stored_checksum_valid && ! patch_failed)
{
FILE *e;
struct md5_ctx context;
unsigned char buf[8192];
unsigned len;
unsigned char checksum[16];
e = CVS_FOPEN (filename, "r");
if (!e)
error (1, errno, "could not open %s", short_pathname);
md5_init_ctx (&context);
while ((len = fread (buf, 1, sizeof buf, e)) != 0)
md5_process_bytes (buf, len, &context);
if (ferror (e))
error (1, errno, "could not read %s", short_pathname);
md5_finish_ctx (&context, checksum);
fclose (e);
stored_checksum_valid = 0;
if (memcmp (checksum, stored_checksum, 16) != 0)
{
if (data->contents != UPDATE_ENTRIES_PATCH)
error (1, 0, "checksum failure on %s",
short_pathname);
error (0, 0,
"checksum failure after patch to %s; will refetch",
short_pathname);
patch_failed = 1;
}
}
if (patch_failed)
{
failed_patches = xnrealloc (failed_patches,
failed_patches_count + 1,
sizeof (char *));
failed_patches[failed_patches_count] = xstrdup (short_pathname);
++failed_patches_count;
stored_checksum_valid = 0;
free (mode_string);
free (buf);
free (scratch_entries);
free (entries_line);
return;
}
{
int status = change_mode (filename, mode_string, 1);
if (status != 0)
error (0, status, "cannot change mode of %s", short_pathname);
}
free (mode_string);
free (buf);
}
if (stored_mode)
{
change_mode (filename, stored_mode, 1);
free (stored_mode);
stored_mode = NULL;
}
if (stored_modtime_valid)
{
struct utimbuf t;
memset (&t, 0, sizeof (t));
t.modtime = stored_modtime;
(void) time (&t.actime);
#ifdef UTIME_EXPECTS_WRITABLE
if (!iswritable (filename))
{
xchmod (filename, 1);
change_it_back = 1;
}
#endif
if (utime (filename, &t) < 0)
error (0, errno, "cannot set time on %s", filename);
#ifdef UTIME_EXPECTS_WRITABLE
if (change_it_back)
{
xchmod (filename, 0);
change_it_back = 0;
}
#endif
stored_modtime_valid = 0;
}
if (strcmp (cvs_cmd_name, "export"))
{
char *local_timestamp;
char *file_timestamp;
(void) time (&last_register_time);
local_timestamp = data->timestamp;
if (!local_timestamp || ts[0] == '+')
file_timestamp = time_stamp (filename);
else
file_timestamp = NULL;
if (vn[0] == '\0' || !strcmp (vn, "0") || vn[0] == '-')
local_timestamp = "dummy timestamp";
else if (!local_timestamp)
{
local_timestamp = file_timestamp;
if (!strcmp (cvs_cmd_name, "commit"))
mark_up_to_date (filename);
}
Register (ent_list, filename, vn, local_timestamp,
options, tag, date, ts[0] == '+' ? file_timestamp : NULL);
if (file_timestamp)
free (file_timestamp);
}
free (scratch_entries);
free (entries_line);
}
static void
handle_checked_in (char *args, size_t len)
{
struct update_entries_data dat;
dat.contents = UPDATE_ENTRIES_CHECKIN;
dat.existp = UPDATE_ENTRIES_EXISTING_OR_NEW;
dat.timestamp = NULL;
call_in_directory (args, update_entries, &dat);
}
static void
handle_new_entry (char *args, size_t len)
{
struct update_entries_data dat;
dat.contents = UPDATE_ENTRIES_CHECKIN;
dat.existp = UPDATE_ENTRIES_EXISTING_OR_NEW;
dat.timestamp = "dummy timestamp from new-entry";
call_in_directory (args, update_entries, &dat);
}
static void
handle_updated (char *args, size_t len)
{
struct update_entries_data dat;
dat.contents = UPDATE_ENTRIES_UPDATE;
dat.existp = UPDATE_ENTRIES_EXISTING_OR_NEW;
dat.timestamp = NULL;
call_in_directory (args, update_entries, &dat);
}
static void
handle_created (char *args, size_t len)
{
struct update_entries_data dat;
dat.contents = UPDATE_ENTRIES_UPDATE;
dat.existp = UPDATE_ENTRIES_NEW;
dat.timestamp = NULL;
call_in_directory (args, update_entries, &dat);
}
static void
handle_update_existing (char *args, size_t len)
{
struct update_entries_data dat;
dat.contents = UPDATE_ENTRIES_UPDATE;
dat.existp = UPDATE_ENTRIES_EXISTING;
dat.timestamp = NULL;
call_in_directory (args, update_entries, &dat);
}
static void
handle_merged (char *args, size_t len)
{
struct update_entries_data dat;
dat.contents = UPDATE_ENTRIES_UPDATE;
dat.existp = UPDATE_ENTRIES_EXISTING_OR_NEW;
dat.timestamp = "Result of merge";
call_in_directory (args, update_entries, &dat);
}
static void
handle_patched (char *args, size_t len)
{
struct update_entries_data dat;
dat.contents = UPDATE_ENTRIES_PATCH;
dat.existp = UPDATE_ENTRIES_EXISTING_OR_NEW;
dat.timestamp = NULL;
call_in_directory (args, update_entries, &dat);
}
static void
handle_rcs_diff (char *args, size_t len)
{
struct update_entries_data dat;
dat.contents = UPDATE_ENTRIES_RCS_DIFF;
dat.existp = UPDATE_ENTRIES_EXISTING_OR_NEW;
dat.timestamp = NULL;
call_in_directory (args, update_entries, &dat);
}
static void
remove_entry (void *data, List *ent_list, const char *short_pathname,
const char *filename)
{
Scratch_Entry (ent_list, filename);
}
static void
handle_remove_entry (char *args, size_t len)
{
call_in_directory (args, remove_entry, NULL);
}
static void
remove_entry_and_file (void *data, List *ent_list, const char *short_pathname,
const char *filename)
{
Scratch_Entry (ent_list, filename);
if (unlink_file (filename) < 0)
error (0, errno, "unable to remove %s", short_pathname);
}
static void
handle_removed (char *args, size_t len)
{
call_in_directory (args, remove_entry_and_file, NULL);
}
static int
is_cvsroot_level (char *pathname)
{
if (strcmp (toplevel_repos, current_parsed_root->directory))
return 0;
return !strchr (pathname, '/');
}
static void
set_static (void *data, List *ent_list, const char *short_pathname,
const char *filename)
{
FILE *fp;
fp = xfopen (CVSADM_ENTSTAT, "w+");
if (fclose (fp) == EOF)
error (1, errno, "cannot close %s", CVSADM_ENTSTAT);
}
static void
handle_set_static_directory (char *args, size_t len)
{
if (!strcmp (cvs_cmd_name, "export"))
{
read_line (NULL);
return;
}
call_in_directory (args, set_static, NULL);
}
static void
clear_static (void *data, List *ent_list, const char *short_pathname,
const char *filename)
{
if (unlink_file (CVSADM_ENTSTAT) < 0 && ! existence_error (errno))
error (1, errno, "cannot remove file %s", CVSADM_ENTSTAT);
}
static void
handle_clear_static_directory (char *pathname, size_t len)
{
if (!strcmp (cvs_cmd_name, "export"))
{
read_line (NULL);
return;
}
if (is_cvsroot_level (pathname))
{
return;
}
call_in_directory (pathname, clear_static, NULL);
}
static void
set_sticky (void *data, List *ent_list, const char *short_pathname,
const char *filename)
{
char *tagspec;
FILE *f;
read_line (&tagspec);
f = CVS_FOPEN (CVSADM_TAG, "w+");
if (!f)
{
error (0, errno, "cannot open %s", CVSADM_TAG);
free (tagspec);
return;
}
if (fprintf (f, "%s\n", tagspec) < 0)
error (1, errno, "writing %s", CVSADM_TAG);
if (fclose (f) == EOF)
error (1, errno, "closing %s", CVSADM_TAG);
free (tagspec);
}
static void
handle_set_sticky (char *pathname, size_t len)
{
if (!strcmp (cvs_cmd_name, "export"))
{
read_line (NULL);
read_line (NULL);
return;
}
if (is_cvsroot_level (pathname))
{
read_line (NULL);
read_line (NULL);
return;
}
call_in_directory (pathname, set_sticky, NULL);
}
static void
clear_sticky (void *data, List *ent_list, const char *short_pathname,
const char *filename)
{
if (unlink_file (CVSADM_TAG) < 0 && ! existence_error (errno))
error (1, errno, "cannot remove %s", CVSADM_TAG);
}
static void
handle_clear_sticky (char *pathname, size_t len)
{
if (!strcmp (cvs_cmd_name, "export"))
{
read_line (NULL);
return;
}
if (is_cvsroot_level (pathname))
{
return;
}
call_in_directory (pathname, clear_sticky, NULL);
}
static void
handle_edit_file (char *pathname, size_t len)
{
call_in_directory (pathname, edit_file, NULL);
}
static void
template (void *data, List *ent_list, const char *short_pathname,
const char *filename)
{
char *buf = Xasprintf ("%s/%s", short_pathname, CVSADM_TEMPLATE);
read_counted_file (CVSADM_TEMPLATE, buf);
free (buf);
}
static void
handle_template (char *pathname, size_t len)
{
call_in_directory (pathname, template, NULL);
}
static void
clear_template (void *data, List *ent_list, const char *short_pathname,
const char *filename)
{
if (unlink_file (CVSADM_TEMPLATE) < 0 && ! existence_error (errno))
error (1, errno, "cannot remove %s", CVSADM_TEMPLATE);
}
static void
handle_clear_template (char *pathname, size_t len)
{
call_in_directory (pathname, clear_template, NULL);
}
struct save_dir {
char *dir;
struct save_dir *next;
};
struct save_dir *prune_candidates;
static void
add_prune_candidate (const char *dir)
{
struct save_dir *p;
if ((dir[0] == '.' && dir[1] == '\0')
|| (prune_candidates && !strcmp (dir, prune_candidates->dir)))
return;
p = xmalloc (sizeof (struct save_dir));
p->dir = xstrdup (dir);
p->next = prune_candidates;
prune_candidates = p;
}
static void
process_prune_candidates (void)
{
struct save_dir *p;
struct save_dir *q;
if (toplevel_wd)
{
if (CVS_CHDIR (toplevel_wd) < 0)
error (1, errno, "could not chdir to %s", toplevel_wd);
}
for (p = prune_candidates; p; )
{
if (isemptydir (p->dir, 1))
{
char *b;
if (unlink_file_dir (p->dir) < 0)
error (0, errno, "cannot remove %s", p->dir);
b = strrchr (p->dir, '/');
if (!b)
Subdir_Deregister (NULL, NULL, p->dir);
else
{
*b = '\0';
Subdir_Deregister (NULL, p->dir, b + 1);
}
}
free (p->dir);
q = p->next;
free (p);
p = q;
}
prune_candidates = NULL;
}
static char *last_repos;
static char *last_update_dir;
static void
send_repository (const char *dir, const char *repos, const char *update_dir)
{
char *adm_name;
if (!repos)
{
error (1, 0, "no repository");
}
if (!update_dir || update_dir[0] == '\0')
update_dir = ".";
if (last_repos && !strcmp (repos, last_repos)
&& last_update_dir && !strcmp (update_dir, last_update_dir))
return;
if (client_prune_dirs)
add_prune_candidate (update_dir);
if (update_dir && *update_dir != '\0' && strcmp (update_dir, ".")
&& !findnode (dirs_sent_to_server, update_dir))
{
Node *n;
n = getnode ();
n->type = NT_UNKNOWN;
n->key = xstrdup (update_dir);
n->data = NULL;
if (addnode (dirs_sent_to_server, n))
error (1, 0, "cannot add directory %s to list", n->key);
}
adm_name = xmalloc (strlen (dir) + 80);
send_to_server ("Directory ", 0);
{
char buf[1];
const char *p = update_dir;
while (*p != '\0')
{
assert (*p != '\012');
if (ISSLASH (*p))
{
buf[0] = '/';
send_to_server (buf, 1);
}
else
{
buf[0] = *p;
send_to_server (buf, 1);
}
++p;
}
}
send_to_server ("\012", 1);
if (supported_request ("Relative-directory"))
{
const char *short_repos = Short_Repository (repos);
send_to_server (short_repos, 0);
}
else
send_to_server (repos, 0);
send_to_server ("\012", 1);
if (supported_request ("Static-directory"))
{
adm_name[0] = '\0';
if (dir[0] != '\0')
{
strcat (adm_name, dir);
strcat (adm_name, "/");
}
strcat (adm_name, CVSADM_ENTSTAT);
if (isreadable (adm_name))
{
send_to_server ("Static-directory\012", 0);
}
}
if (supported_request ("Sticky"))
{
FILE *f;
if (dir[0] == '\0')
strcpy (adm_name, CVSADM_TAG);
else
sprintf (adm_name, "%s/%s", dir, CVSADM_TAG);
f = CVS_FOPEN (adm_name, "r");
if (!f)
{
if (! existence_error (errno))
error (1, errno, "reading %s", adm_name);
}
else
{
char line[80];
char *nl = NULL;
send_to_server ("Sticky ", 0);
while (fgets (line, sizeof (line), f))
{
send_to_server (line, 0);
nl = strchr (line, '\n');
if (nl)
break;
}
if (!nl)
send_to_server ("\012", 1);
if (fclose (f) == EOF)
error (0, errno, "closing %s", adm_name);
}
}
free (adm_name);
if (last_repos) free (last_repos);
if (last_update_dir) free (last_update_dir);
last_repos = xstrdup (repos);
last_update_dir = xstrdup (update_dir);
}
void
send_a_repository (const char *dir, const char *repository,
const char *update_dir_in)
{
char *update_dir = xstrdup (update_dir_in);
if (!toplevel_repos && repository)
{
if (update_dir[0] == '\0'
|| (update_dir[0] == '.' && update_dir[1] == '\0'))
toplevel_repos = xstrdup (repository);
else
{
if (update_dir[0] == '/')
toplevel_repos = Name_Repository (update_dir, update_dir);
else
{
int repository_len, update_dir_len;
strip_trailing_slashes (update_dir);
repository_len = strlen (repository);
update_dir_len = strlen (update_dir);
if (repository_len > update_dir_len
&& !strcmp (repository + repository_len - update_dir_len,
update_dir)
&& ((size_t)(repository_len - update_dir_len)
> strlen (current_parsed_root->directory)))
{
toplevel_repos = xmalloc (repository_len - update_dir_len);
strncpy (toplevel_repos, repository,
repository_len - update_dir_len - 1);
toplevel_repos[repository_len - update_dir_len - 1] = '\0';
}
else
{
toplevel_repos = xstrdup (current_parsed_root->directory);
}
}
}
}
send_repository (dir, repository, update_dir);
free (update_dir);
}
static void
notified_a_file (void *data, List *ent_list, const char *short_pathname,
const char *filename)
{
FILE *fp;
FILE *newf;
size_t line_len = 8192;
char *line = xmalloc (line_len);
char *cp;
int nread;
int nwritten;
char *p;
fp = xfopen (CVSADM_NOTIFY, "r");
if (getline (&line, &line_len, fp) < 0)
{
if (feof (fp))
error (0, 0, "cannot read %s: end of file", CVSADM_NOTIFY);
else
error (0, errno, "cannot read %s", CVSADM_NOTIFY);
goto error_exit;
}
cp = strchr (line, '\t');
if (!cp)
{
error (0, 0, "malformed %s file", CVSADM_NOTIFY);
goto error_exit;
}
*cp = '\0';
if (strcmp (filename, line + 1))
error (0, 0, "protocol error: notified %s, expected %s", filename,
line + 1);
if (getline (&line, &line_len, fp) < 0)
{
if (feof (fp))
{
free (line);
if (fclose (fp) < 0)
error (0, errno, "cannot close %s", CVSADM_NOTIFY);
if ( CVS_UNLINK (CVSADM_NOTIFY) < 0)
error (0, errno, "cannot remove %s", CVSADM_NOTIFY);
return;
}
else
{
error (0, errno, "cannot read %s", CVSADM_NOTIFY);
goto error_exit;
}
}
newf = xfopen (CVSADM_NOTIFYTMP, "w");
if (fputs (line, newf) < 0)
{
error (0, errno, "cannot write %s", CVSADM_NOTIFYTMP);
goto error2;
}
while ((nread = fread (line, 1, line_len, fp)) > 0)
{
p = line;
while ((nwritten = fwrite (p, sizeof *p, nread, newf)) > 0)
{
nread -= nwritten;
p += nwritten;
}
if (ferror (newf))
{
error (0, errno, "cannot write %s", CVSADM_NOTIFYTMP);
goto error2;
}
}
if (ferror (fp))
{
error (0, errno, "cannot read %s", CVSADM_NOTIFY);
goto error2;
}
if (fclose (newf) < 0)
{
error (0, errno, "cannot close %s", CVSADM_NOTIFYTMP);
goto error_exit;
}
free (line);
if (fclose (fp) < 0)
{
error (0, errno, "cannot close %s", CVSADM_NOTIFY);
return;
}
{
int saved_noexec = noexec;
noexec = 0;
rename_file (CVSADM_NOTIFYTMP, CVSADM_NOTIFY);
noexec = saved_noexec;
}
return;
error2:
(void)fclose (newf);
error_exit:
free (line);
(void)fclose (fp);
}
static void
handle_notified (char *args, size_t len)
{
call_in_directory (args, notified_a_file, NULL);
}
static int modules_count;
static int modules_allocated;
static char **modules_vector;
static void
handle_module_expansion (char *args, size_t len)
{
if (!modules_vector)
{
modules_allocated = 1;
modules_vector = xnmalloc (modules_allocated,
sizeof (modules_vector[0]));
}
else if (modules_count >= modules_allocated)
{
modules_allocated *= 2;
modules_vector = xnrealloc (modules_vector,
modules_allocated,
sizeof (modules_vector[0]));
}
modules_vector[modules_count] = xstrdup (args);
++modules_count;
}
static int module_argc;
static char **module_argv;
void
client_expand_modules (int argc, char **argv, int local)
{
int errs;
int i;
module_argc = argc;
module_argv = xnmalloc (argc + 1, sizeof (module_argv[0]));
for (i = 0; i < argc; ++i)
module_argv[i] = xstrdup (argv[i]);
module_argv[argc] = NULL;
for (i = 0; i < argc; ++i)
send_arg (argv[i]);
send_a_repository ("", current_parsed_root->directory, "");
send_to_server ("expand-modules\012", 0);
errs = get_server_responses ();
if (last_repos) free (last_repos);
last_repos = NULL;
if (last_update_dir) free (last_update_dir);
last_update_dir = NULL;
if (errs)
error (errs, 0, "cannot expand modules");
}
void
client_send_expansions (int local, char *where, int build_dirs)
{
int i;
char *argv[1];
send_file_names (module_argc, module_argv, 0);
for (i = 0; i < modules_count; ++i)
{
argv[0] = where ? where : modules_vector[i];
if (isfile (argv[0]))
send_files (1, argv, local, 0, build_dirs ? SEND_BUILD_DIRS : 0);
}
send_a_repository ("", current_parsed_root->directory, "");
}
void
client_nonexpanded_setup (void)
{
send_a_repository ("", current_parsed_root->directory, "");
}
static void
handle_wrapper_rcs_option (char *args, size_t len)
{
char *p;
p = strchr (args, ' ');
if (!p)
goto handle_error;
if (*++p != '-'
|| *++p != 'k'
|| *++p != ' '
|| *++p != '\'')
goto handle_error;
if (!strchr (p, '\''))
goto handle_error;
wrap_add (args, 0);
return;
handle_error:
error (0, errno, "protocol error: ignoring invalid wrappers %s", args);
}
static void
handle_m (char *args, size_t len)
{
fflush (stderr);
fwrite (args, sizeof *args, len, stdout);
putc ('\n', stdout);
}
static void
handle_mbinary (char *args, size_t len)
{
char *size_string;
size_t size;
size_t totalread;
size_t nread;
size_t toread;
char buf[8192];
read_line (&size_string);
size = atoi (size_string);
free (size_string);
totalread = 0;
while (totalread < size)
{
toread = size - totalread;
if (toread > sizeof buf)
toread = sizeof buf;
nread = try_read_from_server (buf, toread);
cvs_output_binary (buf, nread);
totalread += nread;
}
}
static void
handle_e (char *args, size_t len)
{
fflush (stdout);
fwrite (args, sizeof *args, len, stderr);
putc ('\n', stderr);
}
static void
handle_f (char *args, size_t len)
{
fflush (stderr);
}
static void
handle_mt (char *args, size_t len)
{
char *p;
char *tag = args;
char *text;
fflush (stderr);
p = strchr (args, ' ');
if (!p)
text = NULL;
else
{
*p++ = '\0';
text = p;
}
switch (tag[0])
{
case '+':
if (!strcmp (tag, "+updated"))
updated_seen = 1;
else if (!strcmp (tag, "+importmergecmd"))
importmergecmd.seen = 1;
break;
case '-':
if (!strcmp (tag, "-updated"))
updated_seen = 0;
else if (!strcmp (tag, "-importmergecmd"))
{
char buf[80];
if (importmergecmd.conflicts == 0
|| !importmergecmd.mergetag1
|| !importmergecmd.mergetag2
|| !importmergecmd.repository)
{
error (0, 0,
"invalid server: incomplete importmergecmd tags");
break;
}
if (importmergecmd.conflicts == -1)
sprintf (buf, "\nNo conflicts created by this import.\n");
else
sprintf (buf, "\n%d conflicts created by this import.\n",
importmergecmd.conflicts);
cvs_output (buf, 0);
cvs_output ("Use the following command to help the merge:\n\n",
0);
cvs_output ("\t", 1);
cvs_output (program_name, 0);
if (CVSroot_cmdline)
{
cvs_output (" -d ", 0);
cvs_output (CVSroot_cmdline, 0);
}
cvs_output (" checkout -j", 0);
cvs_output (importmergecmd.mergetag1, 0);
cvs_output (" -j", 0);
cvs_output (importmergecmd.mergetag2, 0);
cvs_output (" ", 1);
cvs_output (importmergecmd.repository, 0);
cvs_output ("\n\n", 0);
importmergecmd.conflicts = 0;
free (importmergecmd.mergetag1);
importmergecmd.mergetag1 = NULL;
free (importmergecmd.mergetag2);
importmergecmd.mergetag2 = NULL;
free (importmergecmd.repository);
importmergecmd.repository = NULL;
importmergecmd.seen = 0;
}
break;
default:
if (updated_seen)
{
if (!strcmp (tag, "fname"))
{
if (updated_fname)
{
cvs_output ("U ", 0);
cvs_output (updated_fname, 0);
cvs_output ("\n", 1);
free (updated_fname);
}
updated_fname = xstrdup (text);
}
}
else if (importmergecmd.seen)
{
if (!strcmp (tag, "conflicts"))
{
if (!strcmp (text, "No"))
importmergecmd.conflicts = -1;
else
importmergecmd.conflicts = atoi (text);
}
else if (!strcmp (tag, "mergetag1"))
importmergecmd.mergetag1 = xstrdup (text);
else if (!strcmp (tag, "mergetag2"))
importmergecmd.mergetag2 = xstrdup (text);
else if (!strcmp (tag, "repository"))
importmergecmd.repository = xstrdup (text);
}
else if (!strcmp (tag, "newline"))
printf ("\n");
else if (!strcmp (tag, "date"))
{
char *date = format_date_alloc (text);
printf ("%s", date);
free (date);
}
else if (text)
printf ("%s", text);
}
}
#endif
#if defined(CLIENT_SUPPORT) || defined(SERVER_SUPPORT)
struct response responses[] =
{
#ifdef CLIENT_SUPPORT
#define RSP_LINE(n, f, t, s) {n, f, t, s}
#else
#define RSP_LINE(n, f, t, s) {n, s}
#endif
RSP_LINE("ok", handle_ok, response_type_ok, rs_essential),
RSP_LINE("error", handle_error, response_type_error, rs_essential),
RSP_LINE("Valid-requests", handle_valid_requests, response_type_normal,
rs_essential),
RSP_LINE("Force-gzip", handle_force_gzip, response_type_normal,
rs_optional),
RSP_LINE("Referrer", handle_referrer, response_type_normal, rs_optional),
RSP_LINE("Redirect", handle_redirect, response_type_redirect, rs_optional),
RSP_LINE("Checked-in", handle_checked_in, response_type_normal,
rs_essential),
RSP_LINE("New-entry", handle_new_entry, response_type_normal, rs_optional),
RSP_LINE("Checksum", handle_checksum, response_type_normal, rs_optional),
RSP_LINE("Copy-file", handle_copy_file, response_type_normal, rs_optional),
RSP_LINE("Updated", handle_updated, response_type_normal, rs_essential),
RSP_LINE("Created", handle_created, response_type_normal, rs_optional),
RSP_LINE("Update-existing", handle_update_existing, response_type_normal,
rs_optional),
RSP_LINE("Merged", handle_merged, response_type_normal, rs_essential),
RSP_LINE("Patched", handle_patched, response_type_normal, rs_optional),
RSP_LINE("Rcs-diff", handle_rcs_diff, response_type_normal, rs_optional),
RSP_LINE("Mode", handle_mode, response_type_normal, rs_optional),
RSP_LINE("Mod-time", handle_mod_time, response_type_normal, rs_optional),
RSP_LINE("Removed", handle_removed, response_type_normal, rs_essential),
RSP_LINE("Remove-entry", handle_remove_entry, response_type_normal,
rs_optional),
RSP_LINE("Set-static-directory", handle_set_static_directory,
response_type_normal,
rs_optional),
RSP_LINE("Clear-static-directory", handle_clear_static_directory,
response_type_normal,
rs_optional),
RSP_LINE("Set-sticky", handle_set_sticky, response_type_normal,
rs_optional),
RSP_LINE("Clear-sticky", handle_clear_sticky, response_type_normal,
rs_optional),
RSP_LINE("Edit-file", handle_edit_file, response_type_normal,
rs_optional),
RSP_LINE("Template", handle_template, response_type_normal,
rs_optional),
RSP_LINE("Clear-template", handle_clear_template, response_type_normal,
rs_optional),
RSP_LINE("Notified", handle_notified, response_type_normal, rs_optional),
RSP_LINE("Module-expansion", handle_module_expansion, response_type_normal,
rs_optional),
RSP_LINE("Wrapper-rcsOption", handle_wrapper_rcs_option,
response_type_normal,
rs_optional),
RSP_LINE("M", handle_m, response_type_normal, rs_essential),
RSP_LINE("Mbinary", handle_mbinary, response_type_normal, rs_optional),
RSP_LINE("E", handle_e, response_type_normal, rs_essential),
RSP_LINE("F", handle_f, response_type_normal, rs_optional),
RSP_LINE("MT", handle_mt, response_type_normal, rs_optional),
RSP_LINE(NULL, NULL, response_type_normal, rs_essential)
#undef RSP_LINE
};
#endif
#ifdef CLIENT_SUPPORT
void
send_to_server_via (struct buffer *via_buffer, const char *str, size_t len)
{
static int nbytes;
if (len == 0)
len = strlen (str);
buf_output (via_buffer, str, len);
nbytes += len;
if (nbytes >= 2 * BUFFER_DATA_SIZE)
{
int status;
status = buf_send_output (via_buffer);
if (status != 0)
error (1, status, "error writing to server");
nbytes = 0;
}
}
void
send_to_server (const char *str, size_t len)
{
send_to_server_via (global_to_server, str, len);
}
static size_t
try_read_from_server( char *buf, size_t len )
{
int status;
size_t nread;
char *data;
status = buf_read_data (global_from_server, len, &data, &nread);
if (status != 0)
{
if (status == -1)
error (1, 0,
"end of file from server (consult above messages if any)");
else if (status == -2)
error (1, 0, "out of memory");
else
error (1, status, "reading from server");
}
memcpy (buf, data, nread);
return nread;
}
void
read_from_server (char *buf, size_t len)
{
size_t red = 0;
while (red < len)
{
red += try_read_from_server (buf + red, len - red);
if (red == len)
break;
}
}
int
get_server_responses (void)
{
struct response *rs;
do
{
char *cmd;
size_t len;
len = read_line (&cmd);
for (rs = responses; rs->name; ++rs)
if (!strncmp (cmd, rs->name, strlen (rs->name)))
{
size_t cmdlen = strlen (rs->name);
if (cmd[cmdlen] == '\0')
;
else if (cmd[cmdlen] == ' ')
++cmdlen;
else
continue;
(*rs->func) (cmd + cmdlen, len - cmdlen);
break;
}
if (!rs->name)
error (0, 0,
"warning: unrecognized response `%s' from cvs server",
cmd);
free (cmd);
} while (rs->type == response_type_normal);
if (updated_fname)
{
cvs_output ("U ", 0);
cvs_output (updated_fname, 0);
cvs_output ("\n", 1);
free (updated_fname);
updated_fname = NULL;
}
if (rs->type == response_type_redirect) return 2;
if (rs->type == response_type_error) return 1;
if (failure_exit) return 1;
return 0;
}
static inline void
close_connection_to_server (struct buffer **to, struct buffer **from)
{
int status;
TRACE (TRACE_FUNCTION, "close_connection_to_server ()");
status = buf_shutdown (*to);
if (status != 0)
error (0, status, "shutting down buffer to server");
buf_free (*to);
*to = NULL;
status = buf_shutdown (*from);
if (status != 0)
error (0, status, "shutting down buffer from server");
buf_free (*from);
*from = NULL;
}
int server_started = 0;
int
get_responses_and_close (void)
{
int errs = get_server_responses ();
if (toplevel_wd)
{
if (CVS_CHDIR (toplevel_wd) < 0)
error (1, errno, "could not chdir to %s", toplevel_wd);
}
if (client_prune_dirs)
process_prune_candidates ();
close_connection_to_server (&global_to_server, &global_from_server);
server_started = 0;
if (last_register_time)
sleep_past (last_register_time);
return errs;
}
bool
supported_request (const char *name)
{
struct request *rq;
for (rq = requests; rq->name; rq++)
if (!strcmp (rq->name, name))
return (rq->flags & RQ_SUPPORTED) != 0;
error (1, 0, "internal error: testing support for unknown request?");
return 0;
}
#if defined (AUTH_CLIENT_SUPPORT) || defined (SERVER_SUPPORT) || defined (HAVE_KERBEROS) || defined (HAVE_GSSAPI)
static int
get_port_number (const char *envname, const char *portname, int defaultport)
{
struct servent *s;
char *port_s;
if (envname && (port_s = getenv (envname)))
{
int port = atoi (port_s);
if (port <= 0)
{
error (0, 0, "%s must be a positive integer! If you", envname);
error (0, 0, "are trying to force a connection via rsh, please");
error (0, 0, "put \":server:\" at the beginning of your CVSROOT");
error (1, 0, "variable.");
}
return port;
}
else if (portname && (s = getservbyname (portname, "tcp")))
return ntohs (s->s_port);
else
return defaultport;
}
int
get_cvs_port_number (const cvsroot_t *root)
{
if (root->port) return root->port;
switch (root->method)
{
# ifdef HAVE_GSSAPI
case gserver_method:
# endif
# ifdef AUTH_CLIENT_SUPPORT
case pserver_method:
# endif
# if defined (AUTH_CLIENT_SUPPORT) || defined (HAVE_GSSAPI)
return get_port_number ("CVS_CLIENT_PORT", "cvspserver",
CVS_AUTH_PORT);
# endif
# ifdef HAVE_KERBEROS
case kserver_method:
return get_port_number ("CVS_CLIENT_PORT", "cvs", CVS_PORT);
# endif
default:
error(1, EINVAL,
"internal error: get_cvs_port_number called for invalid connection method (%s)",
method_names[root->method]);
break;
}
return -1;
}
static int
get_proxy_port_number (const cvsroot_t *root)
{
if (root->proxy_port) return root->proxy_port;
return get_port_number ("CVS_PROXY_PORT", NULL, CVS_PROXY_PORT);
}
void
make_bufs_from_fds(int tofd, int fromfd, int child_pid, cvsroot_t *root,
struct buffer **to_server_p,
struct buffer **from_server_p, int is_sock)
{
# ifdef NO_SOCKET_TO_FD
if (is_sock)
{
assert (tofd == fromfd);
*to_server_p = socket_buffer_initialize (tofd, 0, NULL);
*from_server_p = socket_buffer_initialize (tofd, 1, NULL);
}
else
# endif
{
close_on_exec (tofd);
close_on_exec (fromfd);
if (tofd == fromfd)
{
fromfd = dup (tofd);
if (fromfd < 0)
error (1, errno, "cannot dup net connection");
}
*to_server_p = fd_buffer_initialize (tofd, 0, root, false, NULL);
*from_server_p = fd_buffer_initialize (fromfd, child_pid, root,
true, NULL);
}
}
#endif
#if defined (AUTH_CLIENT_SUPPORT) || defined(HAVE_GSSAPI)
void
connect_to_pserver (cvsroot_t *root, struct buffer **to_server_p,
struct buffer **from_server_p, int verify_only,
int do_gssapi)
{
int sock;
int port_number,
proxy_port_number = 0;
union sai {
struct sockaddr_in addr_in;
struct sockaddr addr;
} client_sai;
struct hostent *hostinfo;
struct buffer *to_server, *from_server;
sock = socket (AF_INET, SOCK_STREAM, 0);
if (sock == -1)
error (1, 0, "cannot create socket: %s", SOCK_STRERROR (SOCK_ERRNO));
port_number = get_cvs_port_number (root);
if (root->proxy_hostname)
{
proxy_port_number = get_proxy_port_number (root);
hostinfo = init_sockaddr (&client_sai.addr_in, root->proxy_hostname,
proxy_port_number);
TRACE (TRACE_FUNCTION, "Connecting to %s:%d via proxy %s(%s):%d.",
root->hostname, port_number, root->proxy_hostname,
inet_ntoa (client_sai.addr_in.sin_addr), proxy_port_number);
}
else
{
hostinfo = init_sockaddr (&client_sai.addr_in, root->hostname,
port_number);
TRACE (TRACE_FUNCTION, "Connecting to %s(%s):%d.",
root->hostname,
inet_ntoa (client_sai.addr_in.sin_addr), port_number);
}
if (connect (sock, &client_sai.addr, sizeof (client_sai))
< 0)
error (1, 0, "connect to %s(%s):%d failed: %s",
root->proxy_hostname ? root->proxy_hostname : root->hostname,
inet_ntoa (client_sai.addr_in.sin_addr),
root->proxy_hostname ? proxy_port_number : port_number,
SOCK_STRERROR (SOCK_ERRNO));
make_bufs_from_fds (sock, sock, 0, root, &to_server, &from_server, 1);
if (root->proxy_hostname)
{
#define CONNECT_STRING "CONNECT %s:%d HTTP/1.0\r\n\r\n"
char* read_buf;
int codenum;
size_t count;
char* write_buf = Xasnprintf (NULL, &count, CONNECT_STRING,
root->hostname, port_number);
send_to_server_via (to_server, write_buf, count);
read_line_via (from_server, to_server, &read_buf);
sscanf (read_buf, "%s %d", write_buf, &codenum);
if ((codenum / 100) != 2)
error (1, 0, "proxy server %s:%d does not support http tunnelling",
root->proxy_hostname, proxy_port_number);
free (read_buf);
free (write_buf);
while (read_line_via (from_server, to_server, &read_buf) > 0)
{
if (read_buf[0] == '\r' || read_buf[0] == 0)
{
free (read_buf);
break;
}
free (read_buf);
}
}
auth_server (root, to_server, from_server, verify_only, do_gssapi,
hostinfo);
if (verify_only)
{
int status;
status = buf_shutdown (to_server);
if (status != 0)
error (0, status, "shutting down buffer to server");
buf_free (to_server);
to_server = NULL;
status = buf_shutdown (from_server);
if (status != 0)
error (0, status, "shutting down buffer from server");
buf_free (from_server);
from_server = NULL;
}
else
{
*to_server_p = to_server;
*from_server_p = from_server;
}
return;
}
static void
auth_server (cvsroot_t *root, struct buffer *to_server,
struct buffer *from_server, int verify_only, int do_gssapi,
struct hostent *hostinfo)
{
char *username = NULL;
char no_passwd = 0;
if (do_gssapi)
{
# ifdef HAVE_GSSAPI
int fd = buf_get_fd (to_server);
struct stat s;
if ((fd < 0) || (fstat (fd, &s) < 0) || !S_ISSOCK(s.st_mode))
{
error (1, 0,
"gserver currently only enabled for socket connections");
}
if (! connect_to_gserver (root, fd, hostinfo))
{
error (1, 0,
"authorization failed: server %s rejected access to %s",
root->hostname, root->directory);
}
# else
error (1, 0,
"INTERNAL ERROR: This client does not support GSSAPI authentication");
# endif
}
else
{
# ifdef AUTH_CLIENT_SUPPORT
char *begin = NULL;
char *password = NULL;
char *end = NULL;
if (verify_only)
{
begin = "BEGIN VERIFICATION REQUEST";
end = "END VERIFICATION REQUEST";
}
else
{
begin = "BEGIN AUTH REQUEST";
end = "END AUTH REQUEST";
}
password = get_cvs_password ();
username = root->username ? root->username : getcaller();
if (!password)
{
no_passwd = 1;
password = scramble ("");
}
send_to_server_via(to_server, begin, 0);
send_to_server_via(to_server, "\012", 1);
send_to_server_via(to_server, root->directory, 0);
send_to_server_via(to_server, "\012", 1);
send_to_server_via(to_server, username, 0);
send_to_server_via(to_server, "\012", 1);
send_to_server_via(to_server, password, 0);
send_to_server_via(to_server, "\012", 1);
send_to_server_via(to_server, end, 0);
send_to_server_via(to_server, "\012", 1);
memset (password, 0, strlen (password));
# else
error (1, 0, "INTERNAL ERROR: This client does not support pserver authentication");
# endif
}
{
char *read_buf;
while (1)
{
read_line_via (from_server, to_server, &read_buf);
if (!strcmp (read_buf, "I HATE YOU"))
{
error (0, 0,
"authorization failed: server %s rejected access to %s for user %s",
root->hostname, root->directory,
username ? username : "(null)");
if (no_passwd)
{
error (0, 0,
"used empty password; try \"cvs login\" with a real password");
}
exit (EXIT_FAILURE);
}
else if (!strncmp (read_buf, "E ", 2))
{
fprintf (stderr, "%s\n", read_buf + 2);
}
else if (!strncmp (read_buf, "error ", 6))
{
char *p;
p = read_buf + 6;
while (*p != ' ' && *p != '\0')
++p;
if (*p == ' ')
++p;
fprintf (stderr, "%s\n", p);
exit (EXIT_FAILURE);
}
else if (!strcmp (read_buf, "I LOVE YOU"))
{
free (read_buf);
break;
}
else
{
error (1, 0,
"unrecognized auth response from %s: %s",
root->hostname, read_buf);
}
free (read_buf);
}
}
}
#endif
#if defined (CLIENT_SUPPORT) || defined (SERVER_SUPPORT)
static void
connect_to_forked_server (cvsroot_t *root, struct buffer **to_server_p,
struct buffer **from_server_p)
{
int tofd, fromfd;
int child_pid;
char *command[3];
command[0] = (root->cvs_server
? root->cvs_server : getenv ("CVS_SERVER"));
if (!command[0])
# ifdef SERVER_SUPPORT
command[0] = (char *)program_path;
# else
{
error( 0, 0, "You must set the CVS_SERVER environment variable when" );
error( 0, 0, "using the :fork: access method." );
error( 1, 0, "This CVS was not compiled with server support." );
}
# endif
command[1] = "server";
command[2] = NULL;
TRACE (TRACE_FUNCTION, "Forking server: %s %s",
command[0] ? command[0] : "(null)", command[1]);
child_pid = piped_child (command, &tofd, &fromfd, false);
if (child_pid < 0)
error (1, 0, "could not fork server process");
make_bufs_from_fds (tofd, fromfd, child_pid, root, to_server_p,
from_server_p, 0);
}
#endif
static int
send_variable_proc (Node *node, void *closure)
{
send_to_server ("Set ", 0);
send_to_server (node->key, 0);
send_to_server ("=", 1);
send_to_server (node->data, 0);
send_to_server ("\012", 1);
return 0;
}
void
open_connection_to_server (cvsroot_t *root, struct buffer **to_server_p,
struct buffer **from_server_p)
{
TRACE (TRACE_FUNCTION, "open_connection_to_server (%s)", root->original);
switch (root->method)
{
case pserver_method:
#ifdef AUTH_CLIENT_SUPPORT
connect_to_pserver (root, to_server_p, from_server_p, 0, 0);
#else
error (0, 0, "CVSROOT is set for a pserver access method but your");
error (1, 0, "CVS executable doesn't support it.");
#endif
break;
case kserver_method:
#if HAVE_KERBEROS
start_kerberos4_server (root, to_server_p,
from_server_p);
#else
error (0, 0,
"CVSROOT is set for a kerberos access method but your");
error (1, 0, "CVS executable doesn't support it.");
#endif
break;
case gserver_method:
#ifdef HAVE_GSSAPI
connect_to_pserver (root, to_server_p, from_server_p, 0, 1);
#else
error (0, 0, "CVSROOT is set for a GSSAPI access method but your");
error (1, 0, "CVS executable doesn't support it.");
#endif
break;
case ext_method:
#ifdef NO_EXT_METHOD
error (0, 0, ":ext: method not supported by this port of CVS");
error (1, 0, "try :server: instead");
#else
start_rsh_server (root, to_server_p,
from_server_p);
#endif
break;
case server_method:
#ifdef START_SERVER
{
int tofd, fromfd;
START_SERVER (&tofd, &fromfd, getcaller (),
root->username,
root->hostname,
root->directory);
# ifdef START_SERVER_RETURNS_SOCKET
make_bufs_from_fds (tofd, fromfd, 0, root, to_server_p,
from_server_p, 1);
# else
make_bufs_from_fds (tofd, fromfd, 0, root, to_server_p,
from_server_p, 0);
# endif
}
#else
error (1, 0,
"the :server: access method is not supported by this port of CVS");
#endif
break;
case fork_method:
connect_to_forked_server (root, to_server_p, from_server_p);
break;
default:
error (1, 0,
"(start_server internal error): unknown access method");
break;
}
server_started = 1;
}
void
start_server (void)
{
bool rootless;
int status;
bool have_global;
do
{
if (toplevel_repos)
free (toplevel_repos);
toplevel_repos = NULL;
open_connection_to_server (current_parsed_root, &global_to_server,
&global_from_server);
setup_logfiles ("CVS_CLIENT_LOG", &global_to_server,
&global_from_server);
if (toplevel_repos)
{
free (toplevel_repos);
toplevel_repos = NULL;
}
if (last_repos)
{
free (last_repos);
last_repos = NULL;
}
if (last_update_dir)
{
free (last_update_dir);
last_update_dir = NULL;
}
stored_checksum_valid = 0;
if (stored_mode)
{
free (stored_mode);
stored_mode = NULL;
}
rootless = !strcmp (cvs_cmd_name, "init");
if (!rootless)
{
send_to_server ("Root ", 0);
send_to_server (current_parsed_root->directory, 0);
send_to_server ("\012", 1);
}
{
struct response *rs;
bool suppress_redirect = !current_parsed_root->redirect;
send_to_server ("Valid-responses", 0);
for (rs = responses; rs->name; ++rs)
{
if (suppress_redirect && !strcmp (rs->name, "Redirect"))
continue;
send_to_server (" ", 0);
send_to_server (rs->name, 0);
}
send_to_server ("\012", 1);
}
send_to_server ("valid-requests\012", 0);
if (get_server_responses ())
exit (EXIT_FAILURE);
have_global = supported_request ("Global_option");
if (cvsencrypt && !rootless)
{
#ifdef ENCRYPTION
# ifdef HAVE_KERBEROS
if (current_parsed_root->method == kserver_method)
{
if (!supported_request ("Kerberos-encrypt"))
error (1, 0, "This server does not support encryption");
send_to_server ("Kerberos-encrypt\012", 0);
initialize_kerberos4_encryption_buffers (&global_to_server,
&global_from_server);
}
else
# endif
# ifdef HAVE_GSSAPI
if (current_parsed_root->method == gserver_method)
{
if (!supported_request ("Gssapi-encrypt"))
error (1, 0, "This server does not support encryption");
send_to_server ("Gssapi-encrypt\012", 0);
initialize_gssapi_buffers (&global_to_server,
&global_from_server);
cvs_gssapi_encrypt = 1;
}
else
# endif
error (1, 0,
"Encryption is only supported when using GSSAPI or Kerberos");
#else
error (1, 0, "This client does not support encryption");
#endif
}
if (quiet)
{
if (have_global)
{
send_to_server ("Global_option -q\012", 0);
}
else
error (1, 0,
"This server does not support the global -q option.");
}
if (really_quiet)
{
if (have_global)
{
send_to_server ("Global_option -Q\012", 0);
}
else
error (1, 0,
"This server does not support the global -Q option.");
}
if (!rootless && (gzip_level || force_gzip))
{
if (supported_request ("Gzip-stream"))
{
char *gzip_level_buf = Xasprintf ("%d", gzip_level);
send_to_server ("Gzip-stream ", 0);
send_to_server (gzip_level_buf, 0);
free (gzip_level_buf);
send_to_server ("\012", 1);
global_to_server =
compress_buffer_initialize (global_to_server, 0,
gzip_level, NULL);
global_from_server =
compress_buffer_initialize (global_from_server, 1,
gzip_level, NULL);
}
#ifndef NO_CLIENT_GZIP_PROCESS
else if (supported_request ("gzip-file-contents"))
{
char *gzip_level_buf = Xasprintf ("%d", gzip_level);
send_to_server ("gzip-file-contents ", 0);
send_to_server (gzip_level_buf, 0);
free (gzip_level_buf);
send_to_server ("\012", 1);
file_gzip_level = gzip_level;
}
#endif
else
{
fprintf (stderr, "server doesn't support gzip-file-contents\n");
gzip_level = 0;
}
}
if (client_referrer && supported_request ("Referrer"))
{
send_to_server ("Referrer ", 0);
send_to_server (client_referrer->original, 0);
send_to_server ("\012", 0);
}
if (!rootless && supported_request ("Command-prep"))
{
send_to_server ("Command-prep ", 0);
send_to_server (cvs_cmd_name, 0);
send_to_server ("\012", 0);
status = get_server_responses ();
if (status == 1) exit (EXIT_FAILURE);
if (status == 2) close_connection_to_server (&global_to_server,
&global_from_server);
}
else status = 0;
} while (status == 2);
if (noexec)
{
if (have_global)
{
send_to_server ("Global_option -n\012", 0);
}
else
error (1, 0,
"This server does not support the global -n option.");
}
if (!cvswrite)
{
if (have_global)
{
send_to_server ("Global_option -r\012", 0);
}
else
error (1, 0,
"This server does not support the global -r option.");
}
if (trace)
{
if (have_global)
{
int count = trace;
while (count--) send_to_server ("Global_option -t\012", 0);
}
else
error (1, 0,
"This server does not support the global -t option.");
}
if (!strcmp (cvs_cmd_name, "import") || !strcmp (cvs_cmd_name, "add"))
{
if (supported_request ("wrapper-sendme-rcsOptions"))
{
int err;
send_to_server ("wrapper-sendme-rcsOptions\012", 0);
err = get_server_responses ();
if (err != 0)
error (err, 0, "error reading from server");
}
}
if (cvsauthenticate && ! cvsencrypt && !rootless)
{
#ifdef HAVE_GSSAPI
if (current_parsed_root->method == gserver_method)
{
if (! supported_request ("Gssapi-authenticate"))
error (1, 0,
"This server does not support stream authentication");
send_to_server ("Gssapi-authenticate\012", 0);
initialize_gssapi_buffers(&global_to_server, &global_from_server);
}
else
error (1, 0, "Stream authentication is only supported when using GSSAPI");
#else
error (1, 0, "This client does not support stream authentication");
#endif
}
if (supported_request ("Set"))
walklist (variable_list, send_variable_proc, NULL);
}
void
send_arg (const char *string)
{
const char *p = string;
send_to_server ("Argument ", 0);
while (*p)
{
if (*p == '\n')
send_to_server ("\012Argumentx ", 0);
else
send_to_server (p, 1);
++p;
}
send_to_server ("\012", 1);
}
static void
send_modified (const char *file, const char *short_pathname, Vers_TS *vers)
{
struct stat sb;
int fd;
unsigned char *buf;
char *mode_string;
size_t bufsize;
int bin;
TRACE (TRACE_FUNCTION, "Sending file `%s' to server", file);
if (stat (file, &sb) < 0)
error (1, errno, "reading %s", short_pathname);
mode_string = mode_to_string (sb.st_mode);
bufsize = sb.st_size;
buf = xmalloc (bufsize);
if (vers && vers->options)
bin = !strcmp (vers->options, "-kb");
else
bin = 0;
#ifdef BROKEN_READWRITE_CONVERSION
if (!bin)
{
char *tfile = Xasprintf ("%s.CVSBFCTMP", file);
convert_file (file, O_RDONLY,
tfile, O_WRONLY | O_CREAT | O_TRUNC | OPEN_BINARY);
fd = CVS_OPEN (tfile, O_RDONLY | OPEN_BINARY);
if (fd < 0)
error (1, errno, "reading %s", short_pathname);
free (tfile);
}
else
fd = CVS_OPEN (file, O_RDONLY | OPEN_BINARY);
#else
fd = CVS_OPEN (file, O_RDONLY | (bin ? OPEN_BINARY : 0));
#endif
if (fd < 0)
error (1, errno, "reading %s", short_pathname);
if (file_gzip_level && sb.st_size > 100)
{
size_t newsize = 0;
if (read_and_gzip (fd, short_pathname, &buf,
&bufsize, &newsize,
file_gzip_level))
error (1, 0, "aborting due to compression error");
if (close (fd) < 0)
error (0, errno, "warning: can't close %s", short_pathname);
{
char tmp[80];
send_to_server ("Modified ", 0);
send_to_server (file, 0);
send_to_server ("\012", 1);
send_to_server (mode_string, 0);
send_to_server ("\012z", 2);
sprintf (tmp, "%lu\n", (unsigned long) newsize);
send_to_server (tmp, 0);
send_to_server (buf, newsize);
}
}
else
{
int newsize;
{
unsigned char *bufp = buf;
int len;
while ((len = read (fd, bufp, (buf + sb.st_size) - bufp)) > 0)
bufp += len;
if (len < 0)
error (1, errno, "reading %s", short_pathname);
newsize = bufp - buf;
}
if (close (fd) < 0)
error (0, errno, "warning: can't close %s", short_pathname);
{
char tmp[80];
send_to_server ("Modified ", 0);
send_to_server (file, 0);
send_to_server ("\012", 1);
send_to_server (mode_string, 0);
send_to_server ("\012", 1);
sprintf (tmp, "%lu\012", (unsigned long) newsize);
send_to_server (tmp, 0);
}
#ifdef BROKEN_READWRITE_CONVERSION
if (!bin)
{
char *tfile = Xasprintf ("%s.CVSBFCTMP", file);
if (CVS_UNLINK (tfile) < 0)
error (0, errno, "warning: can't remove temp file %s", tfile);
free (tfile);
}
#endif
if (newsize > 0)
send_to_server (buf, newsize);
}
free (buf);
free (mode_string);
}
struct send_data
{
int build_dirs;
int force;
int no_contents;
int backup_modified;
};
static int
send_fileproc (void *callerdat, struct file_info *finfo)
{
struct send_data *args = callerdat;
Vers_TS *vers;
struct file_info xfinfo;
const char *filename;
send_a_repository ("", finfo->repository, finfo->update_dir);
xfinfo = *finfo;
xfinfo.repository = NULL;
xfinfo.rcs = NULL;
vers = Version_TS (&xfinfo, NULL, NULL, NULL, 0, 0);
if (vers->entdata)
filename = vers->entdata->user;
else
filename = finfo->file;
if (vers->vn_user)
{
send_to_server ("Entry /", 0);
send_to_server (filename, 0);
send_to_server ("/", 0);
send_to_server (vers->vn_user, 0);
send_to_server ("/", 0);
if (vers->ts_conflict)
{
if (vers->ts_user && !strcmp (vers->ts_conflict, vers->ts_user))
send_to_server ("+=", 0);
else
send_to_server ("+modified", 0);
}
send_to_server ("/", 0);
send_to_server (vers->entdata ? vers->entdata->options : vers->options,
0);
send_to_server ("/", 0);
if (vers->entdata && vers->entdata->tag)
{
send_to_server ("T", 0);
send_to_server (vers->entdata->tag, 0);
}
else if (vers->entdata && vers->entdata->date)
{
send_to_server ("D", 0);
send_to_server (vers->entdata->date, 0);
}
send_to_server ("\012", 1);
}
else
{
wrap_add_file (CVSDOTWRAPPER, 1);
if (wrap_name_has (filename, WRAP_RCSOPTION))
{
if (supported_request ("Kopt"))
{
char *opt;
send_to_server ("Kopt ", 0);
opt = wrap_rcsoption (filename, 1);
send_to_server (opt, 0);
send_to_server ("\012", 1);
free (opt);
}
else
error (0, 0, "\
warning: ignoring -k options due to server limitations");
}
}
if (!vers->ts_user)
{
}
else if (!vers->ts_rcs || args->force
|| strcmp (vers->ts_conflict
? vers->ts_conflict : vers->ts_rcs, vers->ts_user)
|| (vers->ts_conflict && !strcmp (cvs_cmd_name, "diff")))
{
if (args->no_contents
&& supported_request ("Is-modified"))
{
send_to_server ("Is-modified ", 0);
send_to_server (filename, 0);
send_to_server ("\012", 1);
}
else
send_modified (filename, finfo->fullname, vers);
if (args->backup_modified)
{
char *bakname;
bakname = backup_file (filename, vers->vn_user);
if (! really_quiet)
printf ("(Locally modified %s moved to %s)\n",
filename, bakname);
free (bakname);
}
}
else
{
send_to_server ("Unchanged ", 0);
send_to_server (filename, 0);
send_to_server ("\012", 1);
}
if (ignlist)
{
Node *p;
p = getnode ();
p->type = FILES;
p->key = xstrdup (finfo->file);
(void) addnode (ignlist, p);
}
freevers_ts (&vers);
return 0;
}
static void
send_ignproc (const char *file, const char *dir)
{
if (ign_inhibit_server || !supported_request ("Questionable"))
{
if (dir[0] != '\0')
(void) printf ("? %s/%s\n", dir, file);
else
(void) printf ("? %s\n", file);
}
else
{
send_to_server ("Questionable ", 0);
send_to_server (file, 0);
send_to_server ("\012", 1);
}
}
static int
send_filesdoneproc (void *callerdat, int err, const char *repository,
const char *update_dir, List *entries)
{
if (ignlist)
{
ignore_files (ignlist, entries, update_dir, send_ignproc);
dellist (&ignlist);
}
return err;
}
static Dtype
send_dirent_proc (void *callerdat, const char *dir, const char *repository,
const char *update_dir, List *entries)
{
struct send_data *args = callerdat;
int dir_exists;
char *cvsadm_name;
if (ignore_directory (update_dir))
{
if (!quiet)
error (0, 0, "Ignoring %s", update_dir);
return R_SKIP_ALL;
}
cvsadm_name = Xasprintf ("%s/%s", dir, CVSADM);
dir_exists = isdir (cvsadm_name);
free (cvsadm_name);
if (dir_exists)
{
char *repos = Name_Repository (dir, update_dir);
send_a_repository (dir, repos, update_dir);
free (repos);
ignlist = getlist ();
}
else
{
if (args->build_dirs && noexec)
send_a_repository (dir, repository, update_dir);
}
return dir_exists ? R_PROCESS : R_SKIP_ALL;
}
static int
send_dirleave_proc (void *callerdat, const char *dir, int err,
const char *update_dir, List *entries )
{
if (ignlist)
dellist (&ignlist);
return err;
}
void
send_options (int argc, char * const *argv)
{
int i;
for (i = 0; i < argc; i++)
send_arg (argv[i]);
}
void
send_file_names (int argc, char **argv, unsigned int flags)
{
int i;
if (flags & SEND_EXPAND_WILD)
expand_wild (argc, argv, &argc, &argv);
for (i = 0; i < argc; ++i)
{
char buf[1];
char *p;
#ifdef FILENAMES_CASE_INSENSITIVE
char *line = NULL;
#endif
if (arg_should_not_be_sent_to_server (argv[i]))
continue;
#ifdef FILENAMES_CASE_INSENSITIVE
{
List *stack;
size_t line_len = 0;
char *q, *r;
struct saved_cwd sdir;
stack = getlist();
r = xstrdup (argv[i]);
while ((q = (char *)last_component (r)) != r)
{
push (stack, xstrdup (q));
*--q = '\0';
}
push (stack, r);
save_cwd (&sdir);
while (q = pop (stack))
{
Node *node = NULL;
if (isdir (CVSADM))
{
List *entries;
entries = Entries_Open (0, NULL);
node = findnode_fn (entries, q);
if (node)
{
if (line_len)
xrealloc_and_strcat (&line, &line_len, "/");
xrealloc_and_strcat (&line, &line_len, node->key);
delnode (node);
}
Entries_Close (entries);
}
if (!node)
{
if (line_len)
xrealloc_and_strcat (&line, &line_len, "/");
xrealloc_and_strcat (&line, &line_len, q);
break;
}
if (isdir (q))
CVS_CHDIR (q);
free (q);
}
restore_cwd (&sdir);
free_cwd (&sdir);
while (q = pop (stack))
{
if (line_len)
xrealloc_and_strcat (&line, &line_len, "/");
xrealloc_and_strcat (&line, &line_len, q);
free (q);
}
p = line;
dellist (&stack);
}
#else
p = argv[i];
#endif
send_to_server ("Argument ", 0);
while (*p)
{
if (*p == '\n')
{
send_to_server ("\012Argumentx ", 0);
}
else if (ISSLASH (*p))
{
buf[0] = '/';
send_to_server (buf, 1);
}
else
{
buf[0] = *p;
send_to_server (buf, 1);
}
++p;
}
send_to_server ("\012", 1);
#ifdef FILENAMES_CASE_INSENSITIVE
free (line);
#endif
}
if (flags & SEND_EXPAND_WILD)
{
int i;
for (i = 0; i < argc; ++i)
free (argv[i]);
free (argv);
}
}
static void
send_max_dotdot (argc, argv)
int argc;
char **argv;
{
int i;
int level = 0;
int max_level = 0;
for (i = 0; i < argc; ++i)
{
level = pathname_levels (argv[i]);
if (level > 0)
{
if (!uppaths) uppaths = getlist();
push_string (uppaths, xstrdup (argv[i]));
}
if (level > max_level)
max_level = level;
}
if (max_level > 0)
{
if (supported_request ("Max-dotdot"))
{
char buf[10];
sprintf (buf, "%d", max_level);
send_to_server ("Max-dotdot ", 0);
send_to_server (buf, 0);
send_to_server ("\012", 1);
}
else
{
error (1, 0,
"backreference in path (`..') not supported by old (pre-Max-dotdot) servers");
}
}
}
void
send_files (int argc, char **argv, int local, int aflag, unsigned int flags)
{
struct send_data args;
int err;
send_max_dotdot (argc, argv);
args.build_dirs = flags & SEND_BUILD_DIRS;
args.force = flags & SEND_FORCE;
args.no_contents = flags & SEND_NO_CONTENTS;
args.backup_modified = flags & BACKUP_MODIFIED_FILES;
err = start_recursion
(send_fileproc, send_filesdoneproc, send_dirent_proc,
send_dirleave_proc, &args, argc, argv, local, W_LOCAL, aflag,
CVS_LOCK_NONE, NULL, 0, NULL);
if (err)
exit (EXIT_FAILURE);
if (!toplevel_repos)
toplevel_repos = xstrdup (current_parsed_root->directory);
send_repository ("", toplevel_repos, ".");
}
void
client_import_setup (char *repository)
{
if (!toplevel_repos)
send_a_repository ("", repository, "");
}
int
client_process_import_file (char *message, char *vfile, char *vtag, int targc,
char *targv[], char *repository,
int all_files_binary,
int modtime )
{
char *update_dir;
char *fullname;
Vers_TS vers;
assert (toplevel_repos);
if (strncmp (repository, toplevel_repos, strlen (toplevel_repos)))
error (1, 0,
"internal error: pathname `%s' doesn't specify file in `%s'",
repository, toplevel_repos);
if (!strcmp (repository, toplevel_repos))
{
update_dir = "";
fullname = xstrdup (vfile);
}
else
{
update_dir = repository + strlen (toplevel_repos) + 1;
fullname = Xasprintf ("%s/%s", update_dir, vfile);
}
send_a_repository ("", repository, update_dir);
if (all_files_binary)
vers.options = xstrdup ("-kb");
else
vers.options = wrap_rcsoption (vfile, 1);
if (vers.options)
{
if (supported_request ("Kopt"))
{
send_to_server ("Kopt ", 0);
send_to_server (vers.options, 0);
send_to_server ("\012", 1);
}
else
error (0, 0,
"warning: ignoring -k options due to server limitations");
}
if (modtime)
{
if (supported_request ("Checkin-time"))
{
struct stat sb;
char *rcsdate;
char netdate[MAXDATELEN];
if (stat (vfile, &sb) < 0)
error (1, errno, "cannot stat %s", fullname);
rcsdate = date_from_time_t (sb.st_mtime);
date_to_internet (netdate, rcsdate);
free (rcsdate);
send_to_server ("Checkin-time ", 0);
send_to_server (netdate, 0);
send_to_server ("\012", 1);
}
else
error (0, 0,
"warning: ignoring -d option due to server limitations");
}
send_modified (vfile, fullname, &vers);
if (vers.options)
free (vers.options);
free (fullname);
return 0;
}
void
client_import_done (void)
{
if (!toplevel_repos)
toplevel_repos = xstrdup (current_parsed_root->directory);
send_repository ("", toplevel_repos, ".");
}
void
client_notify (const char *repository, const char *update_dir,
const char *filename, int notif_type, const char *val)
{
char buf[2];
send_a_repository ("", repository, update_dir);
send_to_server ("Notify ", 0);
send_to_server (filename, 0);
send_to_server ("\012", 1);
buf[0] = notif_type;
buf[1] = '\0';
send_to_server (buf, 1);
send_to_server ("\t", 1);
send_to_server (val, 0);
}
void
option_with_arg (const char *option, const char *arg)
{
if (!arg)
return;
send_to_server ("Argument ", 0);
send_to_server (option, 0);
send_to_server ("\012", 1);
send_arg (arg);
}
void
client_senddate (const char *date)
{
char buf[MAXDATELEN];
date_to_internet (buf, date);
option_with_arg ("-D", buf);
}
void
send_init_command (void)
{
send_to_server ("init ", 0);
send_to_server (current_parsed_root->directory, 0);
send_to_server ("\012", 0);
}
#if defined AUTH_CLIENT_SUPPORT || defined HAVE_KERBEROS || defined HAVE_GSSAPI
struct hostent *
init_sockaddr (struct sockaddr_in *name, char *hostname, unsigned int port)
{
struct hostent *hostinfo;
unsigned short shortport = port;
memset (name, 0, sizeof (*name));
name->sin_family = AF_INET;
name->sin_port = htons (shortport);
hostinfo = gethostbyname (hostname);
if (!hostinfo)
{
fprintf (stderr, "Unknown host %s.\n", hostname);
exit (EXIT_FAILURE);
}
name->sin_addr = *(struct in_addr *) hostinfo->h_addr;
return hostinfo;
}
#endif
#endif