| 1 | package dolda.jsvc.store; |
| 2 | |
| 3 | import dolda.jsvc.*; |
| 4 | import dolda.jsvc.util.*; |
| 5 | import java.io.*; |
| 6 | import java.util.*; |
| 7 | import java.security.*; |
| 8 | |
| 9 | class FileStore extends Store { |
| 10 | private final java.io.File base; |
| 11 | private static final int smbuflimit; |
| 12 | private static int txserial = 0; |
| 13 | |
| 14 | static { |
| 15 | int res; /* Java is stupid, as usual... */ |
| 16 | try { |
| 17 | String p = System.getProperty("dolda.jsvc.store.smallbuf"); |
| 18 | if(p != null) |
| 19 | res = Integer.parseInt(p); |
| 20 | else |
| 21 | res = 65536; |
| 22 | } catch(SecurityException e) { |
| 23 | res = 65536; |
| 24 | } |
| 25 | smbuflimit = res; |
| 26 | } |
| 27 | |
| 28 | private FileStore(Package pkg, java.io.File root) { |
| 29 | super(pkg); |
| 30 | String nm = pkg.getName(); |
| 31 | java.io.File base = root; |
| 32 | int p = 0; |
| 33 | int p2; |
| 34 | while((p2 = nm.indexOf('.', p)) >= 0) { |
| 35 | base = new java.io.File(base, nm.substring(p, p2)); |
| 36 | p = p2 + 1; |
| 37 | } |
| 38 | this.base = new java.io.File(base, nm.substring(p)); |
| 39 | AccessController.doPrivileged(new PrivilegedAction<Object>() { |
| 40 | public Object run() { |
| 41 | if(!FileStore.this.base.exists()) { |
| 42 | if(!FileStore.this.base.mkdirs()) |
| 43 | throw(new RuntimeException("Could not create store directory (Java won't tell me why)")); |
| 44 | } |
| 45 | return(null); |
| 46 | } |
| 47 | }); |
| 48 | } |
| 49 | |
| 50 | private static String mangle(String in) { |
| 51 | byte[] bytes = in.getBytes(Misc.utf8); |
| 52 | StringBuilder buf = new StringBuilder(); |
| 53 | for(int i = 0; i < bytes.length; i++) { |
| 54 | byte b = bytes[i]; |
| 55 | if(((b >= '0') && (b <= '9')) || ((b >= 'A') && (b <= 'Z')) || ((b >= 'a') && (b <= 'z'))) { |
| 56 | buf.append((char)b); |
| 57 | } else { |
| 58 | buf.append('_'); |
| 59 | buf.append(Misc.int2hex((b & 0xf0) >> 4, true)); |
| 60 | buf.append(Misc.int2hex(b & 0x0f, true)); |
| 61 | } |
| 62 | } |
| 63 | return(buf.toString()); |
| 64 | } |
| 65 | |
| 66 | private static String demangle(String in) { |
| 67 | ByteArrayOutputStream buf = new ByteArrayOutputStream(); |
| 68 | for(int i = 0; i < in.length(); i++) { |
| 69 | char c = in.charAt(i); |
| 70 | if(c == '_') { |
| 71 | char d1 = in.charAt(i + 1); |
| 72 | char d2 = in.charAt(i + 2); |
| 73 | i += 2; |
| 74 | buf.write((byte)((Misc.hex2int(d1) << 4) | Misc.hex2int(d2))); |
| 75 | } else { |
| 76 | if(c >= 256) |
| 77 | throw(new RuntimeException("Invalid filename in store")); |
| 78 | buf.write(c); |
| 79 | } |
| 80 | } |
| 81 | byte[] bytes = buf.toByteArray(); |
| 82 | return(new String(bytes, Misc.utf8)); |
| 83 | } |
| 84 | |
| 85 | private class RFile implements File { |
| 86 | private final java.io.File fs; |
| 87 | private final String name; |
| 88 | |
| 89 | private class TXStream extends OutputStream { |
| 90 | private FileOutputStream buf = null; |
| 91 | private java.io.File tmpfile; |
| 92 | private boolean closed = false; |
| 93 | |
| 94 | private void init() throws IOException { |
| 95 | try { |
| 96 | buf = AccessController.doPrivileged(new PrivilegedExceptionAction<FileOutputStream>() { |
| 97 | public FileOutputStream run() throws IOException { |
| 98 | synchronized(RFile.class) { |
| 99 | int serial = txserial++; |
| 100 | tmpfile = new java.io.File(fs.getPath() + ".new." + txserial); |
| 101 | if(tmpfile.exists()) { |
| 102 | if(!tmpfile.delete()) |
| 103 | throw(new IOException("Could not delete previous temporary file (Java won't tell my why)")); |
| 104 | } |
| 105 | return(new FileOutputStream(tmpfile)); |
| 106 | } |
| 107 | } |
| 108 | }); |
| 109 | } catch(PrivilegedActionException e) { |
| 110 | throw((IOException)e.getCause()); |
| 111 | } |
| 112 | } |
| 113 | |
| 114 | public void write(byte[] b, int off, int len) throws IOException { |
| 115 | if(closed) |
| 116 | throw(new IOException("This file has already been committed")); |
| 117 | if(buf == null) |
| 118 | init(); |
| 119 | buf.write(b, off, len); |
| 120 | } |
| 121 | |
| 122 | public void write(byte[] b) throws IOException { |
| 123 | if(closed) |
| 124 | throw(new IOException("This file has already been committed")); |
| 125 | if(buf == null) |
| 126 | init(); |
| 127 | buf.write(b); |
| 128 | } |
| 129 | |
| 130 | public void write(int b) throws IOException { |
| 131 | if(closed) |
| 132 | throw(new IOException("This file has already been committed")); |
| 133 | if(buf == null) |
| 134 | init(); |
| 135 | buf.write(b); |
| 136 | } |
| 137 | |
| 138 | public void flush() throws IOException { |
| 139 | if(buf == null) |
| 140 | init(); |
| 141 | buf.flush(); |
| 142 | } |
| 143 | |
| 144 | public void close() throws IOException { |
| 145 | flush(); |
| 146 | closed = true; |
| 147 | buf.close(); |
| 148 | try { |
| 149 | AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() { |
| 150 | public Object run() throws IOException { |
| 151 | if(!tmpfile.renameTo(fs)) { |
| 152 | fs.delete(); |
| 153 | if(!tmpfile.renameTo(fs)) |
| 154 | throw(new IOException("Could not replace previous file contents with new (Java won't tell me why)")); |
| 155 | } |
| 156 | return(null); |
| 157 | } |
| 158 | }); |
| 159 | } catch(PrivilegedActionException e) { |
| 160 | throw((IOException)e.getCause()); |
| 161 | } |
| 162 | } |
| 163 | } |
| 164 | |
| 165 | private RFile(java.io.File fs, String name) { |
| 166 | this.fs = fs; |
| 167 | this.name = name; |
| 168 | } |
| 169 | |
| 170 | public String name() { |
| 171 | return(name); |
| 172 | } |
| 173 | |
| 174 | public InputStream read() { |
| 175 | return(AccessController.doPrivileged(new PrivilegedAction<InputStream>() { |
| 176 | public InputStream run() { |
| 177 | try { |
| 178 | return(new FileInputStream(fs)); |
| 179 | } catch(FileNotFoundException e) { |
| 180 | return(null); |
| 181 | } |
| 182 | } |
| 183 | })); |
| 184 | } |
| 185 | |
| 186 | public OutputStream store() { |
| 187 | return(new TXStream()); |
| 188 | } |
| 189 | |
| 190 | public long mtime() { |
| 191 | return(AccessController.doPrivileged(new PrivilegedAction<Long>() { |
| 192 | public Long run() { |
| 193 | return(fs.lastModified()); |
| 194 | } |
| 195 | })); |
| 196 | } |
| 197 | |
| 198 | public void remove() { |
| 199 | AccessController.doPrivileged(new PrivilegedAction<Object>() { |
| 200 | public InputStream run() { |
| 201 | if(!fs.delete()) |
| 202 | throw(new RuntimeException("Could not delete the file " + fs.getPath() + " (Java won't tell me why)")); |
| 203 | return(null); |
| 204 | } |
| 205 | }); |
| 206 | } |
| 207 | } |
| 208 | |
| 209 | public File get(String name) { |
| 210 | return(new RFile(new java.io.File(base, mangle(name)), name)); |
| 211 | } |
| 212 | |
| 213 | public Iterator<File> iterator() { |
| 214 | final java.io.File[] ls = base.listFiles(); |
| 215 | return(new Iterator<File>() { |
| 216 | private int i = 0; |
| 217 | private File cur = null; |
| 218 | |
| 219 | public boolean hasNext() { |
| 220 | return(i < ls.length); |
| 221 | } |
| 222 | |
| 223 | public File next() { |
| 224 | java.io.File f = ls[i++]; |
| 225 | cur = new RFile(f, demangle(f.getName())); |
| 226 | return(cur); |
| 227 | } |
| 228 | |
| 229 | public void remove() { |
| 230 | if(cur == null) |
| 231 | throw(new IllegalStateException()); |
| 232 | cur.remove(); |
| 233 | cur = null; |
| 234 | } |
| 235 | }); |
| 236 | } |
| 237 | |
| 238 | public static void register() { |
| 239 | Store.register("file", new Factory() { |
| 240 | public Store create(String rootname, Package pkg) { |
| 241 | return(new FileStore(pkg, new java.io.File(rootname))); |
| 242 | } |
| 243 | }); |
| 244 | } |
| 245 | } |