/* * kissdx - KiSS PC-Link Daemon eXtended (based on kissd) * * Copyright (C) 2005 Stelian Pop * Portions Copyright (C) 2006 Vidar Tysse * * Heavily based on kiss4lin, * Copyright (C) 2004 Jacob Kolding * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef USE_INTERNAL_SENDFILE #include #else #ifdef Linux #include #endif #endif #include "dvdread.h" #include "kissdx.h" #include "config.h" #include "utils.h" #include "sendfile.h" #include "piccache.h" #include "gdstuff/gdstuff.h" #define C_PSEUDOFOLDER_PREFIX_FIRSTCHAR "{" #define C_ANY_PSEUDOFOLDER_PREFIX C_PSEUDOFOLDER_PREFIX_FIRSTCHAR"kissdx-" #define C_PSEUDOFOLDER_PREFIX_LASTCHAR "}" #define C_DIR_PSEUDOFOLDER_TYPE "DIR" #define C_ISO_PSEUDOFOLDER_TYPE "ISO" #define C_M3U_PSEUDOFOLDER_TYPE "M3U" #define C_PLS_PSEUDOFOLDER_TYPE "PLS" #define C_PSEUDOFOLDER_TYPE_LEN 3 #define C_DIR_PSEUDOFOLDER_PREFIX C_ANY_PSEUDOFOLDER_PREFIX C_DIR_PSEUDOFOLDER_TYPE C_PSEUDOFOLDER_PREFIX_LASTCHAR #define C_ISO_PSEUDOFOLDER_PREFIX C_ANY_PSEUDOFOLDER_PREFIX C_ISO_PSEUDOFOLDER_TYPE C_PSEUDOFOLDER_PREFIX_LASTCHAR #define C_M3U_PSEUDOFOLDER_PREFIX C_ANY_PSEUDOFOLDER_PREFIX C_M3U_PSEUDOFOLDER_TYPE C_PSEUDOFOLDER_PREFIX_LASTCHAR #define C_PLS_PSEUDOFOLDER_PREFIX C_ANY_PSEUDOFOLDER_PREFIX C_PLS_PSEUDOFOLDER_TYPE C_PSEUDOFOLDER_PREFIX_LASTCHAR #define C_ISO_PSEUDOFILE_SUFFIX ".mpg" #define C_PSEUDODVD_TITLE_PART1 "Title no. " #define C_PSEUDODVD_TITLE_PART2 " of " const char *subtitle_file_extensions = "srt,aqt,sub,rt,smi,ssa,txt"; const char *JPEG_extensions = "jpeg,jpg"; /* Prototypes for external functions */ int handle_playlist(int sock, char *path); int CachedSizeResizedJPEG(const char *filename); int CachedSendResizedJPEG(int fd, const char *filename, off_t offset, size_t chunksize); /* Prototypes for internal functions */ static int store_recent(char * filename); static int transform_request_for_recent_file(char *request); int will_resize_jpeg_file(const char *path) { // We resize this file if resizing is configured AND this file is in the pictures folder AND this is not our pseudo-jpeg AND this file has a valid jpeg extension: return (config.picturetargetwidth && config.picturetargetheight && !strncmp(path, config.picturepath, strlen(config.picturepath)) && strncmp(path + strlen(config.picturepath) + 1, config.enablehiddenfilestext, strlen(config.enablehiddenfilestext)) && list_contains_extension(JPEG_extensions, path)); } static int my_filelist_compare(const void *ap, const void *bp) { struct dirent **a = (struct dirent**)ap; struct dirent **b = (struct dirent**)bp; // Files and directories should be listed alphabetically, insensitive to character case return strcasecmp((*a)->d_name, (*b)->d_name); } /// Get the next picture for possible pre-caching static int get_next_picture(char *next_picture_filename, const char *current_picture_filename) { char first_picture_basename[PATH_MAX] = "", last_picture_basename[PATH_MAX] = ""; char current_picture_dirname[PATH_MAX] = "", current_picture_basename[PATH_MAX] = ""; int fileno = -1, current_picture_fileno = -1, next_picture_fileno = -1; int scandir_selector(const struct dirent *entry) { const char *thisname = entry->d_name; if (*thisname != '.' && config.picturefileextensions && *config.picturefileextensions && list_contains_extension(config.picturefileextensions, thisname)) { char fullname[PATH_MAX]; struct stat statbuf; snprintf(fullname, sizeof fullname, "%s/%s", current_picture_dirname, thisname); if (lstat(fullname, &statbuf) < 0) log("get_next_picture.scandir_selector: Couldn't stat %s", fullname); else if (!S_ISDIR(statbuf.st_mode)) { strcpy(last_picture_basename, thisname); if (++fileno == 0) strcpy(first_picture_basename, thisname); if (!strcmp(thisname, current_picture_basename)) current_picture_fileno = fileno; else if (current_picture_fileno > -1 && fileno > current_picture_fileno && next_picture_fileno == -1 && will_resize_jpeg_file(fullname)) { next_picture_fileno = fileno; return 1; } } } return 0; } char filename_buf[PATH_MAX], next_picture_basename[PATH_MAX] = ""; struct dirent **namelist; int num_files, n; strcpy(filename_buf, current_picture_filename); strcpy(current_picture_basename, basename(filename_buf)); strcpy(current_picture_dirname, dirname(filename_buf)); if ((num_files = scandir(current_picture_dirname, &namelist, scandir_selector, my_filelist_compare)) < 0) { log("get_next_picture: scandir %s: %s", current_picture_dirname, strerror(errno)); return -1; } else if (num_files > 1) { log("get_next_picture: scandir %s returned %d files. Expected 0 or 1.", current_picture_dirname, num_files); return -1; } else if (num_files == 0) { if (*last_picture_basename && *first_picture_basename && !strcmp(current_picture_basename, last_picture_basename)) strcpy(next_picture_basename, first_picture_basename); else { log("get_next_picture: scandir '%s' didn't find next picture for '%s'.", current_picture_dirname, current_picture_filename); return -1; } } else strcpy(next_picture_basename, namelist[0]->d_name); for (n = 0; n < num_files; n++) { free(namelist[n]); } free(namelist); snprintf(next_picture_filename, PATH_MAX, "%s/%s", current_picture_dirname, next_picture_basename); return 0; } static void precache_next_picture(const char *current_picture_filename) { // We fork a child process for this and then return immediately to continue processing // NOTE: This child will be orphaned (since we are already a child process) and init will take care of it switch (fork()) { case -1: log("precache_next_picture: fork failed: %s", strerror(errno)); return; case 0: { // This is done in the child process. // We must not return from here, we must exit (if we return, the calling logic will continue in this child process, in parallel with the parent process). char next_picture_filename[PATH_MAX] = "", cached_filename[PATH_MAX]; if (piccache_is_precaching_in_progress()) _exit(0); // We can only have one pre-caching process running if (get_next_picture(next_picture_filename, current_picture_filename)) _exit(0); // There is no next picture - nothing for us to do if (!piccache_get_entry_filename(cached_filename, next_picture_filename, config.picturetargetwidth, config.picturetargetheight, config.picturemaxzoompercent)) _exit(0); // The next picture is already cached if (!piccache_set_precaching_status(next_picture_filename)) { logv("Pre-caching %s", next_picture_filename); CachedSizeResizedJPEG(next_picture_filename); piccache_clear_precaching_status(); } _exit(0); } default: return; } } /// Types and vars for pseudofile commands et al enum pseudofile_command_t {pfc_none=0, pfc_list_hidden_entries}; static enum pseudofile_command_t pseudofile_command = pfc_none; static void * commandpseudofile_data = NULL; static int commandpseudofile_size = 0; /// If path is an "Command Pseudofile", generate the confirmation jpeg in memory, and return true. static int create_commandpseudofile_if_needed(char* path) { if (!strncmp(path, config.picturepath, strlen(config.picturepath))) { char text[255]; const char *filename = path + strlen(config.picturepath) + 1; if (*config.enablehiddenfilestext && !strncmp(filename, config.enablehiddenfilestext, strlen(config.enablehiddenfilestext))) { if (!commandpseudofile_data) { static const char* header = "CONFIGURATION CHANGE:"; static const char* footer = "Please return to the file list to use this setting."; static const char* text1 = "Viewing of hidden content"; snprintf(text, sizeof text, "is now %s", options.list_hidden_entries? "disabled" : "enabled"); if (!options.list_hidden_entries && config.enablehiddenfilesminutes) snprintf(text + strlen(text), sizeof text - strlen(text), " for %d minute%s", config.enablehiddenfilesminutes, config.enablehiddenfilesminutes > 1? "s" : ""); strncat(text, ".", sizeof text - strlen(text)); if (!(commandpseudofile_data = create_text_jpeg(header, text1, text, footer, &commandpseudofile_size)) || commandpseudofile_size <= 0) log("create_commandpseudofile_if_needed: create_text_jpeg returned NULL with size %d.", commandpseudofile_size); } pseudofile_command = pfc_list_hidden_entries; return 1; } // TODO: Future? Process any other Action Pseudofiles here in "else if" blocks } return 0; } /// Send a local command to our main process static void deliver_local_command(const char* command) { int sock; int yes = 1; struct sockaddr_un sun; /* get a local domain socket */ if ((sock = socket(PF_LOCAL, SOCK_STREAM, 0)) < 0) { log("command socket: %s", strerror(errno)); return; } /* lose the pesky "address already in use" error message */ if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) < 0) { log("command setsockopt: %s", strerror(errno)); return; } // Compose a socket structure to connect with the command server in our main process memset(&sun, 0, sizeof(sun)); sun.sun_family = AF_LOCAL; strncpy (sun.sun_path, commandserver_path, sizeof (sun.sun_path)); // Open a connection to the command server in our main process if (connect(sock, (struct sockaddr *) &sun, SUN_LEN (&sun)) < 0) { log("command connect: %s", strerror(errno)); return; } logv("Sending local command: %s", command); send(sock, command, strlen(command), 0); close (sock); } /// Perform appropriate command when user selects a Command Pseudofile static void perform_pseudofile_command(void) { switch (pseudofile_command) { case pfc_list_hidden_entries: { char command[LOCAL_COMMAND_SIZE]; // Compose a local command that will toggle the value of options.list_hidden_entries in our main process sprintf(command, "SET LIST_HIDDEN_ENTRIES %s", options.list_hidden_entries? "OFF" : "ON"); deliver_local_command(command); break; } case pfc_none: log0("No pseudofile command to be performed (none)"); break; default: log0("No pseudofile command to be performed (default)"); break; } } /// Transform a subtitle filename according to the subtitlefilemapping configuration option static void transform_subtitle_filename(char *filename) { char subtitle_dirname[PATH_MAX] = ""; char subtitle_basename_pattern[PATH_MAX] = ""; int scandir_selector(const struct dirent *entry) { const char *thisname = entry->d_name; if (*thisname != '.' && list_contains_extension(subtitle_file_extensions, thisname)) { char fullname[PATH_MAX]; struct stat statbuf; snprintf(fullname, sizeof fullname, "%s/%s", subtitle_dirname, thisname); if (lstat(fullname, &statbuf) < 0) log("scandir_selector: Couldn't stat %s", fullname); else if (!S_ISDIR(statbuf.st_mode)) return !fnmatch(subtitle_basename_pattern, thisname, FNM_CASEFOLD); } return 0; } char pattern[PATH_MAX], fullpattern[PATH_MAX], recentpath[PATH_MAX]; char filename_buf[PATH_MAX], path[PATH_MAX] = "", name[PATH_MAX] = "", name_and_ext[PATH_MAX] = "", ext[PATH_MAX] = "", *p; struct dirent **namelist; int num_files, n, ext_index, isPathInPattern, isRecent; if (!*config.subtitle_catchall_pattern) return; // Subtitle filename mapping has not been configured // Populate our substitutes for this file strcpy(filename_buf, filename); strcpy(name_and_ext, basename(filename_buf)); strcpy(name, name_and_ext); strcpy(path, dirname(filename_buf)); if (*(path + strlen(path) - 1) == '/') *(path + strlen(path) - 1) = '\0'; if ((p = strrchr(name, '.'))) { strcpy(ext, p + 1); *p = '\0'; } else *ext = '\0'; // Find this file's index into the list of subtitle filename extensions. This will be used to pick the appropriate subtitle file if (!*ext || !(p = (char *)list_pointer(subtitle_file_extensions, ext, ","))) return; // Unknown extension for (ext_index = 0; p > subtitle_file_extensions; p--) if (*p == ',') ext_index++; // If playing from the [Recently used] folder, get the real path from the symlink for the video file being played if (get_recent_path(recentpath, config.videopath)) return; isRecent = !strcmp(path, recentpath); if (isRecent) { char videofilename[PATH_MAX], target[PATH_MAX]; struct stat statbuf; strcpy(videofilename, filename); char *pExt = strrchr(videofilename, '.') + 1; strcpy(pExt, "avi"); if (casestat(videofilename, &statbuf) < 0) { log("transform_subtitle_filename: Cannot find video file symlink for subtitle file '%s'", filename); return; } if ((n = readlink(videofilename, target, PATH_MAX)) > 0) { *(target + n) = '\0'; strcpy(path, dirname(target)); } else { log("transform_subtitle_filename: Cannot find real file for symlink '%s'", videofilename); return; } } // Replace the special tokens in the pattern with our substitutes for this file strcpy(pattern, config.subtitle_catchall_pattern); isPathInPattern = string_substitute(pattern, "{path}", path, sizeof pattern); string_substitute(pattern, "{name}", name, sizeof pattern); string_substitute(pattern, "{ext}" , ext , sizeof pattern); // Construct the full pattern, including the path if (isPathInPattern) { *fullpattern = '\0'; } else { strcpy(fullpattern, path); strcat(fullpattern, "/"); } strcat(fullpattern, pattern); // Traverse a directory listing based on the pattern and pick the appropriate subtitle file p = strrchr(fullpattern, '/'); strcpy(subtitle_basename_pattern, p + 1); *p = '\0'; strcpy(subtitle_dirname, fullpattern); if ((num_files = scandir(fullpattern, &namelist, scandir_selector, my_filelist_compare)) < 0) { log("transform_subtitle_filename: scandir %s: %s", fullpattern, strerror(errno)); return; } for (n = 0; n < num_files; n++) { if (n == ext_index) { string_substitute(filename, name_and_ext, namelist[n]->d_name, PATH_MAX); if (isRecent) string_substitute(filename, recentpath, path, PATH_MAX); } free(namelist[n]); } free(namelist); } /* * Name: timesort * * Description: Sort function for scandir which sorts by time. * Most recent files first. */ static int timesort(const void * f1, const void * f2) { char fname1[PATH_MAX]; char fname2[PATH_MAX]; char cwd[PATH_MAX]; struct stat st1, st2; struct dirent *d1; struct dirent *d2; if (f1 == NULL || f2 == NULL) return 0; d1 = *(struct dirent **)f1; d2 = *(struct dirent **)f2; if (getcwd(cwd, PATH_MAX) == NULL) return 0; snprintf(fname1, PATH_MAX, "%s/%s", cwd, d1->d_name); snprintf(fname2, PATH_MAX, "%s/%s", cwd, d2->d_name); if (lstat(fname1, &st1) < 0) log("Couldn't stat %s", fname1); if (lstat(fname2, &st2) < 0) log("Couldn't stat %s", fname2); if (st1.st_ctime > st2.st_ctime) return -1; /* if timestamp is bigger, file is newer */ if (st1.st_ctime < st2.st_ctime) return 1; /* if timestamp is smaller, file is older */ return 0; } int run_trigger(char *command, char *filename, size_t fsize, char *result, size_t size) { int pid, status; int p[2]; size_t len; if (!command[0]) { if (result) memcpy(result, filename, min(fsize, size)); return 0; } logv("Running trigger '%s' on file '%s'", command, filename); if (pipe(p) < 0) { log("trigger pipe() failed: %s", strerror(errno)); return -1; } pid = fork(); if (pid < 0) { log("trigger fork() failed: %s", strerror(errno)); close(p[0]); close(p[1]); return -1; } if (pid == 0) { setpgid(pid, 0); close(p[0]); close(STDOUT_FILENO); if (dup2(p[1], STDOUT_FILENO) < 0) { log("trigger dup2() failed: %s", strerror(errno)); _exit(1); } execlp(command, command, filename, NULL); log("trigger exec() failed: %s", strerror(errno)); _exit(1); } close(p[1]); if (waitpid(pid, &status, 0) < 0) { log("trigger waitpid() failed: %s", strerror(errno)); close(p[0]); return -1; } if (!WIFEXITED(status)) { log("trigger child exited abnormally: %x", status); close(p[0]); return -1; } if (WEXITSTATUS(status)) { log("trigger child returned non 0: %d", WEXITSTATUS(status)); close(p[0]); return -1; } if (result) { if ((len = read(p[0], result, size)) < 0) { log("trigger read() failed: %s", strerror(errno)); close(p[0]); return -1; } while (len > 0 && (result[len - 1] == '\r' || result[len - 1] == '\n')) len--; result[len < size ? len : len - 1] = '\0'; } close(p[0]); return 0; } static int do_recv(int sock, char *buffer, int size, int timeout_seconds) { int len; static int callno = 0; callno++; // For tracking each call when debugging logv("do_recv %d: Reading from socket %d", callno, sock); if (is_socket_timed_out) { logv("do_recv %d: Already timed out.", callno); return -1; } /* We support timeout handling. This is needed because OGG playback on a DP-600 will leave connections open (one per song played in sequence). 2006-11-18/VT: The cause may be something else but this sort of fixes it. 2007-01-06/VT: This approach doesn't actually FIX it: A KiSS DP-600 will choke (die: no audio, video output shut off) when too many (10 to 20) connections have been forced closed by us. */ if (timeout_seconds > 0) { fd_set readset; int result; struct timeval tv; // Initialize the set FD_ZERO(&readset); FD_SET(sock, &readset); // Initialize timeout struct tv.tv_sec = timeout_seconds; tv.tv_usec = 0; // Wait until we have data or a timeout if ((result = select(sock+1, &readset, NULL, NULL, &tv)) < 0) { log("do_recv: select failed. %s", strerror(errno)); return -1; } else if (result == 0) { logv("do_recv %d: Timed out waiting for data from player.", callno); is_socket_timed_out = 1; return -1; } } if ((len = recv(sock, buffer, size, 0)) < 0) { log("recv: %s", strerror(errno)); return len; } if (len == 0) { logv("do_recv %d: EOF on socket %d", callno, sock); return -1; } logv("do_recv %d: Read %d bytes from socket %d", callno, len, sock); while (len > 0 && (buffer[len - 1] == '\r' || buffer[len - 1] == '\n')) len--; /* properly end the string and prevent buffer overflows */ buffer[len < size ? len : len - 1] = '\0'; logv("<-- [%s]", buffer); return len; } int do_send(int sock, char *buffer, int size) { int len; logv("--> [%s]", buffer); if ((len = send(sock, buffer, size, 0)) < 0) log("send: %s", strerror(errno)); return len; } /// Open a pseudo-folder as a DVD static dvd_reader_t* open_DVD_cached(char *foldername) { char realFullName[PATH_MAX]; // Will store the real foldername for a DVD folder, or the real filename for an ISO file. This is the name that we will send to libdvdread. int len; // We expect, and can only handle, a request for a path/filename in one of the following formats: // [/path]/ // [/path]/ char basepath[PATH_MAX] = {'\0'}, pseudoFolderType[16] = {'\0'}, real_filename[PATH_MAX] = {'\0'}; const char *formatString = "%[^"C_PSEUDOFOLDER_PREFIX_FIRSTCHAR"]"C_ANY_PSEUDOFOLDER_PREFIX"%[^"C_PSEUDOFOLDER_PREFIX_LASTCHAR"]"C_PSEUDOFOLDER_PREFIX_LASTCHAR"%[^|]"; int assignedCount = sscanf(foldername, formatString, &basepath, &pseudoFolderType, &real_filename); if (assignedCount != 3) { log("invalid request for pseudo-folder. Assigned %d, expected 3. Path/name must be like \"%s\", but was: \"%s\"", assignedCount, formatString, foldername); return NULL; }; if (strcasecmp(pseudoFolderType, C_ISO_PSEUDOFOLDER_TYPE) && strcasecmp(pseudoFolderType, C_DIR_PSEUDOFOLDER_TYPE)) { log("invalid request for pseudo-folder, folder type must be "C_ISO_PSEUDOFOLDER_TYPE" or "C_DIR_PSEUDOFOLDER_TYPE", but was: \"%s\"", pseudoFolderType); return NULL; }; // Construct the real fully qualified filename if ((len = snprintf(realFullName, sizeof(realFullName), "%s%s", basepath, real_filename)) < sizeof(realFullName)) { // Open the DVD image / folder dvd_reader_t* dvd = CachedDvdOpen(realFullName, 1); if (!dvd) log("open_DVD_cached: CachedDvdOpen failed for %s, title %d", realFullName, 1); return dvd; // Normal return } else log("Buffer too small: snprintf returned %d, expected < %d for open_DVD_cached(\"%s\")", len, sizeof(realFullName), foldername); return NULL; // Error return } /// Open a pseudo-file (a DVD title) as a DVD media file static dvd_reader_t* open_DVD_title_cached(char *foldername) { char realFullName[PATH_MAX]; // Will store the real foldername for a DVD folder, or the real filename for an ISO file. This is the name that we will send to libdvdread. int len; // We expect, and can only handle, a request for a path/filename in one of the following formats: // [/path]//Title no. nn of nn // [/path]//Title no. nn of nn char basepath[PATH_MAX], pseudoFolderType[16], real_filename[PATH_MAX]; int titleNo, titleCount; const char *formatString = "%[^"C_PSEUDOFOLDER_PREFIX_FIRSTCHAR"]"C_ANY_PSEUDOFOLDER_PREFIX"%[^"C_PSEUDOFOLDER_PREFIX_LASTCHAR"]"C_PSEUDOFOLDER_PREFIX_LASTCHAR"%[^/]/"C_PSEUDODVD_TITLE_PART1"%d"C_PSEUDODVD_TITLE_PART2"%d"C_ISO_PSEUDOFILE_SUFFIX; int assignedCount = sscanf(foldername, formatString, &basepath, &pseudoFolderType, &real_filename, &titleNo, &titleCount); if (assignedCount != 5) { log("invalid request for pseudo-file title. Assigned %d, expected 5. Path/name must be like \"%s\", but was: \"%s\"", assignedCount, formatString, foldername); return NULL; }; if (strcasecmp(pseudoFolderType, C_ISO_PSEUDOFOLDER_TYPE) && strcasecmp(pseudoFolderType, C_DIR_PSEUDOFOLDER_TYPE)) { log("invalid request for pseudo-file title. Folder type must be "C_ISO_PSEUDOFOLDER_TYPE" or "C_DIR_PSEUDOFOLDER_TYPE", but was: \"%s\"", pseudoFolderType); return NULL; }; // Construct the real fully qualified filename if ((len = snprintf(realFullName, sizeof(realFullName), "%s%s", basepath, real_filename)) < sizeof(realFullName)) { // Open the DVD image / folder dvd_reader_t* dvd = CachedDvdOpen(realFullName, titleNo); if (!dvd) log("open_DVD_title_cached: CachedDvdOpen failed for %s, title %d", realFullName, titleNo); return dvd; // Normal return } else log("Buffer too small: snprintf returned %d, expected < %d for open_DVD_title_cached(\"%s\")", len, sizeof(realFullName), foldername); return NULL; // Error return } /// Send a list of all titles in the DVD or playlist that a pseudo-folder represents static void list_pseudofolder(int sock, char *base, char *path) { dvd_reader_t *dvd; char fullpath[PATH_MAX], line[2*PATH_MAX]; int pseudoFolderPrefixLen = strlen(C_ANY_PSEUDOFOLDER_PREFIX); char *pFileName = strrchr(path, '/'), *pConverted; pFileName = pFileName? pFileName + 1 : path; if (!strncmp(pFileName, C_ANY_PSEUDOFOLDER_PREFIX, pseudoFolderPrefixLen)) { int len; if ((len = snprintf(fullpath, PATH_MAX, "%s/%s", base, path)) < PATH_MAX) { clean_pathname(fullpath); if (verify_path(fullpath) < 0) { log("access denied: %s", fullpath); goto done; } if (!strncmp(pFileName + pseudoFolderPrefixLen, C_ISO_PSEUDOFOLDER_TYPE, C_PSEUDOFOLDER_TYPE_LEN) || !strncmp(pFileName + pseudoFolderPrefixLen, C_DIR_PSEUDOFOLDER_TYPE, C_PSEUDOFOLDER_TYPE_LEN)) { // *** List a DVD folder *** if ((dvd = open_DVD_cached(fullpath))) { int i; int titleCount = CachedDvdGetTitleCount(); for (i = 0; i < titleCount; i++) { // Send this title as a filename to the player char titleName[PATH_MAX], fpath[PATH_MAX]; snprintf(titleName, sizeof titleName, "%s%02d%s%02d%s", C_PSEUDODVD_TITLE_PART1, i+1, C_PSEUDODVD_TITLE_PART2, titleCount, C_ISO_PSEUDOFILE_SUFFIX); if ((len = snprintf(fpath, sizeof fpath, "%s/%s", fullpath, titleName)) < sizeof fpath) { if ((len = snprintf(line, sizeof line, "%s|%s|0|", titleName, fpath)) < sizeof line - 1) { pConverted = (char*)convert_charset(line, cdt_to_client); strcat(pConverted, "\n"); do_send(sock, pConverted, strlen(pConverted)); } else log("Buffer too small: snprintf to fpath in list_pseudofolder() returned %d, expected < %d for titlename \"%s\"", len, sizeof line, titleName); } else log("Buffer too small: snprintf in list_pseudofolder() returned %d, expected < %d for titlename \"%s\"", len, sizeof fpath, titleName); } } } else if (!strncmp(pFileName + pseudoFolderPrefixLen, C_M3U_PSEUDOFOLDER_TYPE, C_PSEUDOFOLDER_TYPE_LEN) || !strncmp(pFileName + pseudoFolderPrefixLen, C_PLS_PSEUDOFOLDER_TYPE, C_PSEUDOFOLDER_TYPE_LEN)) { // *** List a PLAYLIST folder *** char realPlaylistPath[PATH_MAX]; strcpy(realPlaylistPath, fullpath); char *pLastSlash = strrchr(realPlaylistPath, '/'); char *pRealPlaylistFileName = pLastSlash + 1 + strlen(C_M3U_PSEUDOFOLDER_PREFIX); memmove(pLastSlash + 1, pRealPlaylistFileName, strlen(pRealPlaylistFileName) + 1); handle_playlist(sock, realPlaylistPath); } else log("Internal error: list_pseudofolder() inexplicably wrecked pseudofolder filename: %s", pFileName); } else log("Buffer too small: snprintf in list_pseudofolder() returned %d, expected < %d for path \"%s\"", len, PATH_MAX, path); } else log("Internal error: list_pseudofolder() was called with non-pseudofolder argument: %s", path); done: do_send(sock, "EOL\n", 4); } static void list_folder(int sock, char *base, char *path, enum mediastore_t mediastore) { char fullpath[PATH_MAX], line[2*PATH_MAX], *thisname, pseudoname[PATH_MAX], *pConverted; int i, n, to_send, len; struct dirent **namelist; char fpath[PATH_MAX]; struct stat statbuf; const char *validExtensions = mediastore == mst_audio? config.audiofileextensions : mediastore == mst_video? config.videofileextensions : mediastore == mst_picture? config.picturefileextensions : NULL; const char *recentlyUsedFolderName = "[Recently used]"; int (*sortmethod)(const void *,const void *) = my_filelist_compare; // Check whether this is a pseudo-path char *pFileName = strrchr(path, '/'); pFileName = pFileName? pFileName + 1 : path; if (!strncmp(pFileName, C_ANY_PSEUDOFOLDER_PREFIX, strlen(C_ANY_PSEUDOFOLDER_PREFIX))) { list_pseudofolder(sock, base, path); return; } if (config.max_recent_files && strstr(path, recentlyUsedFolderName)) { if (get_recent_path(fullpath, base)) goto done; sortmethod = timesort; len = strlen(fullpath); } else if ((len = snprintf(fullpath, sizeof fullpath, "%s/%s", base, path)) >= sizeof fullpath) { log("Buffer too small: snprintf returned %d, expected < %d for path \"%s\"", len, sizeof fullpath, path); goto done; } clean_pathname(fullpath); if (verify_path(fullpath) < 0) { log("access denied: %s", fullpath); goto done; } chdir(fullpath); /* scandir does not provide path info, so set it */ if ((n = scandir(fullpath, &namelist, 0, sortmethod)) < 0) { log("scandir %s: %s", fullpath, strerror(errno)); goto done; } /* * do this only in root and only if no -a option set or the .recent directory is not inside the media directory * to avoid duplicate entry */ if (config.max_recent_files && !*path && (!options.list_hidden_entries || config.persistentstoragepath)) { if ((to_send = snprintf(line, PATH_MAX, "%s|%s|1|\n", recentlyUsedFolderName, recentlyUsedFolderName)) < PATH_MAX) do_send(sock, line, to_send); else log("Buffer too small: snprintf returned %d, expected < %d for recent list name \"%s\"", to_send, PATH_MAX, recentlyUsedFolderName); } // First, send directory names (including pseudo-folders) for (i = 0; i < n; i++) { int list_this_entry = -1; thisname = namelist[i]->d_name; if (!options.list_hidden_entries) { // Don't list hidden directories unless specified by option if (*thisname == '.') list_this_entry = 0; } else { // Never list the "this level" and "previous level" pseudo directory entries if (!strcmp(thisname, ".") || !strcmp(thisname, "..")) list_this_entry = 0; } if (list_this_entry) { if ((len = snprintf(fpath, sizeof fpath, "%s/%s", fullpath, thisname)) < sizeof fpath) { clean_pathname(fpath); // Get the filetype so we can test whether this is a directory mode_t filetype = 0; if (stat(fpath, &statbuf) == 0) filetype = statbuf.st_mode & S_IFMT; // Set up a pointer to the filename extension, if there is one char *ext = strrchr(thisname, '.'); if (ext) ext++; // *** Substitute a pseudo-folder for each audio playlist file *** // We add our special prefix so we will recognize the folder when the player asks for it later int isPlayablePlaylistFile = (filetype != S_IFDIR && mediastore == mst_audio && ext && (!strcasecmp(ext, "m3u") || !strcasecmp(ext, "pls"))); if (isPlayablePlaylistFile) { const char *prefix = !strcasecmp(ext, "M3U")? C_M3U_PSEUDOFOLDER_PREFIX : C_PLS_PSEUDOFOLDER_PREFIX; int prefix_len = strlen(prefix); int thisname_len = strlen(thisname); if (prefix_len + thisname_len + strlen(fullpath) + 6 <= PATH_MAX) { memmove(pseudoname + prefix_len, thisname, thisname_len + 1); memmove(pseudoname, prefix, prefix_len); thisname = pseudoname; } else { log("Path too long: cannot process playlist file \"%s\"", fpath); isPlayablePlaylistFile = 0; // We cannot process this playlist file: Path would be too long } } // *** Substitute a pseudo-folder for each playable ISO file *** // We add our special prefix so we will recognize the folder when the player asks for it later int isPlayableIsoFile = (filetype != S_IFDIR && mediastore == mst_video && ext && list_contains_extension(config.isofileextensions, thisname)); if (isPlayableIsoFile) { const char *prefix = C_ISO_PSEUDOFOLDER_PREFIX; int prefix_len = strlen(prefix); int thisname_len = strlen(thisname); if (prefix_len + thisname_len + strlen(fullpath) + 6 <= PATH_MAX) { memmove(pseudoname + prefix_len, thisname, thisname_len + 1); memmove(pseudoname, prefix, prefix_len); thisname = pseudoname; } else { log("Path too long: cannot process ISO file \"%s\"", fpath); isPlayableIsoFile = 0; // We cannot process this ISO file: Path would be too long } } // *** Substitute a pseudo-folder for any directory that is a DVD root *** // We add our special prefix so we will recognize the folder when the player asks for it later if (filetype == S_IFDIR && mediastore == mst_video) { char statpath[PATH_MAX], statdir[PATH_MAX]; if ((len = snprintf(statdir, sizeof statdir, "%s/VIDEO_TS", fpath)) < sizeof statdir) { if (casestat(statdir, &statbuf) == 0 && S_ISDIR(statbuf.st_mode)) { if ((len = snprintf(statpath, sizeof statpath, "%s/VIDEO_TS.IFO", statdir)) < sizeof statpath) { if (casestat(statpath, &statbuf) == 0 && !S_ISDIR(statbuf.st_mode)) { if ((len = snprintf(statpath, sizeof statpath, "%s/VIDEO_TS.BUP", statdir)) < sizeof statpath) { if (casestat(statpath, &statbuf) == 0 && !S_ISDIR(statbuf.st_mode)) { // That's good enough. We assume that this is a DVD root folder const char *prefix = C_DIR_PSEUDOFOLDER_PREFIX; int prefix_len = strlen(prefix); int thisname_len = strlen(thisname); if (prefix_len + thisname_len + strlen(fullpath) + 6 <= PATH_MAX) { memmove(pseudoname + prefix_len, thisname, thisname_len + 1); memmove(pseudoname, prefix, prefix_len); thisname = pseudoname; } } } else log("Buffer too small: snprintf returned %d, expected < %d for statpath 2 of path \"%s\"", len, sizeof statpath, statdir); } } else log("Buffer too small: snprintf returned %d, expected < %d for statpath 1 of path \"%s\"", len, sizeof statpath, statdir); } } else log("Buffer too small: snprintf returned %d, expected < %d for statdir of path \"%s\"", len, sizeof statdir, fpath); } if (filetype == S_IFDIR || isPlayableIsoFile || isPlayablePlaylistFile) { // Send this directory name to the player if ((to_send = snprintf(line, sizeof line, "%s|%s|1|", thisname, thisname)) < sizeof line - 1) { pConverted = (char*)convert_charset(line, cdt_to_client); strcat(pConverted, "\n"); do_send(sock, pConverted, strlen(pConverted)); } else log("Buffer too small: snprintf returned %d, expected < %d for sending thisname \"%s\"", to_send, sizeof line, thisname); // Mark this entry as processed (the filename loop below will skip these) *(namelist[i]->d_name) = '\0'; } } else log("Buffer too small: snprintf returned %d, expected < %d for thisname \"%s\"", len, sizeof fpath, thisname); } } // Next, send the file names for (i = 0; i < n; i++) { int list_this_entry = -1; thisname = namelist[i]->d_name; // Don't list hidden files unless specified by option if (!options.list_hidden_entries && *thisname == '.') list_this_entry = 0; // Don't process the directories; they were processed in the loop above if (*thisname == '\0') list_this_entry = 0; // Only list files with extensions that match the configuration for this media store (no config means list all files) if (list_this_entry && validExtensions && *validExtensions) if (!list_contains_extension(validExtensions, thisname)) list_this_entry = 0; if (list_this_entry) { // Send this file name to the player if ((len = snprintf(fpath, sizeof fpath, "%s/%s", fullpath, thisname)) < sizeof fpath) { clean_pathname(fpath); if ((to_send = snprintf(line, sizeof line, "%s|%s|0|", thisname, fpath)) < sizeof line - 1) { pConverted = (char*)convert_charset(line, cdt_to_client); strcat(pConverted, "\n"); do_send(sock, pConverted, strlen(pConverted)); } else log("Buffer too small: snprintf returned %d, expected < %d for sending file \"%s\" (2)", to_send, sizeof line, thisname); } else log("Buffer too small: snprintf returned %d, expected < %d for sending file \"%s\" (1)", len, sizeof fpath, thisname); } free(namelist[i]); } free(namelist); // Show a pseudo-jpeg to be selected to toggle hidden content visible/invisible. // Do this only in Picture root and only if enablehiddenfilestext is configured if (*config.enablehiddenfilestext && !*path && mediastore == mst_picture) { if ((to_send = snprintf(line, PATH_MAX, "%s|%s/%s.jpg|0|\n", config.enablehiddenfilestext, config.picturepath, config.enablehiddenfilestext)) < PATH_MAX) do_send(sock, line, to_send); else log("Buffer too small: snprintf returned %d, expected < %d for enablehiddenfilestext \"%s\"", to_send, PATH_MAX, config.enablehiddenfilestext); } done: do_send(sock, "EOL\n", 4); } static void handle_list(int sock, char *request, size_t reqsize) { char *p1, *p2; p1 = index(request, '|'); p2 = rindex(request, '|'); if (p1 == NULL || p2 == NULL) { log("invalid LIST command \"%s\"", request); return; } *p2 = '\0'; char path[PATH_MAX]; memset(path, 0, sizeof path); strncpy(path, (char *) convert_charset(p1 + 1, cdt_from_client), sizeof path); if (strstr(request, "AUDIO") == request + 5) list_folder(sock, config.audiopath, path, mst_audio); else if (strstr(request, "VIDEO") == request + 5) list_folder(sock, config.videopath, path, mst_video); else if (strstr(request, "PICTURE") == request + 5) list_folder(sock, config.picturepath, path, mst_picture); else log("unknown LIST command \"%s\"", request); } /// Send the size of the DVD title that a pseudo-file represents static void handle_size_pseudofile(int sock, char *request, size_t reqsize) { char response[20]; char *p1, *filename; dvd_reader_t *dvd; p1 = index(request, '|'); if (p1 == NULL) { log("invalid SIZE command for pseudo-file: \"%s\"", request); return; } *p1 = '\0'; filename = (char *) convert_charset(request + 5, cdt_from_client); if (verify_path(filename) < 0) { log("access denied for pseudo-file: %s", filename); return; } // Find the size of the entire DVD movie if (!(dvd = open_DVD_title_cached(filename))) return; u_int64_t size = CachedDvdGetSize(); snprintf(response, sizeof(response), "%015lld", (long long)size); do_send(sock, response, 15); } /// Send the requested chunk of media content from the DVD title that a pseudo-file represents static void handle_get_pseudofile(int sock, char *request, size_t reqsize, dvd_reader_t *dvd) { char *filename; char *p1, *p2; long long o; long c; off_t offset; size_t chunk; ssize_t sent; p1 = index(request, '/'); p2 = index(request, '|'); if (p1 == NULL || p2 == NULL) { log("invalid GET command for pseudo-file: \"%s\"", request); return; } *p2 = '\0'; filename = (char *) convert_charset(p1, cdt_from_client); if (sscanf(p2 + 1, "%lld %ld", &o, &c) != 2) { log("invalid GET parameters for pseudo-file: \"%s\"", p2 + 1); return; }; offset = o; chunk = c; if (!dvd) { if (verify_path(filename) < 0) { log("access denied for pseudo-file: %s", filename); return; } if (!(dvd = open_DVD_title_cached(filename))) return; } if (config.max_recent_files) store_recent(filename); if ((sent = CachedDvdSendChunk(sock, offset, chunk)) < 0) { log0("handle_get_pseudofile: CachedDvdSendChunk failed"); return; } /* The client asked for more data than we have, send padding bytes */ while (sent++ < chunk) write(sock, "\0", 1); } /// Send 200 if the requested DVD title can be opened, send 404 otherwise. /// Then enter a tight loop that will serve the DVD contents to the player. static void handle_action1_pseudofile(int sock, char *request, size_t reqsize) { char *p1, *p2; dvd_reader_t *dvd; int len; p1 = strchr(request, '/'); p2 = strrchr(request, '|'); if (p1 == NULL || p2 == NULL) { log("invalid ACTION command for pseudo-file: \"%s\"", request); return; } *p2 = '\0'; char *path = (char *) convert_charset(p1, cdt_from_client); if (!(dvd = open_DVD_title_cached(path))) { do_send(sock, "404", 3); return; } if (do_send(sock, "200", 3) < 0) { CachedDvdClose(); return; } while (1) { static char newreq[512]; if ((len = do_recv(sock, newreq, sizeof(newreq), config.networktimeoutinterval)) < 0) { CachedDvdClose(); return; } transform_request_for_recent_file(newreq); if (strstr(newreq, "SIZE") == newreq) handle_size_pseudofile(sock, newreq, 512); else if (strstr(newreq, "GET") == newreq) handle_get_pseudofile(sock, newreq, 512, dvd); else log("unknown ACTION command for pseudo-file: \"%s\"", newreq); } } static void handle_size(int sock, char *request, size_t reqsize) { struct stat st; char response[20]; char *p1; char tfilename[PATH_MAX]; long long filesize; p1 = index(request, '|'); if (p1 == NULL) { log("invalid SIZE command \"%s\"", request); return; } *p1 = '\0'; char *path = (char *) convert_charset(request + 5, cdt_from_client); int isSubtitleFile = list_contains_extension(subtitle_file_extensions, path); if (isSubtitleFile) transform_subtitle_filename(path); int isActionPseuodFile = create_commandpseudofile_if_needed(path); if (isActionPseuodFile && commandpseudofile_data) { filesize = (long long)commandpseudofile_size; snprintf(response, sizeof(response), "%015lld", filesize); do_send(sock, response, 15); return; } if (verify_path(path) < 0) { log("access denied: %s", path); return; } if (run_trigger(config.pretrigger, path, reqsize - 5, tfilename, sizeof(tfilename)) < 0) { log("pretrigger %s failed", tfilename); return; } if (stat(tfilename, &st) < 0) { log("stat %s: %s", tfilename, strerror(errno)); return; } if (will_resize_jpeg_file(tfilename)) { if (config.picturecachesize && config.picturecachesize != 1) piccache_wait_if_precaching_this(tfilename); filesize = (long long)CachedSizeResizedJPEG(tfilename); } else filesize = (long long)st.st_size; snprintf(response, sizeof(response), "%015lld", filesize); do_send(sock, response, 15); run_trigger(config.posttrigger, tfilename, sizeof(tfilename), NULL, 0); } static void handle_get(int sock, char *request, size_t reqsize, int fd) { char *filename; char *p1, *p2; off_t offset, requested_offset; size_t chunk; int lfd = -1; long long o; long c; char tfilename[PATH_MAX]; #if defined(USE_INTERNAL_SENDFILE) || defined(Linux) ssize_t sent; #else /* FreeBSD */ soff_t sent; #endif p1 = index(request, '/'); p2 = index(request, '|'); if (p1 == NULL || p2 == NULL) { log("invalid GET command \"%s\"", request); return; } // Special handling of pseudo files for playing ISO images / VIDEO_TS folders and audio playlists char *p3 = strrchr(request, '/'); if (p3) { *p3 = '\0'; char *p4 = strrchr(request, '/'); if (p4 && strncmp(p4+1, C_ANY_PSEUDOFOLDER_PREFIX, strlen(C_ANY_PSEUDOFOLDER_PREFIX)) == 0) { *p3 = '/'; // We temporarily had a '\0' here if (fd == -1) { handle_get_pseudofile(sock, request, reqsize, NULL); } else { log("Unexpected handle_get with fd=%d for request %s", fd, request); } return; } *p3 = '/'; // We temporarily had a '\0' here } // Normal (real file) sending follows *p2 = '\0'; filename = (char *) convert_charset(p1, cdt_from_client); if (sscanf(p2 + 1, "%lld %ld", &o, &c) != 2) { log("invalid GET parameters \"%s\"", p2 + 1); return; }; offset = o; chunk = c; requested_offset = offset; int willResizeJPEGFile = will_resize_jpeg_file(filename); if (fd == -1) { int isSubtitleFile = list_contains_extension(subtitle_file_extensions, filename); if (isSubtitleFile) transform_subtitle_filename(filename); int isActionPseuodFile = create_commandpseudofile_if_needed(filename); if (isActionPseuodFile && commandpseudofile_data) { sent = send(sock, commandpseudofile_data + offset, chunk, 0); return; } if (verify_path(filename) < 0) { log("access denied: %s", filename); return; } if (run_trigger(config.pretrigger, filename, reqsize - (p1 - request), tfilename, sizeof(tfilename)) < 0) { log("pretrigger %s failed", filename); return; } filename = tfilename; if (!willResizeJPEGFile) { if ((lfd = open(filename, O_RDONLY)) < 0) { if (!isSubtitleFile) log("open %s: %s", filename, strerror(errno)); return; } } fd = lfd; if (config.max_recent_files && !isSubtitleFile && !isActionPseuodFile) store_recent(filename); } if (willResizeJPEGFile) { if (offset == 0 && config.picturecachesize && config.picturecachesize != 1) piccache_wait_if_precaching_this(filename); if ((sent = CachedSendResizedJPEG(sock, filename, offset, chunk)) < 0) { log("CachedSendResizedJPEG: %s", strerror(errno)); run_trigger(config.posttrigger, filename, reqsize - (p1 - request), NULL, 0); return; } } else { if (chunk == 0) { struct stat st; if (stat(filename, &st) < 0) { log("stat %s: %s", filename, strerror(errno)); if (lfd != -1) { run_trigger(config.posttrigger, filename, reqsize - (p1 - request), NULL, 0); close(lfd); } return; } chunk = st.st_size; } #ifdef USE_INTERNAL_SENDFILE if ((sent = my_internal_sendfile(sock, fd, &offset, chunk)) < 0) { #else #ifdef Linux if ((sent = sendfile(sock, fd, &offset, chunk)) < 0) { #else /* FreeBSD */ if (sendfile(fd, sock, offset, chunk, NULL, &sent, 0) < 0) { #endif #endif log("sendfile: %s", strerror(errno)); if (lfd != -1) { run_trigger(config.posttrigger, filename, reqsize - (p1 - request), NULL, 0); close(lfd); } return; } } logv("--> [%d bytes at offset %ld]", sent, requested_offset); /* The client asked for more data than we have, send padding bytes */ if (sent < chunk) logv("--> [%d padding null bytes]", chunk - sent); while (sent++ < chunk) write(sock, "\0", 1); if (lfd != -1 || willResizeJPEGFile) { run_trigger(config.posttrigger, filename, reqsize - (p1 - request), NULL, 0); if (lfd != -1) close(lfd); } } static void handle_action1(int sock, char *request, size_t reqsize) { char *p1, *p2; int fd = -1; int len; char tfilename[PATH_MAX]; int willResizeJPEGFile = 0; p1 = index(request, '/'); p2 = rindex(request, '|'); if (p1 == NULL || p2 == NULL) { log("invalid ACTION command \"%s\"", request); return; } // Special handling of pseudo files for playing ISO images and VIDEO_TS folders char *p3 = strrchr(request, '/'); if (p3) { *p3 = '\0'; char *p4 = strrchr(request, '/'); if (p4 && strncmp(p4+1, C_ANY_PSEUDOFOLDER_PREFIX, strlen(C_ANY_PSEUDOFOLDER_PREFIX)) == 0) { *p3 = '/'; // We temporarily had a '\0' here handle_action1_pseudofile(sock, request, reqsize); return; } *p3 = '/'; // We temporarily had a '\0' here } // Normal (real file) ACTION handling follows *p2 = '\0'; char *path = (char *) convert_charset(p1, cdt_from_client); int isSubtitleFile = list_contains_extension(subtitle_file_extensions, path); if (isSubtitleFile) transform_subtitle_filename(path); int isActionPseuodFile = create_commandpseudofile_if_needed(path); if (!isActionPseuodFile) { if (verify_path(path) < 0) { log("access denied: %s", path); do_send(sock, "404", 3); return; } if (run_trigger(config.pretrigger, path, reqsize - (p1 - request), tfilename, sizeof(tfilename)) < 0) { log("pretrigger %s failed", path); do_send(sock, "404", 3); return; } path = tfilename; willResizeJPEGFile = will_resize_jpeg_file(path); if (!willResizeJPEGFile) { if ((fd = open(path, O_RDONLY)) < 0) { if (!isSubtitleFile) log("open %s: %s", path, strerror(errno)); do_send(sock, "404", 3); run_trigger(config.posttrigger, path, reqsize - (p1 - request), NULL, 0); return; } } } if (do_send(sock, "200", 3) < 0) { if (fd != -1) close(fd); if (!isActionPseuodFile) run_trigger(config.posttrigger, path, reqsize - (p1 - request), NULL, 0); return; } if (config.max_recent_files && !isSubtitleFile && !isActionPseuodFile) store_recent(path); while (1) { static char newreq[512]; char prevcmd = *newreq; if ((len = do_recv(sock, newreq, sizeof(newreq), config.networktimeoutinterval)) < 0) { if (fd != -1) close(fd); if (!isActionPseuodFile) run_trigger(config.posttrigger, path, reqsize - (p1 - request), NULL, 0); // After last GET of current picture, try caching next one if (prevcmd == 'G' && config.picturecachesize && config.picturecachesize != 1 && willResizeJPEGFile) precache_next_picture(path); // After last GET of Command Pseudofile, perform the command and release confirmation jpeg memory if (prevcmd == 'G' && isActionPseuodFile) { if (pseudofile_command != pfc_none) perform_pseudofile_command(); if (commandpseudofile_data) { free_gd_mem(commandpseudofile_data); commandpseudofile_data = NULL; commandpseudofile_size = 0; } } return; } transform_request_for_recent_file(newreq); if (!strncmp(newreq, "SIZE", 4)) handle_size(sock, newreq, 512); else if (!strncmp(newreq, "GET", 3)) handle_get(sock, newreq, 512, fd); else log("unknown ACTION command \"%s\"", newreq); } } void handle_request(int sock) { int timeout = 0; // We know we have data waiting when called while (1) { static char request[512]; int len; if ((len = do_recv(sock, request, sizeof(request), timeout)) < 0) { CachedDvdClose(); return; } // // ############# DEBUGGING - REMOVE BELOW!!!!! ########### 2006-10-22/VT // static int isFirstRequest = 1; // if (isFirstRequest) { // char userinputbuf[255]; // fputs("New process started. Press Enter to continue (after attaching debugger).", stderr); // fgets(userinputbuf, sizeof userinputbuf, stdin); // isFirstRequest = 0; // } // // ############# DEBUGGING - REMOVE ABOVE!!!!! ########### 2006-10-22/VT transform_request_for_recent_file(request); if (strstr(request, "LIST") == request) handle_list(sock, request, 512); else if (strstr(request, "ACTION 1") == request) handle_action1(sock, request, 512); else if (strstr(request, "ACTION 2") == request) handle_action1(sock, request, 512); else if (strstr(request, "SIZE") == request) handle_size(sock, request, 512); else if (strstr(request, "GET") == request) handle_get(sock, request, 512, -1); else log("unknown KiSS command \"%s\"", request); timeout = config.networktimeoutinterval; } } void handle_kmlrequest(int sock) { char request[512]; int len; char *ok1 = "\n\n\n\n"; char *error = "\n501 Method Not Implemented\n\n

Method Not Implemented

\n\n"; if ((len = do_recv(sock, request, sizeof(request), 0)) < 0) return; if (strstr(request, "GET /index.kml") == request) { do_send(sock, ok1, strlen(ok1)); do_send(sock, config.kmlurl, strlen(config.kmlurl)); do_send(sock, ok2, strlen(ok2)); } else { do_send(sock, error, strlen(error)); log("unknown KiSS kml command \"%s\"", request); } } /* * Name: store_recent * * Description: Writes currently requested file to recently used * direcory list. */ static int store_recent(char *filename) { char recent[PATH_MAX]; char target[PATH_MAX]; char fpath[PATH_MAX]; char *thisname; int n, num_files, len, file_no; struct dirent **namelist; int isPseudoFile = strstr(filename, "/"C_DIR_PSEUDOFOLDER_PREFIX) || strstr(filename, "/"C_ISO_PSEUDOFOLDER_PREFIX); if (strstr(filename, "/.")) { return 0; // We don't store hidden files } if (get_recent_path(recent, filename)) { log ("store_recent: Could not produce recently used directory for filename \"%s\"", filename); return -1; } if (mkdir(recent, standard_dir_filemode) < 0) { if (errno != EEXIST) { log("store_recent: Couldn't create directory %s", recent); return -1; } } else if (chmod(recent, standard_dir_filemode) < 0) log("store_recent: cannot chmod directory %s: %s", recent, strerror(errno)); chdir(recent); /* scandir does not provide path info to sort */ num_files = scandir(recent, &namelist, 0, timesort); if (num_files < 0) log("store_recent: scandir(%s) failed with %s", recent, strerror(errno)); else { for (file_no = 1, n = 0; n < num_files; n++) { thisname = namelist[n]->d_name; /* skip parent and current dir */ if (!strcmp (thisname, ".") || !strcmp (thisname, "..")) { free(namelist[n]); continue; } /* delete excess files */ if (file_no >= config.max_recent_files) { logv("Delete oldest recent entry %s", thisname); snprintf(fpath, PATH_MAX, "%s/%s", recent, thisname); unlink(fpath); } free(namelist[n]); file_no++; } free(namelist); } if (!strncmp(filename, recent, strlen(recent))) if (isPseudoFile) { int fd, charsread; if ((fd = open(filename, O_RDONLY)) < 0) { log("store_recent: cannot open link file %s: %s", filename, strerror(errno)); return -1; } if ((charsread = read(fd, target, sizeof target)) < 0) log("store_recent: cannot read from link file %s: %s", filename, strerror(errno)); else *(target + charsread) = '\0'; close(fd); } else if ((n = readlink(filename, target, PATH_MAX)) < 0) { log("store_recent: cannot read symlink %s: %s", filename, strerror(errno)); return -1; } else *(target + n) = '\0'; else strncpy(target, filename, PATH_MAX); if (isPseudoFile) { *(strrchr(target, '/')) = '\0'; // Strip the title from a DVD entry strcat(target, strrchr(filename, '.')); // And add the ".mpg" extension instead } if ((len = snprintf(fpath, sizeof fpath, "%s/%s", recent, basename(target))) >= sizeof fpath) { log ("store_recent: Buffer too small for new recent filename \"%s\"", basename(target)); return -1; } /* make sure link is created anew */ unlink(fpath); if (isPseudoFile) { // Produce a link file, since a symlink cannot link to a pseudo-file int fd, written; if ((fd = open(fpath, O_CREAT | O_WRONLY | O_TRUNC, standard_filemode)) < 0) { log("store_recent: cannot create link file %s: %s", fpath, strerror(errno)); return -1; } if ((written = write(fd, filename, strlen(filename) + 1)) <= strlen(filename)) log("store_recent: cannot write to link file %s: %s", fpath, strerror(errno)); close(fd); } else if (symlink(target, fpath) < 0) { if (errno != EEXIST) { log("store_recent: cannot create symlink '%s' to '%s', error %s", fpath, target, strerror(errno)); return -1; } } return 0; } /// Simulate symlink behaviour for pseudo-files by replacing the filename in a request for /// a link file from the .recent directory with the filename that the link file links to. /// For picture files: replace filename in request with actual symlink target, so that the /// scaled picture file will be served from the cache if it has been cached. static int transform_request_for_recent_file(char *request) { char *p1, *p2, *p3; const char *pConverted; char rest[512]; p1 = strchr(request, '/'); p2 = strchr(request, '|'); if (p1 != NULL && p2 != NULL && p2 > p1) { if ((p3 = strstr(p1, "/.recent/"))) { if (!strncmp(p3 + 9, C_DIR_PSEUDOFOLDER_PREFIX, strlen(C_DIR_PSEUDOFOLDER_PREFIX)) || !strncmp(p3 + 9, C_ISO_PSEUDOFOLDER_PREFIX, strlen(C_ISO_PSEUDOFOLDER_PREFIX))) { int fd, charsread; strcpy(rest, p2); *p2 = '\0'; pConverted = convert_charset(p1, cdt_from_client); if ((fd = open(pConverted, O_RDONLY)) < 0) { log("transform_request_for_recent_file: cannot open link file %s: %s", pConverted, strerror(errno)); *p2 = '|'; return -1; } if ((charsread = read(fd, p1, 500 - (p1 - request))) < 0) log("transform_request_for_recent_file: cannot read from link file: %s", strerror(errno)); else { *(p1 + charsread) = '\0'; strcpy(p1, convert_charset(p1, cdt_to_client)); } close(fd); strcat(p1, rest); logv("transform_request_for_recent_file: Modified request = '%s'", request); } else if (!strncmp(p1, config.picturepath, strlen(config.picturepath)) || (*config.persistentstoragepath && !strncmp(p1, config.persistentstoragepath, strlen(config.persistentstoragepath)) && !strncmp(p1 + strlen(config.persistentstoragepath), "/picture/.recent/", 17))) { char target[PATH_MAX]; int n; strcpy(rest, p2); *p2 = '\0'; pConverted = convert_charset(p1, cdt_from_client); if ((n = readlink(pConverted, target, PATH_MAX)) > 0) { *(target + n) = '\0'; strcpy(p1, convert_charset(target, cdt_to_client)); strcat(p1, rest); logv("transform_request_for_recent_file: Modified request = '%s'", request); } else { *p2 = '|'; // Restore request to untouched state } } } } return 0; }