/* $Id$ * */ #include #include #include #include #include #include "daapd.h" #include "win32.h" #include "err.h" #include "os-win32.h" #include "w32-eventlog.h" #include "w32-service.h" /* Globals */ static WSADATA w32_wsadata; static os_serviceflag = 0; static pthread_t os_service_tid; static os_initialized=0; static pthread_mutex_t os_mutex=PTHREAD_MUTEX_INITIALIZER; /* Forwards */ static void _os_socket_startup(void); static void _os_socket_shutdown(void); static int _os_sock_to_fd(SOCKET sock); static void _os_lock(void); static void _os_unlock(void); static BOOL WINAPI _os_cancelhandler(DWORD dwCtrlType); extern int gettimeout(struct timeval end,struct timeval *timeoutp); #define OSFI_OPEN 1 #define OSFI_SHUTDOWN 2 #define NOTSOCK (fd < MAXDESC) #define REALSOCK (file_info[fd - MAXDESC].sock) #define SOCKSTATE (file_info[fd - MAXDESC].state) #define MAXBACKLOG 5 typedef struct tag_osfileinfo { SOCKET sock; int state; } OSFILEINFO; /* Globals */ OSFILEINFO file_info[MAXDESC]; char os_config_file[_MAX_PATH]; /* "official" os interface functions */ /** * initialize the os-specific stuff. this would include * backgrounding (or starting as service), setting up * signal handlers (or ctrl-c handlers), etc * * @param background whether or not to start in background (service) * @returns TRUE on success, FALSE otherwise */ int os_init(int foreground) { int err; _os_socket_startup(); if(!os_initialized) { _os_lock(); if(!os_initialized) { memset((void*)&file_info,0,sizeof(file_info)); } os_initialized=1; _os_unlock(); } if(!foreground) { /* startup as service */ os_serviceflag = 1; if((err=pthread_create(&os_service_tid,NULL,service_startup,NULL))) { DPRINTF(E_LOG,L_MISC,"Could not spawn thread: %s\n",strerror(err)); return FALSE; } } else { /* let's set a ctrl-c handler! */ SetConsoleCtrlHandler(_os_cancelhandler,TRUE); } return TRUE; } /** * shutdown the system-specific stuff started in os_init. */ void os_deinit(void) { _os_socket_shutdown(); if(os_serviceflag) { /* then we need to stop the service */ SetConsoleCtrlHandler(_os_cancelhandler,FALSE); service_shutdown(0); } } /** * open the syslog (eventlog) */ int os_opensyslog(void) { return elog_init(); } /** * close the syslog (eventlog) */ int os_closesyslog(void) { return elog_deinit(); } /** * write a message to the syslog * * @param level what level of message (1-10) * @param msg message to write * @return TRUE on success, FALSE otherwise */ int os_syslog(int level, char *msg) { return elog_message(level, msg); } int os_register(void) { service_register(); elog_register(); return TRUE; } int os_unregister(void) { service_unregister(); elog_unregister(); return TRUE; } static BOOL WINAPI _os_cancelhandler(DWORD dwCtrlType) { DPRINTF(E_LOG,L_MISC,"Shutting down with a console event\n"); config.stop = 1; return TRUE; } int _os_sock_to_fd(SOCKET sock) { int fd; if(sock == INVALID_SOCKET) return -1; DPRINTF(E_SPAM,L_MISC,"Converting socket to fd\n"); /* I was doing strange osfhandle stuff here, but it seemed * to be leaking file handles, and I don't really know what * it was doing. Thanks, Microsoft. Anyway, since I'm handling * reads, writes, opens and closes, I might just as well hand out * FAKE fds and swap them out to SOCKETS when I need to. No * more fd leaks. Problem solved. */ /* fd=_open_osfhandle(sock,O_RDWR|O_BINARY); // fd=_open_osfhandle(sock,0); if(fd > 0) { file_info[fd].flags = OSFI_SOCKET | OSFI_OPEN; file_info[fd].sock=sock; } else { DPRINTF(E_LOG,L_MISC,"Could not fd for socket osfhandle\n"); } */ _os_lock(); fd=0; while((fd < MAXDESC) && (file_info[fd].state)) { fd++; } if(fd == MAXDESC) { _os_unlock(); DPRINTF(E_FATAL,L_MISC,"Out of pseudo file handles. See ya\n"); } DPRINTF(E_SPAM,L_MISC,"Returning fd %d\n",fd + MAXDESC); file_info[fd].sock = sock; file_info[fd].state = OSFI_OPEN; _os_unlock(); return fd + MAXDESC; } int os_acceptsocket(int fd, char *hostn, int hostnsize) { socklen_t len = sizeof(struct sockaddr); struct sockaddr_in netclient; SOCKET retval; DPRINTF(E_SPAM,L_MISC,"Accepting socket %d -- %d\n",fd,REALSOCK); if(NOTSOCK || (SOCKSTATE != OSFI_OPEN)) { DPRINTF(E_LOG,L_MISC,"Bad socket passed to accept\n"); return -1; } while (((retval = accept(REALSOCK,(struct sockaddr *)(&netclient), &len)) == SOCKET_ERROR) && (WSAGetLastError() == WSAEINTR)); if ((retval == INVALID_SOCKET) || (hostn == NULL) || (hostnsize <= 0)) { DPRINTF(E_LOG,L_MISC,"Error accepting...\n"); return _os_sock_to_fd(retval); } strncpy(hostn,inet_ntoa(netclient.sin_addr),hostnsize); return _os_sock_to_fd(retval); } int os_waitfdtimed(int fd, struct timeval end) { fd_set readset; int retval; struct timeval timeout; SOCKET sock; DPRINTF(E_SPAM,L_MISC,"Timed wait on fd %d\n"); if(NOTSOCK || (SOCKSTATE != OSFI_OPEN)) return -1; sock = REALSOCK; /* if ((fd < 0) || (fd >= FD_SETSIZE)) { errno = EINVAL; return -1; } */ FD_ZERO(&readset); FD_SET(sock, &readset); if (gettimeout(end, &timeout) == -1) return -1; while (((retval = select(1, &readset, NULL, NULL, NULL)) == SOCKET_ERROR) && (WSAGetLastError() == WSAEINTR)) { if (gettimeout(end, &timeout) == -1) return -1; FD_ZERO(&readset); FD_SET(fd, &readset); } if (retval == 0) { errno = ETIME; return -1; } if (retval == -1) return -1; DPRINTF(E_SPAM,L_MISC,"Timed wait successful\n"); return 0; } /* from the gnu c library */ char *os_strsep(char **stringp, const char *delim) { char *begin, *end; begin = *stringp; if (begin == NULL) return NULL; /* A frequent case is when the delimiter string contains only one character. Here we don't need to call the expensive `strpbrk' function and instead work using `strchr'. */ if (delim[0] == '\0' || delim[1] == '\0') { char ch = delim[0]; if (ch == '\0') { end = NULL; } else { if (*begin == ch) end = begin; else if (*begin == '\0') end = NULL; else end = strchr (begin + 1, ch); } } else { /* Find the end of the token. */ end = strpbrk (begin, delim); } if (end) { /* Terminate the token and set *STRINGP past NUL character. */ *end++ = '\0'; *stringp = end; } else { /* No more delimiters; this is the last token. */ *stringp = NULL; } return begin; } int os_opensocket(unsigned short port) { int error; struct sockaddr_in server; SOCKET sock; int true = 1; int fd; DPRINTF(E_SPAM,L_MISC,"Opening socket\n"); /* make sure the file_info struct is initialized */ if(!os_initialized) { _os_lock(); if(!os_initialized) { memset((void*)&file_info,0,sizeof(file_info)); os_initialized=1; } _os_unlock(); } if((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) return -1; if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&true, sizeof(true)) == SOCKET_ERROR) { error = WSAGetLastError(); while ((closesocket(sock) == SOCKET_ERROR) && (WSAGetLastError() == WSAEINTR)); errno = EINVAL; /* windows errnos suck */ return -1; } server.sin_family = AF_INET; server.sin_addr.s_addr = htonl(INADDR_ANY); server.sin_port = htons((short)port); if ((bind(sock, (struct sockaddr *)&server, sizeof(server)) == -1) || (listen(sock, MAXBACKLOG) == -1)) { error = errno; while ((closesocket(sock) == SOCKET_ERROR) && (WSAGetLastError() == WSAEINTR)); errno = EINVAL; /* should be addrinuse or somethign */ return -1; } fd=_os_sock_to_fd(sock); DPRINTF(E_SPAM,L_MISC,"created socket %d\n",fd); return fd; } int os_write(int fd, void *buffer, unsigned int count) { int retval; if(NOTSOCK) { retval = _write(fd,buffer,count); } else { if(SOCKSTATE != OSFI_OPEN) { DPRINTF(E_LOG,L_MISC,"Write to socket with status: %d\n", file_info[fd-MAXDESC].state); return -1; } retval=send(REALSOCK,buffer,count,0); } return retval; } int os_read(int fd,void *buffer,unsigned int count) { int retval; DPRINTF(E_SPAM,L_MISC,"Reading %d bytes from %d\n",count,fd); if(NOTSOCK) { retval = _read(fd,buffer,count); } else { if(SOCKSTATE != OSFI_OPEN) { DPRINTF(E_LOG,L_MISC,"Read to socket with status: %d\n", file_info[fd-MAXDESC].state); return -1; } retval=recv(REALSOCK,buffer,count,0); DPRINTF(E_SPAM,L_MISC,"Actually returning %d\n",retval); } return retval; } int os_shutdown(int fd, int how) { if(NOTSOCK || (SOCKSTATE != OSFI_OPEN)) return -1; SOCKSTATE = OSFI_SHUTDOWN; shutdown(REALSOCK,how); return 0; } /* FIXME: mode */ int os_open(const char *filename, int oflag) { int fd; fd = _open(filename, oflag | O_BINARY); return fd; } int os_close(int fd) { if(NOTSOCK) { _close(fd); } else { /* socket */ if(SOCKSTATE == OSFI_OPEN) { os_shutdown(fd,SHUT_RDWR); } if(SOCKSTATE == OSFI_SHUTDOWN) { closesocket(REALSOCK); SOCKSTATE = 0; } } return 0; } /** * get uid of current user. this is really stubbed, as it's only used * as a check during startup (it fails normally if you run non-root, as it means * that it can't drop privs, can't write pidfile, etc) */ int os_getuid(void) { return 0; } /** * this is now pretty close to a true realpath implementation */ char *os_realpath(const char *pathname, char *resolved_path) { char *ptr; if(!_fullpath(resolved_path,pathname,PATH_MAX)) { DPRINTF(E_FATAL,L_MISC,"Could not realpath %s\n",pathname); } ptr = resolved_path; while(*ptr) { *ptr = tolower(*ptr); if(*ptr == '/') *ptr = '\\'; ptr++; } return &resolved_path[0]; } int os_gettimeofday (struct timeval *tv, struct timezone* tz) { union { long long ns100; /*time since 1 Jan 1601 in 100ns units */ FILETIME ft; } now; GetSystemTimeAsFileTime (&now.ft); tv->tv_usec = (long) ((now.ns100 / 10LL) % 1000000LL); tv->tv_sec = (long) ((now.ns100 - 116444736000000000LL) / 10000000LL); if(tz) { tz->tz_minuteswest = _timezone; } return (0); } /** * initialize winsock */ void _os_socket_startup(void) { WORD minver; int err; minver = MAKEWORD( 2, 2 ); err = WSAStartup( minver, &w32_wsadata ); if ( err != 0 ) { DPRINTF(E_FATAL,L_MISC,"Could not initialize winsock\n"); } } /** * deinitialize winsock */ void _os_socket_shutdown(void) { WSACleanup(); } /* COMPAT FUNCTIONS */ time_t timegm(struct tm *tm) { time_t ret; char *tz; char buffer[255]; tz = getenv("TZ"); _putenv("TZ=UTC0"); _tzset(); ret = mktime(tm); if(tz) sprintf(buffer,"TZ=%s",tz); else strcpy(buffer,"TZ="); _putenv(buffer); _tzset(); return ret; } /* opendir/closedir/readdir emulation taken from emacs. Thanks. :) */ DIR *os_opendir(char *filename) { DIR *dirp; /* Opening is done by FindFirstFile. However, a read is inherent to this operation, so we defer the open until read time. */ if (!(dirp = (DIR *) malloc (sizeof (DIR)))) return NULL; dirp->dir_find_handle = INVALID_HANDLE_VALUE; dirp->dd_fd = 0; dirp->dd_loc = 0; dirp->dd_size = 0; strncpy (dirp->dir_pathname, filename,_MAX_PATH); dirp->dir_pathname[_MAX_PATH] = '\0'; return dirp; } void os_closedir(DIR *dirp) { /* If we have a find-handle open, close it. */ if (dirp->dir_find_handle != INVALID_HANDLE_VALUE) { FindClose(dirp->dir_find_handle); } free((char *) dirp); } int os_readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result) { if (dirp->dir_find_handle == INVALID_HANDLE_VALUE) { /* If we aren't dir_finding, do a find-first, otherwise do a find-next. */ char filename[MAXNAMLEN + 3]; int ln; strcpy (filename, dirp->dir_pathname); ln = (int) strlen (filename) - 1; if(filename[ln] != '\\') strcat (filename, "\\"); strcat (filename, "*"); dirp->dir_find_handle = FindFirstFile (filename, &dirp->dir_find_data); if (dirp->dir_find_handle == INVALID_HANDLE_VALUE) { *result=NULL; return 2; } } else { if (!FindNextFile (dirp->dir_find_handle, &dirp->dir_find_data)) { *result = NULL; return 0; } } /* Emacs never uses this value, so don't bother making it match value returned by stat(). */ entry->d_ino = 1; entry->d_namlen = (int) strlen (dirp->dir_find_data.cFileName); entry->d_reclen = sizeof (struct dirent) - MAXNAMLEN + 3 + entry->d_namlen - entry->d_namlen % 4; strcpy (entry->d_name, dirp->dir_find_data.cFileName); /* if (dir_is_fat) _strlwr (dir_static.d_name); else if (!NILP (Vw32_downcase_file_names)) { register char *p; for (p = dir_static.d_name; *p; p++) if (*p >= 'a' && *p <= 'z') break; if (!*p) _strlwr (dir_static.d_name); } */ *result = entry; return 0; } /* can't be worse then strerror */ char *os_strerror (int error_no) { static char buf[500]; if (error_no == 0) error_no = GetLastError (); buf[0] = '\0'; if (!FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM, NULL, error_no, 0, /* choose most suitable language */ buf, sizeof (buf), NULL)) sprintf (buf, "w32 error %u", error_no); return buf; } /** * get the default config path. there might be an argument to be made * for using the install path as determined by registry, but might * just be easiest to grab the directory the executable is running from * * @returns path to config file (from static buffer) */ char *os_configpath(void) { char drive[_MAX_DRIVE]; char dir[_MAX_DIR]; char working_dir[_MAX_PATH]; GetModuleFileName(NULL,os_config_file,MAX_PATH); _splitpath(os_config_file,drive,dir,NULL,NULL); _makepath(os_config_file,drive,dir,"mt-daapd","conf"); _makepath(working_dir,drive,dir,NULL,NULL); if(_chdir(working_dir) == -1) { DPRINTF(E_LOG,L_MISC,"Could not chdir to %s... using c:\\\n",working_dir); if(_chdir("c:\\") == -1) { DPRINTF(E_FATAL,L_MISC,"Could not chdir to c:\\... aborting\n"); } } DPRINTF(E_DBG,L_MISC,"Using config file %s\n",os_config_file); return os_config_file; } /** * Lock the mutex. This is used for initialization stuff, among * other things (?) */ void _os_lock(void) { int err; if((err=pthread_mutex_lock(&os_mutex))) { DPRINTF(E_FATAL,L_MISC,"Cannot lock mutex\n"); } } /** * Unlock the os mutex */ void _os_unlock(void) { int err; if((err=pthread_mutex_unlock(&os_mutex))) { DPRINTF(E_FATAL,L_MISC,"Cannot unlock mutex\n"); } }