/*
* Copyright (c) 2005-2007 Hypertriton, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
* USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "agarrcsd.h"
#include "rcs.h"
#include "pathnames.h"
#include "fgetln.h"
#include "mkpath.h"
static char *
parse_path(NS_Command *cmd, const char *key)
{
char *path = NS_CommandString(cmd, key);
if (path[0] == '/' ||
strstr(path, "..") != NULL) {
AG_SetError("%s: bad object path: `%s'", key, path);
return (NULL);
}
return (path);
}
static char *
parse_name(NS_Command *cmd, const char *key)
{
char *name = NS_CommandString(cmd, key);
const char *p;
for (p = &key[0]; *p != '\0'; p++) {
if (*p == '/' || *p == '.' || *p == ' ') {
AG_SetError("%s: bad name/type: `%s'", key, name);
return (NULL);
}
}
if ((p - &key[0]) > RCSINFO_NAME_MAX) {
AG_SetError("%s: name too large", key);
return (NULL);
}
return (name);
}
static char *
parse_msg(NS_Command *cmd, const char *key)
{
char *name = NS_CommandString(cmd, key);
const char *p;
for (p = &key[0]; *p != '\0'; p++) {
if (!isprint(*p)) {
AG_SetError("%s: non-printable character", key);
return (NULL);
}
}
if ((p - &key[0]) > RCSINFO_MSG_MAX) {
AG_SetError("%s: name/type too large", key);
return (NULL);
}
return (name);
}
static size_t
parse_size(NS_Command *cmd, const char *key)
{
long lval;
if (NS_CommandLong(cmd, key, &lval) == -1)
return (0);
/* Agar signatures are at least 8 bytes. */
if (lval < 8) {
AG_SetError("%s: data file is too small (%lu)", key, lval);
return (0);
}
return ((size_t)lval);
}
static int
parse_revision(NS_Command *cmd, const char *key, Uint *rv)
{
long lval;
if (NS_CommandLong(cmd, key, &lval) == -1) {
return (-1);
}
if (lval < 1) {
AG_SetError("%s: invalid revision number", key);
return (-1);
}
*rv = (Uint)lval;
return (0);
}
int
rcs_commit(NS_Server *ns, NS_Command *cmd, void *p)
{
char buf[AG_BUFFER_MAX];
char filepath[AG_PATHNAME_MAX];
char revno[12];
char *dirpath, *objname, *objtype;
size_t nread = 0, size;
Uint rev;
int fd;
if ((dirpath = parse_path(cmd, "object-path")) == NULL ||
(size = parse_size(cmd, "object-size")) == 0 ||
(objname = parse_name(cmd, "object-name")) == NULL ||
(objtype = parse_name(cmd, "object-type")) == NULL) {
return (-1);
}
if (mkpath(dirpath, 0755) == -1) {
return (-1);
}
if (rcsinfo_add_revision(dirpath, user, &rev,
parse_msg(cmd, "commit-msg"),
parse_msg(cmd, "object-digest"),
objtype, objname) == -1)
return (-1);
Strlcpy(filepath, dirpath, sizeof(filepath));
Strlcat(filepath, "/", sizeof(filepath));
Strlcat(filepath, objname, sizeof(filepath));
Strlcat(filepath, ".", sizeof(filepath));
Strlcat(filepath, objtype, sizeof(filepath));
Strlcat(filepath, ".", sizeof(filepath));
Snprintf(revno, sizeof(revno), "%u", rev);
Strlcat(filepath, revno, sizeof(filepath));
if ((fd = open(filepath, O_CREAT|O_WRONLY|O_EXCL, 0644)) == -1) {
AG_SetError("%s: %s", filepath, strerror(errno));
NS_Log(NS_ERR, "%s", AG_GetError());
return (-1);
}
NS_Log(NS_INFO, "Importing %s (%lu bytes)", filepath,
(u_long)size);
NS_Message(ns, 0, "go ahead");
while (nread < size) {
size_t csize, rv;
if ((csize = (size - nread)) > sizeof(buf))
csize = sizeof(buf);
if ((rv = fread(buf, 1, csize, stdin)) < csize) {
AG_SetError("incomplete chunk");
goto fail;
}
if (write(fd, buf, rv) < rv) {
AG_SetError("write error");
goto fail;
}
nread += rv;
}
close(fd);
NS_Message(ns, 0, "New revision: #%u", rev);
return (0);
fail:
close(fd);
NS_Log(NS_ERR, "Import failed: %s: %s", filepath, AG_GetError());
return (-1);
}
int
rcs_info(NS_Server *ns, NS_Command *cmd, void *p)
{
char msg[RCSINFO_MSG_MAX];
char author[RCSINFO_AUTHOR_MAX];
char digest[RCSINFO_DIGEST_MAX];
char type[RCSINFO_TYPE_MAX];
char name[RCSINFO_NAME_MAX];
char *dirpath;
Uint rev;
if ((dirpath = parse_path(cmd, "object-path")) == NULL) {
return (-1);
}
if ((parse_revision(cmd, "revision", &rev)) == -1) {
rev = RCS_CURRENT_REVISION;
}
if (rcsinfo_get_revision(dirpath, &rev, author, digest, msg, type, name)
== -1) {
return (-1);
}
NS_Message(ns, 0, "r=%u:a=%s:d=%s:m=%s:t=%s:n=%s\n", rev, author,
digest, msg, type, name);
return (0);
}
int
rcs_update(NS_Server *ns, NS_Command *cmd, void *p)
{
char buf[AG_BUFFER_MAX];
char filepath[AG_PATHNAME_MAX];
char revtext[12];
char *dirpath, *objname, *objtype;
Uint rev;
off_t len;
ssize_t rv;
int fd;
if ((dirpath = parse_path(cmd, "object-path")) == NULL ||
(objname = parse_name(cmd, "object-name")) == NULL ||
(objtype = parse_name(cmd, "object-type")) == NULL ||
(parse_revision(cmd, "revision", &rev)) == -1) {
return (-1);
}
Strlcpy(filepath, dirpath, sizeof(filepath));
Strlcat(filepath, "/", sizeof(filepath));
Strlcat(filepath, objname, sizeof(filepath));
Strlcat(filepath, ".", sizeof(filepath));
Strlcat(filepath, objtype, sizeof(filepath));
Strlcat(filepath, ".", sizeof(filepath));
Snprintf(revtext, sizeof(revtext), "%u", rev);
Strlcat(filepath, revtext, sizeof(filepath));
NS_Log(NS_INFO, "sending %s", filepath);
if ((fd = open(filepath, O_RDONLY|O_EXCL)) == -1) {
AG_SetError("%s: %s", filepath, strerror(errno));
NS_Log(NS_ERR, "%s", AG_GetError());
return (-1);
}
if ((len = lseek(fd, 0, SEEK_END)) == -1) {
AG_SetError("%s: cannot seek end", filepath);
goto fail;
}
if (lseek(fd, 0, SEEK_SET) == -1) {
AG_SetError("%s: cannot seek set", filepath);
goto fail;
}
NS_BeginData(ns, (size_t)len);
while ((rv = read(fd, buf, sizeof(buf))) > 0) {
if (NS_Data(ns, buf, rv) < rv) {
AG_SetError("%s: error writing to socket", filepath);
NS_Log(NS_ERR, "%s", AG_GetError());
NS_EndData(ns);
goto fail;
}
}
if (rv < 0) {
AG_SetError("%s: read error", filepath);
NS_Log(NS_ERR, "%s", AG_GetError());
NS_EndData(ns);
goto fail;
}
close(fd);
NS_EndData(ns);
return (0);
fail:
close(fd);
return (-1);
}
static void
rcs_listdir(NS_Server *ns, const char *dirname, const char *path)
{
char author[RCSINFO_AUTHOR_MAX];
char type[RCSINFO_TYPE_MAX];
char name[RCSINFO_NAME_MAX];
char subpath[AG_PATHNAME_MAX];
Uint rev;
struct stat sb;
struct dirent *dent;
DIR *dir;
if (chdir(dirname) == -1) {
NS_Log(NS_ERR, "%s: %s", dirname, strerror(errno));
return;
}
rev = RCS_CURRENT_REVISION;
if (rcsinfo_get_revision(".", &rev, author, NULL, NULL, type, name)
== 0) {
NS_ListString(ns, "%s:%u:%s:%s:%s", path, rev, author, type,
name);
}
if ((dir = opendir(".")) == NULL) {
NS_Log(NS_ERR, "%s: %s", dirname, strerror(errno));
goto out;
}
while ((dent = readdir(dir)) != NULL) {
if (dent->d_name[0] == '.' ||
stat(dent->d_name, &sb) == -1) {
continue;
}
if ((sb.st_mode & S_IFDIR) == S_IFDIR) {
Strlcpy(subpath, path, sizeof(subpath));
Strlcat(subpath, "/", sizeof(subpath));
Strlcat(subpath, dent->d_name, sizeof(subpath));
rcs_listdir(ns, dent->d_name, subpath);
}
}
closedir(dir);
out:
if (chdir("..") == -1) {
NS_Log(NS_ERR, "%s/..: %s", dirname, strerror(errno));
exit(1);
}
}
int
rcs_list(NS_Server *ns, NS_Command *cmd, void *p)
{
NS_BeginList(ns);
rcs_listdir(ns, _PATH_DATA, "");
NS_EndList(ns);
if (chdir(_PATH_DATA) == -1) {
NS_Log(NS_ERR, "%s: %s", _PATH_DATA, AG_GetError());
return (-1);
}
return (0);
}
int
rcs_log(NS_Server *ns, NS_Command *cmd, void *p)
{
char path[AG_PATHNAME_MAX];
char *buf, *lbuf, *dirpath;
FILE *f;
size_t len;
if ((dirpath = parse_path(cmd, "object-path")) == NULL) {
return (-1);
}
Strlcpy(path, dirpath, sizeof(path));
Strlcat(path, "/.RCSInfo", sizeof(path));
if ((f = fopen(path, "r")) == NULL) {
AG_SetError("%s: %s", path, strerror(errno));
return (-1);
}
NS_BeginList(ns);
lbuf = NULL;
while ((buf = MyFgetln(f, &len)) != NULL) {
char *bufp = buf;
char *rev, *author, *digest, *msg, *type, *name;
char *c;
if (buf[len-1] == '\n') {
buf[len-1] = '\0';
} else {
if ((lbuf = malloc(len+1)) == NULL) {
NS_EndList(ns);
AG_SetError("out of memory");
goto fail;
}
memcpy(lbuf, buf, len);
lbuf[len] = '\0';
buf = lbuf;
}
rev = Strsep(&bufp, ":");
author = Strsep(&bufp, ":");
type = Strsep(&bufp, ":");
name = Strsep(&bufp, ":");
digest = Strsep(&bufp, ":");
msg = Strsep(&bufp, ":");
if (rev == NULL || author == NULL || type == NULL ||
name == NULL || digest == NULL || msg == NULL)
continue;
for (c = &msg[0]; *c != '\0'; c++) {
if (*c == ':')
*c = ';';
}
NS_ListString(ns, "%s:%s:%s:%s:%s:%s", rev, author, type,
name, digest, msg);
}
if (lbuf != NULL) {
free(lbuf);
}
fclose(f);
NS_EndList(ns);
return (0);
fail:
fclose(f);
return (-1);
}