/* fhandler.cc. See console.cc for fhandler_console functions. Copyright 1996, 1997, 1998, 1999, 2000, 2001 Cygnus Solutions. This file is part of Cygwin. This software is a copyrighted work licensed under the terms of the Cygwin license. Please consult the file "CYGWIN_LICENSE" for details. */ #include "winsup.h" #include #include #include #include #include #include #include "cygerrno.h" #include "perprocess.h" #include "security.h" #include "cygwin/version.h" #include "fhandler.h" #include "path.h" #include "dtable.h" #include "cygheap.h" #include "shared_info.h" #include "sigproc.h" #include "pinfo.h" #include #include static char fhandler_fifo_dummy_name[] = "some fifo"; /**********************************************************************/ /* fhandler_fifo */ /* Observed behaviour of fifo's on un*x. * * First reader or writer blocks. * pipe reads return EOF when last writer quits * pipe writes return -1 and error EPIPE when the last reader quits. */ fhandler_fifo::fhandler_fifo (const char *name): fhandler_base (FH_FIFO, (uint) name) { set_need_fork_fixup (); unix_path_name = NULL; win32_path_name = NULL; } int fhandler_fifo::open (const char *path, int flags, mode_t mode) { syscall_printf ("(%s, %p)", path, flags); /* O_NOSYMLINK is an internal flag for implementing lstat, nothing more. */ path_conv real_path (path, (flags & O_NOSYMLINK) ? PC_SYM_NOFOLLOW : PC_SYM_FOLLOW); if (real_path.error && (flags & O_NOSYMLINK || real_path.error != ENOENT || !(flags & O_CREAT))) { set_errno (real_path.error); syscall_printf ("0 = fhandler_fifo::open (%s, %p)", path, flags); return 0; } return open (real_path, flags, mode); } int fhandler_fifo::open (path_conv * preal_path, int flags, mode_t mode) { path_conv& real_path = *preal_path; win32_path_name = cstrdup(real_path.get_win32 ()); unix_path_name = real_path.return_and_clear_normalized_path(); set_has_acls (real_path.has_acls ()); if (real_path.file_attributes () != (DWORD) - 1 && (real_path.file_attributes () & FILE_ATTRIBUTE_DIRECTORY)) { set_errno (EPIPE); syscall_printf ("fhandler_fifo::open attempt to open a directory as a FIFO\n"); } system_printf ("FIFO opening : %s\n", real_path.get_win32 ()); const char *path = get_name (); /* insert OS test here - build security descriptor for NT */ /* and GLOBAL\ prefix for terminal serices */ if ((flags & (O_RDONLY | O_WRONLY | O_RDWR)) == O_RDONLY) { // fifoaccess = FILE_MAP_READ; reader = 1; writer = 0; } else if ((flags & (O_RDONLY | O_WRONLY | O_RDWR)) == O_WRONLY) { // fifoaccess = FILE_MAP_WRITE; reader = 0; writer = 1; } else { // fifoaccess = FILE_MAP_WRITE; reader = 1; writer = 1; } set_flags(flags); unsigned int created = 0; SYSTEM_INFO sysinfo; size_t buffer_offset; // todo enter the FIFO global mutex // if os=nt4 ts or win2k or above add "Global\" as a prefix. // setup our names snprintf (filemapname, MAX_PATH, "FIFOFM%s", path); snprintf (rrname, MAX_PATH, "FIFORR%s", path); snprintf (rmname, MAX_PATH, "FIFORM%s", path); snprintf (wmname, MAX_PATH, "FIFOWM%s", path); snprintf (dmname, MAX_PATH, "FIFODM%s", path); snprintf (wdname, MAX_PATH, "FIFOWD%s", path); snprintf (rsname, MAX_PATH, "FIFORS%s", path); snprintf (wsname, MAX_PATH, "FIFOWS%s", path); GetSystemInfo (&sysinfo); if (sysinfo.dwAllocationGranularity >= sizeof (class fifoshared)) buffer_offset = sysinfo.dwAllocationGranularity; else buffer_offset = sysinfo.dwAllocationGranularity * (1 + sizeof (class fifoshared) / sysinfo.dwAllocationGranularity); // FIXME: we need two file map objects. One for the header (r/w for every allowed user // and one for data transfer (access only r or w as per file access rights filemap = CreateFileMapping (INVALID_HANDLE_VALUE, // system pagefile. /* default security - FIXME set allowed access from file permissions */ &sec_none, // and set this appropriately. PAGE_READWRITE, // protection 0x00000000, 0x00100000 + buffer_offset, // 1 Mb data, filemapname // object name ); /* This is not quite what we need to test the file access permissions, but it's a * starting point.*/ #if 0 HANDLE thehandle; thehandle = CreateFile (win32_path_name_, access_, FILE_SHARE_READ | FILE_SHARE_WRITE, &sec_all_nih, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); is_open = 1; return 1; /*success */ #endif if (filemap == NULL) { system_printf ("failed to open mapping: %d\n", GetLastError ()); } else { if (GetLastError () != ERROR_ALREADY_EXISTS) created = 1; /* FIXME: FOR all the create routines: use the security descriptor created above */ readreadyevent = CreateEvent (&sec_none, // SD FALSE, // reset type FALSE, // initial state rrname // object name ); if (readreadyevent == NULL) { system_printf ("failed to open event : %d\n", GetLastError ()); //close the filemap } writingmutex = CreateMutex (&sec_none, // SD FALSE, // initial owner wmname // object name ); if (writingmutex == NULL) { system_printf ("failed to open write mutex : %d\n", GetLastError ()); //close the event and the filemap } readmutex = CreateMutex (&sec_none, // SD FALSE, // initial owner rmname // object name ); if (readmutex == NULL) { system_printf ("failed to open read mutex : %d\n", GetLastError ()); //close the event and the filemap andt eh write mutex } datamutex = CreateMutex (&sec_none, // SD FALSE, // initial owner dmname // object name ); if (datamutex == NULL) { system_printf ("failed to open read mutex : %d\n", GetLastError ()); //close the event and the filemap andt eh write mutex } writedoneevent = CreateEvent (&sec_none, // SD FALSE, // reset type FALSE, // initial state wdname // object name ); if (readreadyevent == NULL) { system_printf ("failed to open event : %d\n", GetLastError ()); //close the event and the filemap and both mutexs } guts = (class fifoshared *) MapViewOfFile (filemap, FILE_MAP_WRITE, 0, 0, 0); if (guts == NULL) { system_printf ("failed to map fifo file to memory: %d\n", GetLastError ()); //close both event and the filemap and both mutexs } // FIXME: SEH for read and writes? if (created) { if (reader) guts->readers = 1; else guts->readers = 0; if (writer) guts->writers = 1; else guts->writers = 0; guts->buffer_offset = buffer_offset; guts->buffer_length = 0x00100000; //1 Mb guts->data_length = 0; // how much data is ready to be read (starts at guts->data_offset = 0; // data_offset guts->pipeactive = 0; // 0 = pipe waiting for an open } else { if (guts->buffer_offset != buffer_offset) { system_printf ("incorrect data offset - check for multiple cygwin dll's \n"); //close both event and the filemap and both mutexs and free the mapped view } if (reader) guts->readers++; if (writer) guts->writers++; } // FIX ME set the file map read/write to the requested access. filedata = MapViewOfFile (filemap, FILE_MAP_WRITE, 0, guts->buffer_offset, guts->buffer_length); if (filedata == NULL) { system_printf ("failed to map fifo data file to memory: %d\n", GetLastError ()); //close both event and the filemap and both mutexs and the first file view } } system_printf("FIFO opened\n"); // FIXME free the FIFO mutex; return 1; #if 0 /* We don't care about the _actual_ file. Maybe we should for locking purposes? */ int res = this->fhandler_base::open (flags, mode); if (!res) goto out; /* This is for file systems known for having a buggy CreateFile call which might return a valid HANDLE without having actually opened the file. The only known file system to date is the SUN NFS Solstice Client 3.1 which returns a valid handle when trying to open a file in a non existant directory. */ if (real_path.has_buggy_open () && GetFileAttributes (win32_path_name_) == (DWORD) - 1) { debug_printf ("Buggy open detected."); close (); set_errno (ENOENT); return 0; } extern BOOL allow_ntea; if (real_path.isdisk () && (real_path.exec_state () == dont_know_if_executable) && !allow_ntea && (!allow_ntsec || !real_path.has_acls ())) { DWORD done; char magic[3]; /* FIXME should we use /etc/magic ? */ magic[0] = magic[1] = magic[2] = '\0'; ReadFile (get_handle (), magic, 3, &done, 0); if (has_exec_chars (magic, done)) real_path.set_exec (); if (!(flags & O_APPEND)) SetFilePointer (get_handle (), 0, 0, FILE_BEGIN); } if (flags & O_APPEND) SetFilePointer (get_handle (), 0, 0, FILE_END); set_symlink_p (real_path.issymlink ()); set_execable_p (real_path.exec_state ()); set_socket_p (real_path.issocket ()); out: syscall_printf ("%d = fhandler_fifo::open (%s, %p)", res, get_win32_name (), flags); return res; #endif } int fhandler_fifo::close () { int rv = 0; // FIXME Get the FIFO mutex if (!filemap) return 0; // what for already closed files? else { if (reader) guts->readers--; if (writer) guts->writers--; if (!(guts->readers) || !(guts->writers)) { guts->pipeactive = 2; // 2 = closed; system_printf ("last reader or writer closed the pipe\n"); system_printf ("Telling the current blocked writers"); PulseEvent (readreadyevent); system_printf ("... and readers\n"); PulseEvent (writedoneevent); } system_printf ("on close: readers %d\n", guts->readers); system_printf ("on close: writers %d\n", guts->writers); if (!CloseHandle (filemap)) rv = -1; /* what error !?!?! */ filemap=NULL; if (!CloseHandle (readreadyevent)) rv = -1; /* what error !?!?! */ readreadyevent=NULL; if (!CloseHandle (writingmutex)) rv = -1; /* what error !?!?! */ writingmutex=NULL; if (!CloseHandle (readmutex)) rv = -1; /* what error !?!?! */ readmutex=NULL; if (!CloseHandle (datamutex)) rv = -1; /* what error !?!?! */ datamutex=NULL; if (!CloseHandle (writedoneevent)) rv = -1; /* what error !?!?! */ writedoneevent=NULL; if (!UnmapViewOfFile (filedata)) rv = -1; /* what error !?!?! */ filedata=NULL; if (!UnmapViewOfFile (guts)) rv = -1; /* what error !?!?! */ guts=NULL; } // FIXME free the FIFO mutex. #if 0 int res; if ((res = this->fhandler_base::close ()) == 0) cygwin_shared->delqueue.process_queue (); return res; #endif return rv; /* Success. Failure is -1 */ } int fhandler_fifo::dup (fhandler_base * child) { fhandler_fifo *fhc = (fhandler_fifo *) child; if (!fhc->open (get_name (), get_flags (), 0)) system_printf ("error opening fifo, %E"); #if 0 fhandler_dev_clipboard *fhc = (fhandler_dev_clipboard *) child; if (!fhc->open (get_name (), get_flags (), 0)) system_printf ("error opening clipboard, %E"); fhc->membuffer = membuffer; fhc->pos = pos; fhc->msize = msize; #endif return 0; } /* -1 = error. 0= EOF. + = bytes read */ int fhandler_fifo::write (const void *buf, size_t len) { unsigned int t; size_t current_length; size_t current_offset; // FIXME: did we open with write permissions? // FIXME: is the fd open? can it get this far if it's closed? // FIXME: is len > max_positive_int ? syscall_printf ("fhandler_fifo::write %x, %d\n", buf,len); t = WaitForSingleObject (writingmutex, INFINITE // time-out interval ); if (t == WAIT_OBJECT_0) { syscall_printf ("fhandler_fifo::write we have the write mutex %d \n", GetLastError ()); current_length = 0; current_offset = 0; t = WaitForSingleObject (datamutex, INFINITE); if (t != WAIT_OBJECT_0) { /* an error of some sort */ system_printf ("Error getting the datamutex - bad exit on the pipe...?? %d %d \n", t, GetLastError ()); } while (current_offset < len) { syscall_printf ("fhandler_fifo::write is there any queued data ? "); while (guts->data_length && guts->pipeactive < 2) { system_printf ("fhandler_fifo::write %d bytes still in the pipe\n", guts->data_length); if (guts->pipeactive > 1) { syscall_printf ("fhandler_fifo::write and the pipe has been closed\n"); ReleaseMutex (datamutex); ReleaseMutex (writingmutex); set_errno(EPIPE); return -1; } if (get_flags() & O_NONBLOCK) { syscall_printf("fhandler_fifo::write returning due to O_NONBLOCK\n"); set_errno(EAGAIN); ReleaseMutex (datamutex); return -1; } t = SignalObjectAndWait (datamutex, readreadyevent, INFINITE, FALSE); if (t == WAIT_OBJECT_0) { // system_printf (" // a read has occured rv %d lasterr %d\n", t, // GetLastError ()); } else if (t == WAIT_ABANDONED) { system_printf ("********* wait timed out! | !?!\n"); } else if (t == WAIT_FAILED) { system_printf ("**********wait failed rv - %d lasterr %d\n", t, GetLastError ()); } else { system_printf ("************unexpected return value - %d lasterr %d\n", t, GetLastError ()); } t = WaitForSingleObject (datamutex, INFINITE); if (t != WAIT_OBJECT_0) { system_printf ("**************/* abandoned mutex - bad exit on the pipe...?? %d %d \n", t, GetLastError ()); } } syscall_printf ("fhandler_fifo::write no data in the pipe.\n"); if (guts->pipeactive > 1) { // system_printf // (" ****but the last reader has quit and the pipe has been closed\n"); ReleaseMutex (datamutex); // system_printf (" released the datamutex, rv %d, getlasterror %d\n", t, // GetLastError ()); ReleaseMutex (writingmutex); set_errno( EPIPE); return -1; } syscall_printf ("fhandler_fifo::write there are either readers waiting, or this is the first write. \n"); // send up to 1 Mb of data. - FIXME: return an error on nonblocking writes of more than 1 Mb // system_printf ("writing\n"); current_length = len - current_offset; if (current_length > 0x00100000) current_length = 0x00100000; memcpy (filedata, (char *) buf + current_offset, current_length); guts->data_offset = 0; guts->data_length = current_length; current_offset += current_length; current_length = 0; if (current_offset == len) guts->eow = 1; else guts->eow = 0; syscall_printf ("fhandler_fifo::write data written, telling any current reader, and waiting for a read to occur\n"); // for non blocking, don't wait for them to get the data if (!(get_flags() & O_NONBLOCK)) { t = ReleaseMutex (datamutex); // system_printf (" released datamutex, rv %d, getlasterror %d\n", t, // GetLastError ()); t = SignalObjectAndWait (writedoneevent, readreadyevent, INFINITE, FALSE); // system_printf (" we have the datamutex\n"); if (t != WAIT_OBJECT_0) { system_printf ("error waiting on readreadyevent\n"); } t = WaitForSingleObject (datamutex, INFINITE); if (t != WAIT_OBJECT_0) { system_printf ("**************/* abandoned mutex - bad exit on the pipe...?? %d %d \n", t, GetLastError ()); } } else { syscall_printf("skippedwaiting for read confirmationdue to O_NONBLOCK\n"); PulseEvent(writedoneevent); } } t = ReleaseMutex (datamutex); system_printf (" released the datamutex, rv %d, getlasterror %d\n", t, GetLastError ()); system_printf ("releasing write mutex\n"); ReleaseMutex (writingmutex); return current_offset; } else if (t == WAIT_ABANDONED_0) { system_printf ("**** mutex was abandoned. \n"); ReleaseMutex (writingmutex); // return fifowrite (data, buf, len); } else { system_printf ("*******/* we don't timeout */ rv=%d err= %d\n", t, GetLastError ()); return 0; } return 0; } int __stdcall fhandler_fifo::read (void *ptr, size_t len) { unsigned int t; size_t current_length, current_offset=0; if (!filemap) { set_errno(EBADF); return -1; } if (guts->pipeactive > 1) return 0; system_printf("reading a maximum of %d bytes, nonblocking = %d\n",len, get_flags() & O_NONBLOCK); // system_printf (" /* wait for the read mutex */\n"); t = WaitForSingleObject (readmutex, // handle to object INFINITE // time-out interval ); #if 0 if (guts->pipeactive > 1) { system_printf ("// the pipe has been closed\n"); system_printf (" // current pipe status. ml=%d dl=%d, eow=%d\n", len, guts->data_length, guts->eow); //seterno EPIPE; ReleaseMutex (readmutex); return 0; } #endif if (t == WAIT_OBJECT_0) { // system_printf ("got the read mutex %d\n", t); current_length = 0; current_offset = 0; t = WaitForSingleObject (datamutex, INFINITE); /* system_printf (" // got the data mutex. co=%d ml=%d dl=%d, eow=%d\n", current_offset, len, guts->data_length, guts->eow); */ while (current_offset < len && !((guts->eow) && guts->data_length == 0)) { // system_printf (" is there data waiting?"); while (!guts->data_length) { // system_printf ("..no.."); // there is no data in the pipe if (get_flags() & O_NONBLOCK) { set_errno(EAGAIN); system_printf ("NO data, not blocking\n"); ReleaseMutex (datamutex); ReleaseMutex (readmutex); return -1; } // is the pipe open? if (guts->pipeactive > 1) { system_printf ("amd the pipe has been closed\n"); /* Note: the first writer blocks, so we _know_ that there was a writer. * EPIPE is not generated here - just EOF */ ReleaseMutex (datamutex); ReleaseMutex (readmutex); return current_offset; } t = SignalObjectAndWait (datamutex, writedoneevent, INFINITE, FALSE); // a write has occured t = WaitForSingleObject (datamutex, INFINITE); if (t != WAIT_OBJECT_0) { system_printf ("Error obtaining mutex - possibly an abandoned mutex - bad exit on the pipe... */\n"); } } // system_printf ("..yes %d bytes of data waiting\n", // guts->data_length); // system_printf ("reading"); current_length = guts->data_length; if (current_length + current_offset > len) current_length = len - current_offset; memcpy ((char *) ptr + current_offset, (char *) filedata + guts->data_offset, current_length); current_offset += current_length; if (!guts->pipeactive) guts->pipeactive = 1; // an io has completed // system_printf // ("...signalling acceptance of %d bytes of data...\n", // current_length); guts->data_length -= current_length; guts->data_offset += current_length; PulseEvent (readreadyevent); } if (guts->data_length == 0) guts->eow = 0; ReleaseMutex (datamutex); // read complete ReleaseMutex (readmutex); } else if (t == WAIT_ABANDONED) { system_printf ("abandoned mutex found \n"); } else { system_printf (" we don't have the mutex - should never ever happen (no timeout was set\n"); } return current_offset; } off_t fhandler_fifo::lseek (off_t offset, int whence) { // for a pipe, you can't seek into the stream you can only rewind. // FIXME: should this be fixing the pipe for _every_ read and writer? or just us. if (!filemap) { set_errno(EBADF); return -1; } WaitForSingleObject (datamutex, INFINITE); guts->pipeactive=0; ReleaseMutex(datamutex); return 0; } void fhandler_fifo::fixup_after_fork (HANDLE parent) { system_printf("fhandler_fifo::fixup_after_fork name = '%s', filemapname='%s'\n",get_name(),filemapname); open(get_name(), get_flags(), 0); }