From: E L. <cr...@my...> - 2008-03-07 16:26:09
|
I have a situation where it appears Windows does not release a file after immediately after my program calls fclose() when there are redirected child processes active. Is this standard operation? Or an artifact of mingw libs working with ms libs? Is there a way to avoid this problem? Below is test code that shows the issue. When popen is not called, the test file can be deleted immediately. When popen is called, the test file can not be deleted until the pipe is closed (or at EOF) child.c: #include <stdio.h> int main() { int i; for (i=0;i<10;i++) { printf("line %i\n", i); fflush(stdout); _sleep(100); } return 0; } parent.c: #include <stdio.h> #include <stdlib.h> #include <process.h> int main() { FILE *f, *p; int ch, err = 1; /* create any file to test unlink */ f = fopen("junk.txt", "w"); fprintf(f, "junk\n"); fclose(f); f = fopen("junk.txt", "r"); p = popen("child.exe", "r"); fclose(f); do { if (err) { err = unlink("junk.txt"); printf("unlink %s\n", err ? "error" : "success"); } ch = fgetc(p); } while (err); return 0; } |
From: Tor L. <tm...@ik...> - 2008-03-07 17:02:04
|
> f = fopen("junk.txt", "r"); > p = popen("child.exe", "r"); The problem is that here the C file descriptor for f (and more importantly the underlying Win32 file HANDLE for it) is inherited by the child process. > fclose(f); Closing the file in the parent doesn't close it in the child. > err = unlink("junk.txt"); As the file is open in the child process, deleting it will fail. You cannot delete an open file on Windows unless the right to delete it while open was specified in the CreateFile() call that opened the file. The Microsoft C runtime doesn't do that for some reason. Read the MSDN documenation for CreateFile(), and the DELETE bit of the "standard access rights" and the FILE_SHARE_DELETE bit of the "share mode". What you need to do is prevent the file HANDLE for the file from being inherited by the child process. If you use open() to open the file instead of fopen(), you can add the O_NOINHERIT flag to the open flags. If you for some reason do want the file HANDLE (and C file descriptor) to be inherited, you could also use the lower level CreateFile() to open the file with delete access (and possibly even allowing other processes to delete it while it is open), and then create a C file descriptor for the file HANDLE by using _open_osfhandle. Yes, this is totally unlike Unix, but then nobody claimed Windows is like Unix. --tml |
From: Andy <que...@ya...> - 2008-03-07 17:09:16
|
If I remember correctly, when you spawn a child process, all open file handles in the parent are inherited by the child. Thus in your example, when popenn is called you will have two open file handles, one owned by the parent and one by the child. You are then closing one of them in the parent, but the one inherited by the child is left open. The unlink cannot then delete the file. I think on Unix, the unlink would succeed - it decrements the file usage count - and the file would be deleted when the child ended, releasing its handle and decrementing the usage count to 0. Some programs used this feature to delete a file from the filesystem before it was finished with - a kind of pending delete. I'm not sure whether this unlinking behaviour is reproduced on Windows - so you might find that it simply fails. E Lofstad wrote: > I have a situation where it appears Windows does not release a file after immediately after my program calls fclose() when there are redirected child processes active. Is this standard operation? Or an artifact of mingw libs working with ms libs? Is there a way to avoid this problem? > > -- |
From: Brian D. <br...@de...> - 2008-03-07 22:24:01
|
Tor Lillqvist wrote: > What you need to do is prevent the file HANDLE for the file from being > inherited by the child process. If you use open() to open the file > instead of fopen(), you can add the O_NOINHERIT flag to the open > flags. There are other ways of marking a HANDLE as non-inheritable after it has been opened. The simplest is SetHandleInformation(), which you can use in conjunction with _fileno() to get the fd of the FILE * and _get_osfhandle() to get the HANDLE of the fd: if (!SetHandleInformation (_get_osfhandle (_fileno (f)), HANDLE_FLAG_INHERIT, 0)) { /* error */ } The problem with this approach is that SetHandleInformation() is only available on NT/2k/XP/2k3/Vista/2k8, not 9x/ME. For most people that's not really an issue but it can be problematic. Another method is to use DuplicateHandle() with the bInheritHandle parameter set to FALSE. You then have a copy of the HANDLE that won't be inherited, and you can close the original (or pass DUPLICATE_CLOSE_SOURCE in dwOptions to do it in one step.) The problem now is that you have to use this new HANDLE in place of the old. If you're using the C fd API, you can accomplish this with _open_osfhandle(); if you're using the C FILE * API, then you can then further use _fdopen() to create a FILE * from that fd. This is rather tedious, but it works on all versions of Windows: HANDLE newh; int newfd; FILE *newf; if (!DuplicateHandle (GetCurrentProcess (), _get_osfhandle (_fileno (f)), GetCurrentProcess (), &newh, 0, FALSE, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS) || -1 == (newfd = _open_osfhandle (newh, _O_RDWR | _O_BINARY)) || NULL == (newf = _fdopen (newfd, "w+b"))) { /* error */ } (Naturally, you'd have to use the correct file mode flags/string. Since MSVCRT doesn't support fcntl (fd, F_GETFL) I don't think there's a way to get them from the existing fd, sadly.) Finally, the blunt-object approach is to simply call CreateProcess() with bInheritHandles set to FALSE, which will override the individual handle settings. But this is probably not all that useful as inheriting handles is how things like _popen() are usually implemented. > Yes, this is totally unlike Unix, but then nobody claimed Windows is like Unix. On the contrary, I find it quite similar to unix in that when you fork/exec to create a child process, all open file descriptors are inherited. Of course in that case you have more fine grained control, as you can close() any sensitive fds that you don't want the new process to have access to in the period following fork() but before exec() on the child side. What is different is the fact that unix allows an unlink() on files and directories with open handles (without the need for any FILE_SHARE_DELETE bit), so the testcase would succeed. Of course the unlinked file would continue to exist until all fds are closed, it would just no longer be accessible through any directory entry. Brian |