uncompress
zpm-addfile
zpm-foreach-path
+zpm-note
zpm-vercmp
zpm-verify
zpm-extract
JSWOBJ=$(JSWSRC:%.c=%.o)
LIBZPMSRC=sha256.c db.c compress.c uncompress.c zpm.c zpm_hash.c \
foreach_path.c vercmp.c findpkg.c quote.c dbquery.c script_hash.c \
- parse.c integ.c
+ parse.c integ.c seterror.c notes.c
LIBZPMOBJ=$(addprefix lib/, $(LIBZPMSRC:%.c=%.o))
ZPKGBIN=zpm-addfile zpm-extract zpm-init zpm-vercmp zpm-stat zpm-hash \
zpm-findpkg zpm-shell zpm-soneed zpm-foreach-path zpm-parse \
zpm-runscript zpm-soname zpm-syncfs zpm-packagehash zpm-verify \
- zpm-elftype zpm-quote
+ zpm-elftype zpm-quote zpm-note
SCRIPTS=zpm zpm-install zpm-merge zpm-list zpm-preserve zpm-test zpm-log \
zpm-contents zpm-uninstall zpm-pathmod zpm-rmpackage zpm-newpackage \
zpm-findpkg: zpm-findpkg.o libzpm.a
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $< -lzpm -lelf
+zpm-note: zpm-note.o libzpm.a
+ $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $< -lzpm -lelf
+
zpm-syncfs: zpm-syncfs.o libzpm.a libelf.a
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $< -lzpm -lelf -lm
uid integer, -- numeric uid, generally ignored
gid integer, -- numeric gid, generally ignored
configuration integer not null default 0, -- boolean if config file
+ confhash text, -- last hash on disk
filetype varchar not null default 'r',
-- r regular file
-- d directory
-- b block special -- not supported
-- c and b device special files add dev number column
-- p fifos (i.e. pipe) -- not supported
+ -- s unix domain socket -- not supported
target text, -- link target for links
- -- device file dev numbers
- devmajor integer,
- devminor integer,
+ device integer, -- device file dev_t
hash text, -- null if not a regular file
mtime integer, -- seconds since epoch, finer resolution not needed
primary key (package,version,release,path),
check (target is null or length(target) between 1 and 4095),
check (hash is null or length(hash) between 1 and 1024),
check (not (filetype = 'r' and hash is null)),
- check (not (filetype = 'c' and (devmajor is null or devminor is null))),
- check (not (filetype = 'b' and (devmajor is null or devminor is null))),
+ check (not (filetype = 'c' and device is null)),
+ check (not (filetype = 'b' and device is null)),
check (filetype in ('r','d','l','h','c','b','p')),
check(length(username) between 1 and 256),
check(length(groupname) between 1 and 256),
configuration = NEW.configuration,
filetype = NEW.filetype,
target = NEW.target,
- devmajor = NEW.devmajor,
- devminor = NEW.devminor,
+ device = NEW.device,
hash = NEW.hash,
mtime = NEW.mtime
where package = OLD.package
info text -- human readable
);
+create table notes (
+ id integer primary key, -- rowid alias
+ ts text default (strftime('%Y-%m-%d %H:%M:%f', 'now')),
+ note text not null,
+ pkgid text, -- package
+ path text, -- file path involved
+ file text, -- hash of file
+ ack integer default 0
+);
+
create table history (
ts integer, -- again, probably needs timestamp sub second
cmd text,
newfiles as (
select distinct
path,username,uid,groupname,gid,mode,filetype,mtime,hash,
- target,devminor,devmajor
+ configuration,target,device, null as ohash
from syncstatus SS
where path not in (select path from syncstatus where
rstatus in ('installed', 'updating', 'removing')
SS.path,
SS.username,
SS.uid, SS.groupname, SS.gid, SS.mode,
- SS.filetype, SS.mtime, SS.hash, SS.target, SS.devminor, SS.devmajor
+ SS.filetype, SS.mtime, SS.hash,SS.configuration, SS.target, SS.device,
+ null as ohash
from syncstatus SS
join syncstatus OS
on SS.path = OS.path and SS.pkgid is not OS.pkgid
preserve as (
select distinct
path,username,uid,groupname,gid,mode,filetype,mtime,hash,
- target,devminor,devmajor
+ configuration,target,device, null as ohash
from syncstatus SS
where path in (select library from needed)
and SS.rstatus in ('removing', 'removed')
remove as (
select distinct
path,username,uid,groupname,gid,mode,filetype,mtime,hash,
- target,devminor,devmajor
+ configuration,target,device, null as ohash
from syncstatus SS
where path not in (
select path from syncstatus where
expired as (
select distinct
path,username,uid,groupname,gid,mode,filetype,mtime,hash,
- target,devminor,devmajor
+ configuration,target,device, null as ohash
from syncstatus BASE
where hash in (select file from elflibraries where file is not null)
and path not in (select path from preserve)
return st;
}
+void zpm_db_run(struct zpm *zpm, char *query, ...) {
+ sqlite3_stmt *st;
+ va_list args;
+ int rv;
+
+ va_start(args, query);
+ st = zpm_dbqueryv(zpm, query, args);
+ va_end(args);
+
+ rv = sqlite3_step(st);
+
+ if (rv != SQLITE_DONE) {
+ zpm->error = 1;
+ zpm_seterror(zpm, "db error: %s", sqlite3_errstr(rv));
+ }
+
+ sqlite3_finalize(st);
+ return ;
+}
+
char *zpm_db_string(struct zpm *zpm, char *query, ...) {
sqlite3_stmt *st;
va_list args;
sqlite3_finalize(st);
return result;
}
+
+int zpm_db_int(struct zpm *zpm, char *query, ...) {
+ sqlite3_stmt *st;
+ va_list args;
+ int rv;
+ int result = 0;
+
+ va_start(args, query);
+ st = zpm_dbqueryv(zpm, query, args);
+ va_end(args);
+
+ rv = sqlite3_step(st);
+
+ if (rv == SQLITE_ROW) {
+ result = sqlite3_column_int(st, 0);
+ }
+ /* TODO set error if it's not SQLITE_ROW */
+
+ sqlite3_finalize(st);
+ return result;
+}
/* hash package files */
sql = sqlite3_mprintf("select path, mode, username, groupname, configuration, "
- "filetype, target, devmajor, devminor, mtime, hash "
+ "filetype, target, device, mtime, hash "
"from packagefiles_pkgid where pkgid = %Q order by path",
pkgid);
hash_query(zpm, sql, &d);
--- /dev/null
+#define _POSIX_C_SOURCE 200809L
+
+#include <inttypes.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "sqlite3.h"
+#include "zpm.h"
+
+void zpm_note_ack(struct zpm *zpm, int64_t note) {
+ char *in = "update notes set ack = 1 where id = %" PRId64;
+ zpm_db_run(zpm, in, note);
+}
+
+void zpm_note_unack(struct zpm *zpm, int64_t note) {
+ char *in = "update notes set ack = 0 where id = %" PRId64;
+ zpm_db_run(zpm, in, note);
+}
+
+void zpm_note_del(struct zpm *zpm, int64_t note) {
+ char *in = "delete from notes where id = %" PRId64;
+ zpm_db_run(zpm, in, note);
+}
+
+static char *colstring(sqlite3_stmt *s, int col) {
+ const char *val;
+ char *dup = 0;
+
+ val = (const char *)sqlite3_column_text(s, col);
+ if (val) {
+ dup = strdup(val);
+ }
+ return dup;
+}
+
+/* normally unacked only */
+/* 0x1 = next note,
+ * 0x2 = include acked
+ * 0x4 = suppress unack, implies 0x2
+ */
+/* TODO filter on pkgid/path/hash if not null */
+int64_t zpm_note(struct zpm *zpm, struct zpm_note *n, unsigned int flags) {
+ char *op = "=", *ack = " and ack = 0";
+
+ sqlite3_stmt *st;
+ int64_t id = 0;
+
+ if (flags & 0x1) {
+ op = ">";
+ }
+
+ if (flags & 0x4) {
+ ack = " and ack = 1";
+ } else if (flags & 0x2) {
+ ack = "";
+ }
+
+ st = zpm_dbquery(zpm, "select id,ts,note,pkgid,path,file,ack "
+ "from notes where id %s %" PRId64 "%s %s",
+ op, n->id, ack);
+
+ switch (sqlite3_step(st)) {
+ case SQLITE_DONE: /* not found */
+ break;
+ case SQLITE_ROW:
+ n->note = colstring(st, 2);
+ n->pkgid = colstring(st, 3);
+ n->path = colstring(st, 4);
+ n->file = colstring(st, 5);
+ n->ack = sqlite3_column_int(st, 6);
+ n->ts = sqlite3_column_int(st, 1);
+ n->id = sqlite3_column_int64(st, 0);
+ id = n->id;
+ break;
+ default: zpm->error = 1;
+ zpm->errmsg = strdup(sqlite3_errmsg(zpm->db));
+ break;
+ }
+
+ return id;
+}
+
+/* free any memory */
+void zpm_note_free(struct zpm_note *n) {
+ free(n->note);
+ free(n->pkgid);
+ free(n->path);
+ free(n->file);
+}
+
+int zpm_notes_available(struct zpm *zpm, int flags) {
+ int total;
+ char *sql;
+
+ if (!flags) {
+ sql = "select count(*) from notes where ack = 0";
+ } else {
+ sql = "select count(*) from notes";
+ }
+
+ total = zpm_db_int(zpm, sql);
+ return total;
+}
+
+/* get up to n notes following. return total number of notes found */
+/* set the first note id to the id before the first one you want
+ * or to 0 to start at the beginning. This can then
+ * be iterated up to n notes at a time by setting the id of the
+ * first one to the id found in the last one.
+ */
+int zpm_notes(struct zpm *zpm, int n, struct zpm_note *note) {
+ int64_t id;
+ int i;
+
+ for (i = 0; i < n; i++) {
+ id = zpm_note(zpm, note, 1);
+ if (!id) {
+ break;
+ }
+ note++;
+ if (i < 0) {
+ note->id = id;
+ }
+ }
+
+ return i;
+}
+
+int64_t zpm_note_next(struct zpm *zpm, struct zpm_note *n) {
+ return zpm_note(zpm, n, 1);
+}
+
+int64_t zpm_note_add(struct zpm *zpm, char *pkgid, char *path, char *filehash,
+ char *notefmt, ...) {
+ sqlite3_stmt *st;
+ char *note;
+ va_list ap;
+ int64_t id = 0;
+ char *in = "insert into notes (note,pkgid,path,file) values (?,?,?,?);";
+
+ if (!notefmt) {
+ return 0;
+ }
+
+ st = zpm_dbquery(zpm, in);
+
+ if (!st) {
+ zpm->error = 1;
+ zpm->errmsg = strdup(sqlite3_errmsg(zpm->db));
+ return 0;
+ }
+
+ va_start(ap, notefmt);
+ note = sqlite3_vmprintf(notefmt, ap);
+ va_end(ap);
+
+ if (!note) {
+ zpm->error = 1;
+ zpm_seterror(zpm, "can't alloc");
+ }
+
+ sqlite3_bind_text(st, 1, note, -1, SQLITE_STATIC);
+ sqlite3_bind_text(st, 2, pkgid, -1, SQLITE_STATIC);
+ sqlite3_bind_text(st, 3, path, -1, SQLITE_STATIC);
+ sqlite3_bind_text(st, 4, filehash, -1, SQLITE_STATIC);
+
+ switch (sqlite3_step(st)) {
+ case SQLITE_DONE:
+ id = sqlite3_last_insert_rowid(zpm->db);
+ break;
+ default: zpm->error = 1;
+ zpm->errmsg = strdup(sqlite3_errmsg(zpm->db));
+ break;
+ }
+
+ sqlite3_finalize(st);
+ sqlite3_free(note);
+ return id;
+}
--- /dev/null
+#define _POSIX_C_SOURCE 200809L
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+
+#include "zpm.h"
+
+void zpm_seterror(struct zpm *zpm, char *msgfmt, ...) {
+ char msg[1024];
+ va_list ap;
+
+ if (zpm->errmsg) {
+ free(zpm->errmsg);
+ zpm->errmsg = 0;
+ }
+
+ if (msgfmt != NULL) {
+ va_start(ap, msgfmt);
+ vsnprintf(msg, sizeof msg, msgfmt, ap);
+ va_end(ap);
+
+ msg[1023] = 0;
+ if (zpm->errmsg) {
+ free(zpm->errmsg);
+ }
+
+ zpm->errmsg = strdup(msg);
+ }
+}
+++ /dev/null
-#!/bin/sh
-
-# Admin notes:
-#
-# Each note is just a file. Stored under /var/spool/admin/notes/[open|ack]/<package>/<note>
-#
-# note: list notes
-# note ack <note> - moves note to "acknowledged" spool
-# note list [package] show notes for a given package, or all notes
-# note delete <note> - delete a note from the system
-# note edit <package> [file] - edit or create a note
-
-# TODO
-# discard unchanged new note file
-# quiet options to suppress new note name echo
-# semi-quiet to just output the number
-# renumber? to renumber all notes/reset sequence
-# lock the temp files
-# clean up unlocked temp files
-
-: ${ZPMSPOOL:=/var/lib/admin/notes}
-
-OPEN=$ZPMSPOOL/open
-
-set -e
-
-umask 007
-
-#cd $SPOOL || { echo "can't chdir to $SPOOL" ; exit 1; }
-
-[ -z "$1" ] && set list
-
-quiet=
-
-cmd=$1
-shift
-
-findnote() {
- file=$(find $ZPMSPOOL -type f -name "$1")
- if [ -z "$file" ] ; then
- [ -z "$quiet" ] || printf "no such note $1\n" 1>&2
- exit 1
- fi
- echo $file
-}
-
-findopen() {
- file=$(find $ZPMSPOOL/open -type f -name "$1")
- if [ -z "$file" ] ; then
- [ -z "$quiet" ] || printf "no such open note $1\n" 1>&2
- exit 1
- fi
- echo $file
-}
-
-case $cmd in
- category)
- notefile=$(findnote $1)
- printf "%s\n" $(basename $(dirname $notefile))
- ;;
- list)
- case $1 in
- -a) find $ZPMSPOOL/ack -type f -printf '%P\n' | sort -n
- ;;
- ?*) if [ -d $ZPMSPOOL/open/$1 ] ; then
- find $ZPMSPOOL/open/$1 -type f -printf '%P\n' | sort -n
- else
- exit 1
- fi
- ;;
- *) find $ZPMSPOOL/open -type f -printf '%P\n' | sort -n
- ;;
- esac
- ;;
- detail)
- len=$(find $ZPMSPOOL/open -type f -printf "%P\n" | awk ' { if ( length > x ) { x = length } }END{ print x }')
- find $ZPMSPOOL/open -type f -printf "%P\n" | sort -n | while read note ; do
- subject=$(head -1 $ZPMSPOOL/open/$note)
- date=$(stat -c '%y' $ZPMSPOOL/open/$note | cut -f1 -d' ')
- owner=$(stat -c '%U' $ZPMSPOOL/open/$note)
- printf '%*s %8s %s %s\n' $len $note $owner "$date" "$subject"
- done
- ;;
- ack)
- file=$(findopen $1)
- subfile=$(echo $file | sed -e "s|$ZPMSPOOL/open/||")
- filedir=$(dirname $subfile)
- mkdir -p $ZPMSPOOL/ack/$filedir
- mv $file $ZPMSPOOL/ack/$filedir
- ;;
- new)
- # new -- edit a new file for package general (or ZPMNOTEPACKAGE)
- # new package -- edit a new note for package
- # new package file -- use file for new package note
- if [ $# -eq 0 ]; then set ${ZPMNOTEPACKAGE:-general}; fi
-
- # edit a new file for the note
- if [ $# -eq 1 ]; then
- tmp=$(mktemp -p $ZPMSPOOL/open)
- pkg=$1
- [ -f $ZPMSPOOL/.template ] && cp $ZPMSPOOL/.template $tmp
- #flock -n -E3 $tmp vim $tmp
- #if [ $? -eq 3 ]; then echo already editing $1; exit 1; fi
- ${EDITOR:-vi} $tmp
- else
- # take note from file or stdin
- pkg=$1 ; shift
- mkdir -p $ZPMSPOOL/open/$pkg
- case $1 in
- '-')
- tmp=$(mktemp -p $ZPMSPOOL/open)
- cat - > $tmp
- ;;
- *)
- tmp=$(mktemp -p $ZPMSPOOL/open)
- cp $1 $tmp
- ;;
- esac
- fi
- file=$(zpm sequence notes)
- mkdir -p $ZPMSPOOL/open/$pkg
- mv $tmp $ZPMSPOOL/open/$pkg/$file || { rm -f $tmp; exit 1; }
- # TODO run newhooks in $ZPMSPOOL/.hook/newnote
- echo "$pkg/$file"
- exit 0
- ;;
- edit)
- file=$(findnote $1)
- flock -n -E3 $file vim $file
- if [ $? -eq 3 ]; then echo already editing $1; exit 1; fi
- ;;
- resolve)
- file=$(findnote $1)
- echo $file
- ;;
- remove)
- file=$(findnote $1)
- rm $file
- ;;
- show)
- file=$(findnote $1)
- ${PAGER:-less} $file
- ;;
- *)
- echo '$0: unknown command ' $cmd 1>&2
- exit 1
-esac
gid_t gid;
char *dest;
char *path;
- char *hash;
+ char *hash, *ohash;
char *target;
time_t mtime;
mode_t mode;
int ftype;
+ int configuration;
struct timespec times[2];
};
struct config *conf = f;
char *dest;
struct stat st;
+ int flags = 0;
dest = COL("dest");
if (!dest) return seterror(conf,"no file dest");
}
if (S_ISDIR(st.st_mode)) {
- if (conf->verbose) {
- fprintf(stderr, "rmdir(%s)\n", dest);
- }
- rmdir(dest);
- } else if (S_ISREG(st.st_mode)) {
- /* TODO conf to import before removal */
- if (conf->verbose) {
- fprintf(stderr, "unlink(%s)\n", dest);
- }
- if (unlink(dest) == -1) {
- return seterror(conf, "can't unlink");
- }
- } else {
- if (unlink(dest) == -1) {
- return seterror(conf, "can't unlink");
+ flags = AT_REMOVEDIR;
+ }
+ /* TODO check that expected filetype matches actual filetype */
+
+ if (conf->verbose) {
+ fprintf(stderr, "%s(%s)\n", flags ? "rmdir" : "unlink", dest);
+ }
+
+ errno = 0;
+
+ if (unlinkat(AT_FDCWD, dest, flags) == -1) {
+ switch (errno) {
+ case ENOENT:
+ break;
+ default:
+ return seterror(conf, "can't unlink");
}
}
#define D_GID 0x80
#define D_MODE 0x100
#define D_MTIME 0x200
+#define D_OHASH 0x400
#define D_ERROR 0x1000
#define D_STATERROR 0x2000
#define D_RLERROR 0x4000
if (strcmp(n->hash, ehash) != 0) {
diff |= D_HASH;
}
+ if (n->ohash && strcmp(n->ohash, ehash) != 0) {
+ diff |= D_OHASH;
+ }
}
if (n->hash && etype == S_IFLNK && stat_type == S_IFLNK) {
lsize = readlink(n->dest, link, sizeof link);
n->mode = strtoul(val, NULL, 8);
+ val = COL("configuration");
+ if (!val) {
+ seterror(conf, "can't determine config status");
+ return 0;
+ }
+ n->configuration = strtoul(val, NULL, 10);
+
val = COL("filetype");
if (!val || strlen(val) == 0) {
seterror(conf, "can't determine file type");
}
n->ftype = *val;
+ n->ohash = COL("hash");
+
if (n->ftype == 'r') {
n->hash = COL("hash");
if (!n->hash) {
/* flags: 1 = set md, 2 = create leading dirs, 4 = unlink existing file,
* 8 = rmdir existing dir, 16 = return true/false
*/
+#define INS_MD 0x1
+#define INS_CLD 0x2
+#define INS_UNLINK 0x4
+#define INS_RMDIR 0x8
+#define INS_RTF 0x10
+#define INS_ZPMNEW 0x20
static int install(struct config *conf, struct nitem *item, unsigned int flags) {
int rv = 1;
struct zpm *source;
struct nitem nitem;
struct stat existing;
int update = 0;
+ char dest[4096];
/* TODO put the result row in a hash table. May not actually
* be faster
return install(conf, &nitem, 3);
}
+ if (nitem.configuration) {
+ /* ohash == nhash, not an update */
+ /* fhash == ohash, just update */
+ /* fhash != ohash, install as dest.zpmnew, warn */
+ /* TODO handle replacing config file
+ * with config directory */
+ if (diffs & D_OHASH) {
+ if (strlen(nitem.dest) > sizeof dest - 8) {
+ return seterror(conf,"config file path too long for install as %s.zpmnew", nitem.dest);
+ }
+ fprintf(stderr, "installing as .zpmnew\n");
+ sprintf(dest, "%s.zpmnew", nitem.dest);
+ nitem.dest = dest;
+ }
+ }
+
/* file exists in filesystem */
if (sametype) {
if (mdsame && hashsame) {
int (*callback)(void *f, int ncols, char **vals, char **cols),
void *data, char **errmsg);
+
int zpm_script_hash(struct zpm *zpm, char *pkgstr, char *phase, char *hash);
int zpm_package_hash(struct zpm *zpm, char *pkgid, char *hash);
int zpm_package_sethash(struct zpm *zpm, char *pkgid, char *hash);
sqlite3_stmt *zpm_dbqueryv(struct zpm *zpm, char *query, va_list args);
sqlite3_stmt *zpm_dbquery(struct zpm *zpm, char *query, ...);
char *zpm_db_string(struct zpm *zpm, char *query, ...);
+int zpm_db_int(struct zpm *zpm, char *query, ...);
+void zpm_db_run(struct zpm *zpm, char *query, ...);
+void zpm_seterror(struct zpm *zpm, char *msgfmt, ...);
struct zpm *zpm_clearmem(struct zpm *zpm);
+struct zpm_note {
+ int64_t id;
+ time_t ts; /* or timespec */
+ char *note;
+ char *pkgid;
+ char *path;
+ char *file;
+ char *hash;
+ int ack;
+};
+
+void zpm_note_ack(struct zpm *zpm, int64_t note);
+void zpm_note_unack(struct zpm *zpm, int64_t note);
+void zpm_note_del(struct zpm *zpm, int64_t note);
+int64_t zpm_note(struct zpm *zpm, struct zpm_note *n, unsigned int flags);
+void zpm_note_free(struct zpm_note *n);
+int zpm_notes(struct zpm *zpm, int n, struct zpm_note *note);
+int64_t zpm_note_next(struct zpm *zpm, struct zpm_note *n);
+int64_t zpm_note_add(struct zpm *zpm, char *pkgid, char *path, char *filehash,
+ char *notefmt, ...);
+int zpm_notes_available(struct zpm *zpm, int flags);
+
#endif