SQLite uses this trick for its own temporary files (etilsq_XXXXXXXX).
unlink()ing before close() works fine in NestedVM when run on any UNIX
filesystem, but it does not work when NestedVM is running on a Windows
machine - the previously unlinked files still remain after the process
exits. This is due to the fact that Java does not attempt to abstract
its filesystem operations from the host operating system.
For this unlink-before-close functionality to work in NestedVM on
Windows it would have to keep a delete-on-close flag on each
NestedVM file object and if this flag is set, the file would have be
unlinked only after the last close is called on the file. The file's
open()/close() operations would have to be ref-counted so that close()
is only called when the open ref-count is 0. Upon exit (atexit, perhaps)
the list of file descriptors could be scanned in NestedVM to perform
these unlink operations after the files are closed.
This would not address the problem of temp files remaining if the
process crashes after the unlink() but before the close(), but it would
properly delete temp files in programs that exit cleanly.
I'd suggest to continue to attempt to delete the file immediately
upon unlink(), as it would would work correctly on UNIX, but also to
attempt to delete it again at last file close or program end. The
redundant delete would be just a no-op in UNIX, but necessary in Windows.
I may try to write a patch to address this issue, but it may not be for
a while. If someone wants to take a crack at it, feel free.
____________________________________________________________________________________
Don't pick lemons.
See all the new 2007 cars at Yahoo! Autos.
http://autos.yahoo.com/new_cars.html
$ cat unlink_before_close.c
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
int main() {
int bytes, rc;
const char msg[] = "hello,\nworld.\n";
const char fname[] = "foo.tmp";
int msg_len = sizeof(msg) / sizeof(msg[0]);
int fd = open(fname, O_WRONLY|O_CREAT, 0600);
if (fd < 0) {
printf("open failed: %s\n", strerror(errno));
return 1;
}
printf("open succeeded\n");
rc = unlink(fname);
if (rc < 0) {
printf("unlink failed: %s\n", strerror(errno));
return 2;
}
printf("unlink succeeded\n");
bytes = write(fd, msg, strlen(msg));
if (bytes < 0) {
printf("write failed: %s\n", strerror(errno));
return 3;
}
printf("wrote %d bytes\n", bytes);
rc = close(fd);
if (rc < 0) {
printf("close failed: %s\n", strerror(errno));
return 4;
}
printf("close succeeded\n");
return 0;
}
# native UNIX filesystem (or cygwin) - succeeds
$ gcc unlink_before_close.c -o unlink_before_close
$ ls foo.tmp
ls: cannot access foo.tmp: No such file or directory
$ ./unlink_before_close
open succeeded
unlink succeeded
wrote 14 bytes
close succeeded
$ ls foo.tmp
ls: cannot access foo.tmp: No such file or directory
# NestedVM on WIN2K, java 1.4.2 - fails
WIN2K$ ls foo.tmp
ls: cannot access foo.tmp: No such file or directory
WIN2K$ upstream/install/bin/mips-unknown-elf-gcc -mmemcpy -ffunction-sections -fdata-sections
-falign-functions=256 -fno-rename-registers -fno-schedule-insns -fno-delayed-branch
-freduce-all-givs -march=mips1 unlink_before_close.c -o unlink_before_close.mips
WIN2K$ java -cp build org.ibex.nestedvm.Interpreter unlink_before_close.mips
open succeeded
unlink failed: Not owner
Exit status: 2
WIN2K$ ls foo.tmp
foo.tmp
# sqlite3.mips on WIN2K under NestedVM, java 1.4.2 - fails
WIN2K$ rm -f etilqs_*
WIN2K$ ls etilqs_*
ls: cannot access etilqs_*: No such file or directory
WIN2K$ java -cp build org.ibex.nestedvm.Interpreter sqlite3.mips foo.db
SQLite version 3.3.10
Enter ".help" for instructions
sqlite> PRAGMA temp_store_directory = '.';
sqlite> create table xyz(x,y,z);
sqlite> insert into xyz values(4,5,6);
sqlite> insert into xyz values(1,2,3);
sqlite> vacuum;
sqlite> select * from xyz order by 3,2,1;
1|2|3
4|5|6
sqlite> .q
Exit status: 0
WIN2K$ ls etilqs_*
etilqs_g6W0rvm2YBEl8gh
Notice the undeleted sqlite temp file above.
Thanks.
____________________________________________________________________________________
8:00? 8:25? 8:40? Find a flick in no time
with the Yahoo! Search movie showtime shortcut.
http://tools.search.yahoo.com/shortcuts/#news
I'm not too crazy about this because it'll never work exactly right.
For example, if you unlink foo.txt then try to write to foo.txt again
it won't work right as you'd be writing to the same file (whereas on
unix you'd get a new file). I think I'd rather have it fail early than
do the wrong thing.
What does cygwin do in this case? I guess I wouldn't mind emulating
cygwin, at least there'd be some precedent for the decision.
-Brian
Content-Description: 4166930583-delete-on-close.diff.txt
> --- src-orig/org/ibex/nestedvm/Runtime.java 2007-01-27 10:41:21.046875000 -0500
> +++ src/org/ibex/nestedvm/Runtime.java 2007-02-04 23:48:04.203125000 -0500
> @@ -698,18 +698,18 @@
> return i;
> }
>
> - /** Hook for subclasses to do something when the process closes an FD */
> - void _closedFD(FD fd) { }
> + /** Hooks for subclasses before and after the process closes an FD */
> + void _preCloseFD(FD fd) { }
> + void _postCloseFD(FD fd) { }
>
> /** Closes file descriptor <i>fdn</i> and removes it from the file descriptor table */
> public final boolean closeFD(int fdn) {
> if(state == EXITED || state == EXECED) throw new IllegalStateException("closeFD called in inappropriate state");
> if(fdn < 0 || fdn >= OPEN_MAX) return false;
> if(fds[fdn] == null) return false;
> -
> - _closedFD(fds[fdn]);
> -
> + _preCloseFD(fds[fdn]);
> fds[fdn].close();
> + _postCloseFD(fds[fdn]);
> fds[fdn] = null;
> return true;
> }
> @@ -1209,6 +1209,14 @@
> /** File Descriptor class */
> public static abstract class FD {
> private int refCount = 1;
> + private String normalizedPath = null;
> + private boolean deleteOnClose = false;
> +
> + public void setNormalizedPath(String path) { normalizedPath = path; }
> + public String getNormalizedPath() { return normalizedPath; }
> +
> + public void markDeleteOnClose() { deleteOnClose = true; }
> + public boolean isMarkedForDeleteOnClose() { return deleteOnClose; }
>
> /** Read some bytes. Should return the number of bytes read, 0 on EOF, or throw an IOException on error */
> public int read(byte[] a, int off, int length) throws ErrnoException { throw new ErrnoException(EBADFD); }
> --- src-orig/org/ibex/nestedvm/UnixRuntime.java 2006-12-13 16:10:17.000000000 -0500
> +++ src/org/ibex/nestedvm/UnixRuntime.java 2007-02-05 00:32:42.484375000 -0500
> @@ -164,7 +164,10 @@
> }
>
> FD _open(String path, int flags, int mode) throws ErrnoException {
> - return gs.open(this,normalizePath(path),flags,mode);
> + path = normalizePath(path);
> + FD fd = gs.open(this,path,flags,mode);
> + if (fd != null && path != null) fd.setNormalizedPath(path);
> + return fd;
> }
>
> private int sys_getppid() {
> @@ -703,7 +706,7 @@
> return n;
> }
>
> - void _closedFD(FD fd) {
> + void _preCloseFD(FD fd) {
> // release all fcntl locks on this file
> Seekable s = fd.seekable();
> if (s == null) return;
> @@ -720,6 +723,13 @@
> } catch (IOException e) { throw new RuntimeException(e); }
> }
>
> + void _postCloseFD(FD fd) {
> + if (fd.isMarkedForDeleteOnClose()) {
> + try { gs.unlink(this, fd.getNormalizedPath()); }
> + catch (Throwable t) {}
> + }
> + }
> +
> /** Implements the F_GETLK and F_SETLK cases of fcntl syscall.
> * If l_start = 0 and l_len = 0 the lock refers to the entire file.
> * Uses GlobalState to ensure locking across processes in the same JVM.
> @@ -1617,7 +1627,21 @@
> File f = hostFile(path);
> if(r.sm != null && !r.sm.allowUnlink(f)) throw new ErrnoException(EPERM);
> if(!f.exists()) throw new ErrnoException(ENOENT);
> - if(!f.delete()) throw new ErrnoException(EPERM);
> + if(!f.delete()) {
> + // Can't delete file immediately, so mark for
> + // delete on close all matching FDs
> + boolean marked = false;
> + for(int i=0;i<OPEN_MAX;i++) {
> + if(r.fds[i] != null) {
> + String fdpath = r.fds[i].getNormalizedPath();
> + if(fdpath != null && fdpath.equals(path)) {
> + r.fds[i].markDeleteOnClose();
> + marked = true;
> + }
> + }
> + }
> + if(!marked) throw new ErrnoException(EPERM);
> + }
> }
>
> public FStat stat(UnixRuntime r, String path) throws ErrnoException {
Yes, it is a compromise, but it is better than before.
Most UNIX process expect UNIX-like filesystems.
It don't think it is possible to have a UNIX-like workaround from within
a JVM running on Windows filesystem unless you are prepared to make your
own synthetic filesystem; i.e., something similar to VMWare's filesystems
in a disk image.
The latest versions of SQLite now checks the return code of unlink(),
whereas it did not before. This is the only reason it "sorta" worked
on Windows before when the unlinks failed. I know all the world is not
SQLite, and NestedVM is attempting to be a UNIX-like environment for
any free software, but it is worth considering.
__________________________________________________
Do You Yahoo!?
Tired of spam? Yahoo! Mail has the best spam protection around
http://mail.yahoo.com
Please see the C program output earlier in this thread:
http://groups.google.com/group/nestedvm/msg/8729dbc6bbb340ae
Cygwin does something behind the scenes to make it work as on a
UNIX filesystem. I am not aware of a way to do the same thing in
Java, as Sun's JVM is not a Cygwin process.
____________________________________________________________________________________
Don't get soaked. Take a quick peak at the forecast
with the Yahoo! Search weather shortcut.
http://tools.search.yahoo.com/shortcuts/#loc_weather
Looks like Cygwin does something similar to your patch. I was actually
kind of hoping it would fail on cygwin so we could punt on the whole
thing :)
Cygwin does, however, not allow you to reopen "close-on-delete" files.
The second open here will fail on cygwin even though foo does indeed
exist at that point in the filesystem:
open("foo",O_CREAT|O_WRONLY);
unlink("foo");
open("foo",O_RDONLY);
This avoids that potential problem I mentioned. We'd probably need an
hashtable in somewhere that would contain all current close-on-delete
files so we could quickly check this on open, stat, etc.
We probably also have to look into how this all interacts with
UnixRuntime's virtual processes (might be ok as is, but I'd have to
check).
-Brian
It's easy enough to add a check in _open after the path is normalized.
Untested change after prior delete-on-close patch applied:
FD _open(String path, int flags, int mode) throws ErrnoException {
path = normalizePath(path);
for(int i=0;i<OPEN_MAX;i++) {
if(r.fds[i] != null && r.fds[i].isMarkedForDeleteOnClose()) {
String fdpath = r.fds[i].getNormalizedPath();
if(fdpath != null && fdpath.equals(path)) {
throw new ErrnoException(EPERM);
}
}
}
FD fd = gs.open(this,path,flags,mode);
if (fd != null && path != null) fd.setNormalizedPath(path);
return fd;
}
Which is probably good enough since unlinking a file while it is open is
a rare thing.