/* * 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. */ /* * Built-in revision control system for AG_Object. */ #include #include #include #include #include #include #include "rcs.h" char agRcsHostname[64] = "localhost"; char agRcsUsername[32] = "anonymous"; char agRcsPassword[32] = ""; Uint agRcsPort = 6785; int agRcsMode = 0; #ifdef AG_NETWORK #include #include #include #include #include "net.h" static NC_Session rcs_client; static int connected = 0; const char *agRcsStatusStrings[] = { N_("Error"), N_("Not in repository"), N_("Up-to-date"), N_("Modified locally"), N_("Desynchronized") }; void AG_RcsInit(void) { NC_Init(&rcs_client, "agarrcs", "1.1"); } void AG_RcsDestroy(void) { NC_Destroy(&rcs_client); } int AG_RcsConnect(void) { char port[12]; if (++connected == 1) { Snprintf(port, sizeof(port), "%u", agRcsPort); if (NC_Connect(&rcs_client, agRcsHostname, port, agRcsUsername, agRcsPassword) == -1) { AG_SetError("RCS connection: %s", AG_GetError()); return (-1); } } return (0); } void AG_RcsDisconnect(void) { if (--connected == 0) NC_Disconnect(&rcs_client); } /* Get the working revision of an object. */ int AG_RcsGetWorkingRev(AG_Object *ob, Uint *pRev) { char path[AG_PATHNAME_MAX]; char buf[12]; size_t rv; long rev; char *ep; FILE *f; AG_ObjectCopyFilename(ob, path, sizeof(path)); Strlcat(path, ".revision", sizeof(path)); /* TODO locking */ if ((f = fopen(path, "r")) == NULL) { AG_SetError("%s: %s", path, strerror(errno)); return (-1); } rv = fread(buf, 1, sizeof(buf), f); buf[rv-1] = '\0'; fclose(f); errno = 0; rev = strtol(buf, &ep, 10); if (buf[0] == '\0' || *ep != '\0') { AG_SetError("%s: not a number", path); return (-1); } #if 0 if ((errno == ERANGE && (rev == AG_LONG_MAX || rev == AG_LONG_MIN)) || (rev > AG_INT_MAX || rev < AG_INT_MIN)) { AG_SetError("%s: out of range", path); return (-1); } #endif *pRev = rev; return (0); } /* Write the working revision of an object. */ int AG_RcsSetWorkingRev(AG_Object *ob, Uint rev) { char path[AG_PATHNAME_MAX]; FILE *f; AG_ObjectCopyFilename(ob, path, sizeof(path)); Strlcat(path, ".revision", sizeof(path)); /* TODO locking */ if ((f = fopen(path, "w")) == NULL) { AG_SetError("%s: %s", path, strerror(errno)); return (-1); } fprintf(f, "%u\n", rev); fclose(f); return (0); } /* Obtain the RCS status of an object. */ enum ag_rcs_status AG_RcsStatus(AG_Object *ob, const char *objdir, const char *digest, char *name, char *type, Uint *repo_rev, Uint *working_rev) { char *buf; int sum_match = 1; char *s; if (NC_Write(&rcs_client, "rcs-info\nobject-path=%s\n\n", &objdir[1]) == -1) { AG_SetError("%s", AG_GetError()); return (AG_RCS_ERROR); } if (NC_Read(&rcs_client, 16) <= 2 || rcs_client.read.buf[0] != '0' || rcs_client.read.buf[1] == '\0') { return (AG_RCS_UNKNOWN); } buf = &rcs_client.read.buf[2]; *repo_rev = 0; while ((s = Strsep(&buf, ":")) != NULL) { char *key = Strsep(&s, "="); char *val = Strsep(&s, "="); if (key == NULL || val == NULL) continue; if (strcmp(key, "d") == 0 && strcmp(val, digest) != 0) { sum_match = 0; } else if (strcmp(key, "r") == 0) { *repo_rev = (Uint)strtol(val, NULL, 10); } else if (strcmp(key, "t") == 0 && type != NULL) { Strlcpy(type, val, AG_OBJECT_HIER_MAX); } else if (strcmp(key, "n") == 0 && name != NULL) { Strlcpy(type, val, AG_OBJECT_NAME_MAX); } } if (AG_RcsGetWorkingRev(ob, working_rev) == -1) return (AG_RCS_ERROR); if (*working_rev == *repo_rev) { if (sum_match) { return (AG_RCS_UPTODATE); } else { return (AG_RCS_LOCALMOD); } } else if (*working_rev < *repo_rev) { return (AG_RCS_DESYNCH); } else { AG_SetError("Working revision %u < %u", *working_rev, *repo_rev); return (AG_RCS_ERROR); } } /* Import a new object into the repository. */ int AG_RcsImport(AG_Object *ob) { char buf[AG_BUFFER_MAX]; char objdir[AG_OBJECT_PATH_MAX]; char objpath[AG_OBJECT_PATH_MAX]; char digest[AG_OBJECT_DIGEST_MAX]; size_t wrote = 0, len, rv; Uint repo_rev, working_rev; FILE *f; enum ag_rcs_status status; if (AG_ObjectCopyName(ob, objdir, sizeof(objdir)) == -1 || AG_ObjectCopyDigest(ob, &len, digest) == -1 || AG_ObjectCopyFilename(ob, objpath, sizeof(objpath)) == -1) return (-1); if (AG_RcsConnect() == -1) return (-1); switch ((status = AG_RcsStatus(ob, objdir, digest, NULL, NULL, &repo_rev, &working_rev))) { case AG_RCS_ERROR: /* AG_TextMsg(AG_MSG_ERROR, "%s", AG_GetError()); */ break; case AG_RCS_UNKNOWN: break; default: AG_SetError(_("Object %s is already in repository (r#%u):\n" "Status: %s\n"), ob->name, repo_rev, agRcsStatusStrings[status]); goto fail; } /* TODO locking */ if ((f = fopen(objpath, "r")) == NULL) { AG_SetError("%s: %s", objpath, strerror(errno)); AG_RcsDisconnect(); return (-1); } if (NC_Write(&rcs_client, "rcs-commit\n" "object-path=%s\n" "object-name=%s\n" "object-type=%s\n" "object-size=%lu\n" "object-digest=%s\n\n", &objdir[1], ob->name, ob->cls->hier, (Ulong)len, digest) == -1) goto fail_close; if (NC_Read(&rcs_client, 12) <= 2 || rcs_client.read.buf[0] != '0') { AG_SetError(_("Server refused data: %s"), &rcs_client.read.buf[2]); goto fail_close; } while ((rv = fread(buf, 1, sizeof(buf), f)) > 0) { size_t nw; nw = write(rcs_client.sock, buf, rv); if (nw == 0) { AG_SetError(_("EOF from server")); goto fail_close; } else if (nw < 0) { AG_SetError(_("Write error")); goto fail_close; } wrote += nw; } if (wrote < len) { AG_SetError(_("Upload incomplete")); goto fail_close; } if (NC_Read(&rcs_client, 32) < 1 || rcs_client.read.buf[0] != '0' || rcs_client.read.buf[1] == '\0') { AG_SetError(_("Commit failed: %s"), rcs_client.read.buf); goto fail_close; } #if 0 AG_TextTmsg(AG_MSG_INFO, 4000, _("Object %s successfully imported to repository.\n" "Size: %lu bytes\n" "%s\n"), ob->name, (Ulong)wrote, &rcs_client.read.buf[2]); #endif AG_RcsSetWorkingRev(ob, 1); fclose(f); AG_RcsDisconnect(); return (0); fail_close: fclose(f); fail: AG_RcsDisconnect(); return (-1); } /* Commit changes to an object. */ int AG_RcsCommit(AG_Object *ob) { char buf[AG_BUFFER_MAX]; char objdir[AG_OBJECT_PATH_MAX]; char objpath[AG_OBJECT_PATH_MAX]; char digest[AG_OBJECT_DIGEST_MAX]; size_t wrote = 0, len, rv; Uint repo_rev, working_rev; FILE *f; if (AG_ObjectCopyName(ob, objdir, sizeof(objdir)) == -1 || AG_ObjectCopyDigest(ob, &len, digest) == -1 || AG_ObjectCopyFilename(ob, objpath, sizeof(objpath)) == -1) return (-1); if (AG_RcsConnect() == -1) return (-1); switch (AG_RcsStatus(ob, objdir, digest, NULL, NULL, &repo_rev, &working_rev)) { case AG_RCS_LOCALMOD: break; case AG_RCS_ERROR: /* AG_TextMsg(AG_MSG_ERROR, "%s", AG_GetError()); */ break; case AG_RCS_UNKNOWN: AG_SetError( _("Object %s does not exist on the repository.\n" "Please use the RCS import command.\n"), ob->name); goto fail; case AG_RCS_UPTODATE: AG_SetError( _("Working copy of %s is up-to-date (r#%u)."), ob->name, repo_rev); goto fail; case AG_RCS_DESYNCH: AG_SetError( _("Your copy of %s is outdated (working=r#%u, repo=r#%u)\n" "Please do a RCS update prior to committing.\n"), ob->name, working_rev, repo_rev); goto fail; } /* TODO locking */ if ((f = fopen(objpath, "r")) == NULL) { AG_SetError("%s: %s", objpath, strerror(errno)); AG_RcsDisconnect(); return (-1); } if (NC_Write(&rcs_client, "rcs-commit\n" "object-path=%s\n" "object-name=%s\n" "object-type=%s\n" "object-size=%lu\n" "object-digest=%s\n\n", &objdir[1], ob->name, ob->cls->hier, (Ulong)len, digest) == -1) goto fail_close; if (NC_Read(&rcs_client, 12) <= 2 || rcs_client.read.buf[0] != '0') { AG_SetError(_("Server refused data: %s"), &rcs_client.read.buf[2]); goto fail_close; } while ((rv = fread(buf, 1, sizeof(buf), f)) > 0) { size_t nw; nw = write(rcs_client.sock, buf, rv); if (nw == 0) { AG_SetError(_("EOF from server")); goto fail_close; } else if (nw < 0) { AG_SetError(_("Write error")); goto fail_close; } wrote += nw; } if (wrote < len) { AG_SetError(_("Upload incomplete")); goto fail_close; } if (NC_Read(&rcs_client, 32) < 1 || rcs_client.read.buf[0] != '0' || rcs_client.read.buf[1] == '\0') { AG_SetError(_("Commit failed: %s"), rcs_client.read.buf); goto fail_close; } #if 0 AG_TextTmsg(AG_MSG_INFO, 4000, _("Object %s successfully committed to repository.\n" "Size: %lu bytes\n" "%s\n"), ob->name, (Ulong)wrote, &rcs_client.read.buf[2]); #endif AG_RcsSetWorkingRev(ob, repo_rev+1); fclose(f); AG_RcsDisconnect(); return (0); fail_close: fclose(f); fail: AG_RcsDisconnect(); return (-1); } /* Update a working copy of an object from the repository. */ int AG_RcsUpdate(AG_Object *ob) { char type[AG_OBJECT_HIER_MAX]; char objdir[AG_OBJECT_PATH_MAX]; char objpath[AG_OBJECT_PATH_MAX]; char digest[AG_OBJECT_DIGEST_MAX]; Uint working_rev, repo_rev; NC_Result *res; size_t len; FILE *f; if (AG_ObjectCopyName(ob, objdir, sizeof(objdir)) == -1 || AG_ObjectCopyDigest(ob, &len, digest) == -1 || AG_ObjectCopyFilename(ob, objpath, sizeof(objpath)) == -1) return (-1); if (AG_RcsConnect() == -1) return (-1); switch (AG_RcsStatus(ob, objdir, digest, NULL, type, &repo_rev, &working_rev)) { case AG_RCS_ERROR: goto fail; case AG_RCS_UNKNOWN: AG_SetError( _("Object %s does not exist on the repository.\n" "Please use the RCS import command.\n"), ob->name); goto fail; case AG_RCS_UPTODATE: AG_SetError( _("Working copy of %s is already up-to-date (r#%u)."), ob->name, repo_rev); goto fail; case AG_RCS_LOCALMOD: #if 1 /* TODO move working version */ AG_SetError( _("Working copy of %s was modified locally.\n" "Please do a RCS commit prior to updating.\n"), ob->name); goto fail; #endif case AG_RCS_DESYNCH: break; } if (strcmp(type, ob->cls->hier) != 0) { AG_SetError(_("Repository has different object type (%s/%s)"), type, ob->cls->hier); goto fail; } res = NC_QueryBinary(&rcs_client, "rcs-update\n" "object-path=%s\n" "object-name=%s\n" "object-type=%s\n" "revision=%u\n", &objdir[1], ob->name, ob->cls->hier, repo_rev); if (res == NULL || res->argc < 1) { AG_SetError("RCS update error: %s", AG_GetError()); goto fail; } /* TODO locking, backup */ if ((f = fopen(objpath, "w")) == NULL) { AG_SetError("%s: %s", objpath, strerror(errno)); goto fail_res; } if (fwrite(res->argv[0], 1, res->argv_len[0], f) < res->argv_len[0]) { AG_SetError("%s: write error", objpath); fclose(f); goto fail_res; } fclose(f); AG_RcsSetWorkingRev(ob, repo_rev); #if 0 AG_TextTmsg(AG_MSG_INFO, 1000, "%s: r#%u -> r#%u (%lu bytes)", ob->name, working_rev, repo_rev, (unsigned long)res->argv_len[0]); #endif NC_FreeResult(res); AG_RcsDisconnect(); /* * Load the generic part of the object since the object manager * only does a page-in. */ if (AG_ObjectLoad(ob) == -1 || AG_ObjectSave(ob) == -1) { AG_SetError("%s: %s", ob->name, AG_GetError()); return (-1); } return (0); fail_res: NC_FreeResult(res); fail: AG_RcsDisconnect(); return (-1); } int AG_RcsImportAll(AG_Object *obj) { AG_Object *cobj; AG_LockVFS(obj); if (AG_RcsImport(obj) == -1) { goto fail; } TAILQ_FOREACH(cobj, &obj->children, cobjs) { if (cobj->flags & AG_OBJECT_NON_PERSISTENT) { continue; } if (AG_RcsImportAll(cobj) == -1) goto fail; } AG_UnlockVFS(obj); return (0); fail: AG_UnlockVFS(obj); return (-1); } int AG_RcsUpdateAll(AG_Object *obj) { AG_Object *cobj; AG_LockVFS(obj); if (AG_RcsUpdate(obj) == -1) { goto fail; } TAILQ_FOREACH(cobj, &obj->children, cobjs) { if (cobj->flags & AG_OBJECT_NON_PERSISTENT) { continue; } if (AG_RcsUpdateAll(cobj) == -1) goto fail; } AG_UnlockVFS(obj); return (0); fail: AG_UnlockVFS(obj); return (-1); } int AG_RcsCommitAll(AG_Object *obj) { AG_Object *cobj; AG_LockVFS(obj); if (AG_RcsCommit(obj) == -1) { goto fail; } TAILQ_FOREACH(cobj, &obj->children, cobjs) { if (cobj->flags & AG_OBJECT_NON_PERSISTENT) { continue; } if (AG_RcsCommitAll(cobj) == -1) goto fail; } AG_UnlockVFS(obj); return (0); fail: AG_UnlockVFS(obj); return (-1); } int AG_RcsGetLog(const char *objdir, AG_RCSLog *log) { NC_Result *res; int i; res = NC_Query(&rcs_client, "rcs-log\n" "object-path=%s\n", &objdir[1]); if (res == NULL) { AG_SetError("%s", AG_GetError()); return (-1); } log->ents = NULL; log->nEnts = 0; for (i = 0; i < res->argc; i++) { char *s = res->argv[i]; char *rev = Strsep(&s, ":"); char *author = Strsep(&s, ":"); char *type = Strsep(&s, ":"); char *name = Strsep(&s, ":"); char *sum = Strsep(&s, ":"); char *msg = Strsep(&s, ":"); AG_RCSLogEntry *lent; if (rev == NULL || author == NULL || sum == NULL) continue; log->ents = Realloc(log->ents, (log->nEnts+1) * sizeof(AG_RCSLogEntry)); lent = &log->ents[log->nEnts++]; lent->rev = AG_Strdup(rev); lent->author = AG_Strdup(author); lent->type = type!=NULL ? AG_Strdup(type) : NULL; lent->name = name!=NULL ? AG_Strdup(name) : NULL; lent->sum = AG_Strdup(sum); lent->msg = msg!=NULL ? AG_Strdup(msg) : NULL; } NC_FreeResult(res); return (0); } void AG_RcsFreeLog(AG_RCSLog *log) { int i; for (i = 0; i < log->nEnts; i++) { AG_RCSLogEntry *lent = &log->ents[i]; Free(lent->rev); Free(lent->author); Free(lent->type); Free(lent->name); Free(lent->sum); Free(lent->msg); } log->ents = NULL; log->nEnts = 0; } int AG_RcsGetList(AG_RCSList *list) { NC_Result *res; int i; if ((res = NC_Query(&rcs_client, "rcs-list\n")) == NULL) { AG_SetError("%s", AG_GetError()); return (-1); } list->ents = NULL; list->nEnts = 0; for (i = 0; i < res->argc; i++) { char *s = res->argv[i]; char *name = Strsep(&s, ":"); char *rev = Strsep(&s, ":"); char *author = Strsep(&s, ":"); char *type = Strsep(&s, ":"); AG_RCSListEntry *lent; if (name == NULL || rev == NULL || author == NULL || type == NULL) continue; list->ents = Realloc(list->ents, (list->nEnts+1) * sizeof(AG_RCSListEntry)); lent = &list->ents[list->nEnts++]; lent->name = Strdup(name); lent->rev = Strdup(rev); lent->author = Strdup(author); lent->type = Strdup(type); } NC_FreeResult(res); return (0); } void AG_RcsFreeList(AG_RCSList *list) { int i; for (i = 0; i < list->nEnts; i++) { AG_RCSListEntry *lent = &list->ents[i]; Free(lent->name); Free(lent->rev); Free(lent->author); Free(lent->type); } list->ents = NULL; list->nEnts = 0; } int AG_RcsDelete(const char *path) { NC_Result *res; if (AG_RcsConnect() == -1) return (-1); res = NC_Query(&rcs_client, "rcs-delete\n" "object-path=%s\n", path); if (res == NULL) { AG_SetError("%s", AG_GetError()); AG_RcsDisconnect(); return (-1); } NC_FreeResult(res); AG_RcsDisconnect(); return (0); } int AG_RcsRename(const char *from, const char *to) { NC_Result *res; if (AG_RcsConnect() == -1) return (-1); res = NC_Query(&rcs_client, "rcs-rename\n" "from-path=%s\n" "to-path=%s\n", from, to); if (res == NULL) { AG_SetError("%s", AG_GetError()); AG_RcsDisconnect(); return (-1); } NC_FreeResult(res); AG_RcsDisconnect(); return (0); } int AG_RcsCheckout(void *vfsRoot, const char *path) { char localpath[AG_OBJECT_PATH_MAX]; char digest[AG_OBJECT_DIGEST_MAX]; char name[AG_OBJECT_NAME_MAX]; char type[AG_OBJECT_HIER_MAX]; char *buf, *s; Uint rev = 0; AG_Object *obj; AG_ObjectClass *cls = NULL; if (AG_RcsConnect() == -1) goto fail; /* Fetch the object information from the repository. */ if (NC_Write(&rcs_client, "rcs-info\nobject-path=%s\n\n", path) == -1) { AG_SetError("%s", AG_GetError()); goto fail; } if (NC_Read(&rcs_client, 16) <= 2 || rcs_client.read.buf[0] != '0' || rcs_client.read.buf[1] == '\0') { AG_SetError("RCS info: %s", rcs_client.read.buf); goto fail; } buf = &rcs_client.read.buf[2]; while ((s = Strsep(&buf, ":")) != NULL) { char *key = Strsep(&s, "="); char *val = Strsep(&s, "="); if (key == NULL || val == NULL) continue; switch (key[0]) { case 'd': Strlcpy(digest, val, AG_OBJECT_DIGEST_MAX); break; case 'r': rev = (Uint)strtol(val, NULL, 10); break; case 't': Strlcpy(type, val, AG_OBJECT_HIER_MAX); break; case 'n': Strlcpy(name, val, AG_OBJECT_NAME_MAX); break; } } if ((cls = AG_LoadClass(type)) == NULL) goto fail; /* Create the working copy if it does not exist. */ localpath[0] = '/'; localpath[1] = '\0'; Strlcat(localpath, path, sizeof(localpath)); if ((obj = AG_ObjectFind(vfsRoot, localpath)) == NULL) { #if 0 AG_TextTmsg(AG_MSG_INFO, 750, _("Creating working copy of %s (%s)."), name, type); #endif obj = Malloc(cls->size); AG_ObjectInit(obj, cls); AG_ObjectSetName(obj, "%s", name); if (AG_ObjectAttachToNamed(vfsRoot, localpath, obj) == -1) { AG_ObjectDestroy(obj); goto fail; } AG_ObjectUnlinkDatafiles(obj); if (AG_ObjectSave(obj) == -1) { #if 0 AG_TextMsg(AG_MSG_ERROR, "%s: %s", name, AG_GetError()); #endif } if (AG_RcsSetWorkingRev(obj, 0) == -1) { AG_ObjectDetach(obj); AG_ObjectDestroy(obj); goto fail; } } else { if (strcmp(type, obj->cls->hier) != 0) { AG_SetError( "%s: existing object of different type (%s)", localpath, obj->cls->hier); goto fail; } } if (AG_RcsUpdate(obj) == -1) { goto fail; } #if 0 AG_TextTmsg(AG_MSG_INFO, 1000, _("Object %s updated."), name); #endif AG_RcsDisconnect(); return (0); fail: AG_RcsDisconnect(); if (cls != NULL) { AG_UnloadClass(cls); } return (-1); } #endif /* AG_NETWORK */