--- /dev/null
+package dolda.jsvc.store;
+
+import dolda.jsvc.*;
+import dolda.jsvc.util.*;
+import java.io.*;
+import java.util.*;
+import java.security.*;
+
+class FileStore extends Store {
+ private final java.io.File base;
+ private static final int smbuflimit;
+ private static int txserial = 0;
+
+ static {
+ int res; /* Java is stupid, as usual... */
+ try {
+ String p = System.getProperty("dolda.jsvc.store.smallbuf");
+ if(p != null)
+ res = Integer.parseInt(p);
+ else
+ res = 65536;
+ } catch(SecurityException e) {
+ res = 65536;
+ }
+ smbuflimit = res;
+ }
+
+ private FileStore(Package pkg, java.io.File root) {
+ super(pkg);
+ String nm = pkg.getName();
+ java.io.File base = root;
+ int p = 0;
+ int p2;
+ while((p2 = nm.indexOf('.', p)) >= 0) {
+ base = new java.io.File(base, nm.substring(p, p2));
+ p = p2 + 1;
+ }
+ this.base = new java.io.File(base, nm.substring(p));
+ AccessController.doPrivileged(new PrivilegedAction<Object>() {
+ public Object run() {
+ if(!FileStore.this.base.mkdirs())
+ throw(new RuntimeException("Could not create store directory (Java won't tell me why)"));
+ return(null);
+ }
+ });
+ }
+
+ private static String mangle(String in) {
+ byte[] bytes = in.getBytes(Misc.utf8);
+ StringBuilder buf = new StringBuilder();
+ for(int i = 0; i < bytes.length; i++) {
+ byte b = bytes[i];
+ if(((b >= '0') && (b <= '9')) || ((b >= 'A') && (b <= 'Z')) || ((b >= 'a') && (b <= 'z'))) {
+ buf.append((char)b);
+ } else {
+ buf.append('_');
+ buf.append(Misc.int2hex((b & 0xf0) >> 4, true));
+ buf.append(Misc.int2hex(b & 0x0f, true));
+ }
+ }
+ return(buf.toString());
+ }
+
+ private static String demangle(String in) {
+ ByteArrayOutputStream buf = new ByteArrayOutputStream();
+ for(int i = 0; i < in.length(); i++) {
+ char c = in.charAt(i);
+ if(c == '_') {
+ char d1 = in.charAt(i + 1);
+ char d2 = in.charAt(i + 2);
+ i += 2;
+ buf.write((byte)((Misc.hex2int(d1) << 4) | Misc.hex2int(d2)));
+ } else {
+ if(c >= 256)
+ throw(new RuntimeException("Invalid filename in store"));
+ buf.write(c);
+ }
+ }
+ byte[] bytes = buf.toByteArray();
+ return(new String(bytes, Misc.utf8));
+ }
+
+ private class RFile implements File {
+ private final java.io.File fs;
+ private final String name;
+
+ private class TXStream extends OutputStream {
+ private FileOutputStream buf = null;
+ private java.io.File tmpfile;
+ private boolean closed = false;
+
+ private void init() throws IOException {
+ try {
+ buf = AccessController.doPrivileged(new PrivilegedExceptionAction<FileOutputStream>() {
+ public FileOutputStream run() throws IOException {
+ synchronized(RFile.class) {
+ int serial = txserial++;
+ tmpfile = new java.io.File(fs.getPath() + ".new." + txserial);
+ if(tmpfile.exists()) {
+ if(!tmpfile.delete())
+ throw(new IOException("Could not delete previous temporary file (Java won't tell my why)"));
+ }
+ return(new FileOutputStream(tmpfile));
+ }
+ }
+ });
+ } catch(PrivilegedActionException e) {
+ throw((IOException)e.getCause());
+ }
+ }
+
+ public void write(byte[] b, int off, int len) throws IOException {
+ if(closed)
+ throw(new IOException("This file has already been committed"));
+ if(buf == null)
+ init();
+ buf.write(b, off, len);
+ }
+
+ public void write(byte[] b) throws IOException {
+ if(closed)
+ throw(new IOException("This file has already been committed"));
+ if(buf == null)
+ init();
+ buf.write(b);
+ }
+
+ public void write(int b) throws IOException {
+ if(closed)
+ throw(new IOException("This file has already been committed"));
+ if(buf == null)
+ init();
+ buf.write(b);
+ }
+
+ public void flush() throws IOException {
+ if(buf == null)
+ init();
+ buf.flush();
+ }
+
+ public void close() throws IOException {
+ flush();
+ closed = true;
+ buf.close();
+ try {
+ AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
+ public Object run() throws IOException {
+ if(!tmpfile.renameTo(fs)) {
+ fs.delete();
+ if(!tmpfile.renameTo(fs))
+ throw(new IOException("Could not replace previous file contents with new (Java won't tell me why)"));
+ }
+ return(null);
+ }
+ });
+ } catch(PrivilegedActionException e) {
+ throw((IOException)e.getCause());
+ }
+ }
+ }
+
+ private RFile(java.io.File fs, String name) {
+ this.fs = fs;
+ this.name = name;
+ }
+
+ public String name() {
+ return(name);
+ }
+
+ public InputStream read() {
+ return(AccessController.doPrivileged(new PrivilegedAction<InputStream>() {
+ public InputStream run() {
+ try {
+ return(new FileInputStream(fs));
+ } catch(FileNotFoundException e) {
+ return(null);
+ }
+ }
+ }));
+ }
+
+ public OutputStream store() {
+ return(new TXStream());
+ }
+
+ public long mtime() {
+ return(AccessController.doPrivileged(new PrivilegedAction<Long>() {
+ public Long run() {
+ return(fs.lastModified());
+ }
+ }));
+ }
+
+ public void remove() {
+ AccessController.doPrivileged(new PrivilegedAction<Object>() {
+ public InputStream run() {
+ if(!fs.delete())
+ throw(new RuntimeException("Could not delete the file " + fs.getPath() + " (Java won't tell me why)"));
+ return(null);
+ }
+ });
+ }
+ }
+
+ public File get(String name) {
+ return(new RFile(new java.io.File(base, mangle(name)), name));
+ }
+
+ public Iterator<File> iterator() {
+ final java.io.File[] ls = base.listFiles();
+ return(new Iterator<File>() {
+ private int i = 0;
+ private File cur = null;
+
+ public boolean hasNext() {
+ return(i < ls.length);
+ }
+
+ public File next() {
+ java.io.File f = ls[i++];
+ cur = new RFile(f, demangle(f.getName()));
+ return(cur);
+ }
+
+ public void remove() {
+ if(cur == null)
+ throw(new IllegalStateException());
+ cur.remove();
+ cur = null;
+ }
+ });
+ }
+
+ public static void register() {
+ Store.register("file", new Factory() {
+ public Store create(String rootname, Package pkg) {
+ java.io.File root = new java.io.File(rootname);
+ ThreadContext ctx = ThreadContext.current();
+ if(ctx != null) {
+ if(ctx.server().name() != null)
+ root = new java.io.File(root, ctx.server().name());
+ }
+ return(new FileStore(pkg, root));
+ }
+ });
+ }
+}
import dolda.jsvc.*;
import dolda.jsvc.util.Misc;
import java.io.*;
-import java.security.*;
-import java.security.cert.Certificate;
import java.util.*;
-public class Store {
+public abstract class Store implements Iterable<File> {
+ private static Map<String, Factory> kinds = new TreeMap<String, Factory>();
private static Map<Package, Store> interned = new WeakHashMap<Package, Store>();
- private final Package pkg;
- private final File base;
+ protected final Package pkg;
- private Store(Package pkg, File root) {
+ protected Store(Package pkg) {
this.pkg = pkg;
- String nm = pkg.getName();
- File base = root;
- int p = 0;
- int p2;
- while((p2 = nm.indexOf('.', p)) >= 0) {
- base = new File(base, nm.substring(p, p2));
- p = p2 + 1;
- }
- this.base = new File(base, nm.substring(p));
}
- private static File getstoreroot() {
+ public abstract File get(String name);
+
+ public static interface Factory {
+ public Store create(String root, Package pkg);
+ }
+
+ private static String getstoreroot() {
ThreadContext ctx = ThreadContext.current();
if(ctx == null)
throw(new RuntimeException("Not running in jsvc context"));
String bn = ctx.server().config("jsvc.storage");
if(bn == null)
throw(new RuntimeException("No storage root has been configured"));
- return(new File(bn));
+ return(bn);
}
-
+
public static Store forclass(final Class<?> cl) {
Package pkg = cl.getPackage();
- File root = getstoreroot();
Store s;
synchronized(interned) {
s = interned.get(pkg);
if(s == null) {
- s = new Store(pkg, root);
+ String root = getstoreroot();
+ int p = root.indexOf(':');
+ if(p < 0)
+ throw(new RuntimeException("Invalid store specification: " + root));
+ String kind = root.substring(0, p);
+ root = root.substring(p + 1);
+ Factory fac;
+ synchronized(kinds) {
+ fac = kinds.get(kind);
+ if(fac == null)
+ throw(new RuntimeException("No such store kind: " + kind));
+ }
+ s = fac.create(root, pkg);
interned.put(pkg, s);
}
}
return(s);
}
+
+ public static void register(String kind, Factory fac) {
+ synchronized(kinds) {
+ if(!kinds.containsKey(kind))
+ kinds.put(kind, fac);
+ else
+ throw(new RuntimeException("Store of type " + kind + " already exists"));
+ }
+ }
+
+ static {
+ FileStore.register();
+ }
}