// wait for the S3 GET/PUT to complete int stream_sync(ObjectStream* os) { /// // TBD: this should be a per-repo config-option /// static const time_t timeout_sec = 5; void* retval; // fuse may call fuse-flush multiple times (one for every open stream). // but will not call flush after calling close(). if (! (os->flags & OSF_OPEN)) { LOG(LOG_ERR, "%s isn't open\n", os->url); errno = EINVAL; /* ?? */ return -1; } // See NOTE, above, regarding the difference between reads and writes. if (! pthread_tryjoin_np(os->op, &retval)) { LOG(LOG_INFO, "op-thread joined\n"); os->flags |= OSF_JOINED; } else { // If a stream_get/put timed-out waiting for their // writefunc/readfunc, then the locks are likely in an inconsistent // state. [Either (a) the readfunc never posted iob_empty for // stream__put(), or (b) the writefunc never posted iob_full for // stream_get().] In either case, the operation started by // stream_open() is declared a failure, and we shouldn't do any of // the normal cleanup stuff, like trying to write recovery-info, etc. // But we do need to the thread to stop before we return, because if // the writefunc/readfunc gets another callback, it will access parts // of the ObjectStream that are about to be deallocated. if (os->flags & OSF_TIMEOUT) { LOG(LOG_INFO, "cancelling timed-out thread\n"); int rc = pthread_cancel(os->op); if (rc) { LOG(LOG_ERR, "cancellation failed (%s), killing thread\n", strerror(errno)); pthread_kill(os->op, SIGKILL); LOG(LOG_INFO, "killed thread\n"); } LOG(LOG_INFO, "waiting for terminated op-thread\n"); if (stream_wait(os)) { LOG(LOG_ERR, "err joining op-thread ('%s')\n", strerror(errno)); return -1; } } // In this case, the get/put timed-out, but it did so inside SAFE_WAIT_KILL(), // rather than SAFE_WAIT(), so the thread has already been cancelled // and joined. else if (os->flags & OSF_TIMEOUT_K) { LOG(LOG_INFO, "timed-out thread already killed\n"); LOG(LOG_INFO, "op-thread returned %d\n", os->op_rc); errno = (os->op_rc ? EIO : 0); return os->op_rc; } #if (LIBCURL_VERSION_MAJOR > 7) || ((LIBCURL_VERSION_MAJOR == 7) && (LIBCURL_VERSION_MINOR >= 45)) // Our installed version of libcurl is 7.19.7. We are experimenting // with a custom-built libcurl based on 7.45.0. We notice that the // latter does not call streaming_readfunc() again, if stream_open() // provided a content-length (i.e. the request had a content-length // header, rather than being chunked-transfer-encoded), and the full // number of chars matching the content-length header has been sent. // In such a case, stream_sync() shouldn't use "stream_put(..0)" to // get the readfunc to quit, because the readfunc isn't running. // // NOTE: This behavior may actually be present in earlier versions of // libcurl, in which case, the VERSION_MINOR here should be // adjusted downwards, toward 19. else if ((os->flags & OSF_WRITING) && os->content_len && (os->content_len == os->written)) { LOG(LOG_INFO, "(wr) wrote content-len, no action needed (flags=0x%04x)\n", os->flags); } #endif // signal EOF to readfunc else if (os->flags & OSF_WRITING) { LOG(LOG_INFO, "(wr) sending empty buffer (flags=0x%04x)\n", os->flags); if (stream_put(os, NULL, 0)) { LOG(LOG_ERR, "stream_put(0) failed\n"); pthread_kill(os->op, SIGKILL); LOG(LOG_INFO, "killed thread\n"); } } // signal QUIT to writefunc else { LOG(LOG_INFO, "(rd) sending empty buffer (flags=0x%04x)\n", os->flags); if (stream_get(os, NULL, 0)) { LOG(LOG_ERR, "stream_get(0) failed\n"); pthread_kill(os->op, SIGKILL); LOG(LOG_INFO, "killed thread\n"); } } // check whether thread has returned. Could mean a curl // error, an S3 protocol error, or server flaking out. LOG(LOG_INFO, "waiting for op-thread\n"); if (stream_wait(os)) { LOG(LOG_ERR, "err joining op-thread ('%s')\n", strerror(errno)); return -1; } } // thread has completed os->flags |= OSF_JOINED; if (( os->flags & OSF_READING) && (os->op_rc == CURLE_WRITE_ERROR)) { // when we signalled writefunc to quit, it provoked this LOG(LOG_INFO, "op-thread returned CURLE_WRITE_ERROR as expected\n"); return 0; } else { LOG(LOG_INFO, "op-thread returned %d\n", os->op_rc); errno = (os->op_rc ? EIO : 0); return os->op_rc; } }
/* Starts the process whose arguments are given in the null-terminated array * 'argv' and waits for it to exit. On success returns 0 and stores the * process exit value (suitable for passing to process_status_msg()) in * '*status'. On failure, returns a positive errno value and stores 0 in * '*status'. * * If 'stdout_log' is nonnull, then the subprocess's output to stdout (up to a * limit of PROCESS_MAX_CAPTURE bytes) is captured in a memory buffer, which * when this function returns 0 is stored as a null-terminated string in * '*stdout_log'. The caller is responsible for freeing '*stdout_log' (by * passing it to free()). When this function returns an error, '*stdout_log' * is set to NULL. * * If 'stderr_log' is nonnull, then it is treated like 'stdout_log' except * that it captures the subprocess's output to stderr. */ int process_run_capture(char **argv, char **stdout_log, char **stderr_log, int *status) { struct stream s_stdout, s_stderr; sigset_t oldsigs; pid_t pid; int error; COVERAGE_INC(process_run_capture); if (stdout_log) { *stdout_log = NULL; } if (stderr_log) { *stderr_log = NULL; } *status = 0; error = process_prestart(argv); if (error) { return error; } error = stream_open(&s_stdout); if (error) { return error; } error = stream_open(&s_stderr); if (error) { stream_close(&s_stdout); return error; } block_sigchld(&oldsigs); pid = fork(); if (pid < 0) { int error = errno; unblock_sigchld(&oldsigs); VLOG_WARN("fork failed: %s", strerror(error)); stream_close(&s_stdout); stream_close(&s_stderr); *status = 0; return error; } else if (pid) { /* Running in parent process. */ struct process *p; p = process_register(argv[0], pid); unblock_sigchld(&oldsigs); close(s_stdout.fds[1]); close(s_stderr.fds[1]); while (!process_exited(p)) { stream_read(&s_stdout); stream_read(&s_stderr); stream_wait(&s_stdout); stream_wait(&s_stderr); process_wait(p); poll_block(); } stream_read(&s_stdout); stream_read(&s_stderr); if (stdout_log) { *stdout_log = ds_steal_cstr(&s_stdout.log); } if (stderr_log) { *stderr_log = ds_steal_cstr(&s_stderr.log); } stream_close(&s_stdout); stream_close(&s_stderr); *status = process_status(p); process_destroy(p); return 0; } else { /* Running in child process. */ int max_fds; int i; fatal_signal_fork(); unblock_sigchld(&oldsigs); dup2(get_null_fd(), 0); dup2(s_stdout.fds[1], 1); dup2(s_stderr.fds[1], 2); max_fds = get_max_fds(); for (i = 3; i < max_fds; i++) { close(i); } execvp(argv[0], argv); fprintf(stderr, "execvp(\"%s\") failed: %s\n", argv[0], strerror(errno)); exit(EXIT_FAILURE); } }
int stream_abort(ObjectStream* os) { /// // TBD: this should be a per-repo config-option /// static const time_t timeout_sec = 5; // fuse may call fuse-flush multiple times (one for every open stream). // but will not call flush after calling close(). if (! (os->flags & OSF_OPEN)) { LOG(LOG_ERR, "%s isn't open\n", os->url); errno = EINVAL; /* ?? */ return -1; } else if (! (os->flags & OSF_WRITING)) { LOG(LOG_ERR, "%s aborting a read-stream is not supported\n", os->url); errno = ENOSYS; return -1; } // See NOTE, above, regarding the difference between reads and writes. void* retval; if (! pthread_tryjoin_np(os->op, &retval)) { LOG(LOG_INFO, "op-thread joined\n"); os->flags |= OSF_JOINED; } else { if (os->flags & OSF_WRITING) { // signal ABORT to readfunc LOG(LOG_INFO, "(wr) sending (char*)1 (flags=0x%04x)\n", os->flags); os->flags |= OSF_ABORT; if (stream_put(os, (const char*)1, 1) != 1) { LOG(LOG_ERR, "stream_put((char*)1) failed\n"); pthread_kill(os->op, SIGKILL); LOG(LOG_INFO, "killed thread\n"); } } // check whether thread has returned. Could mean a curl // error, an S3 protocol error, or server flaking out. LOG(LOG_INFO, "waiting for op-thread\n"); if (stream_wait(os)) { LOG(LOG_ERR, "err joining op-thread\n"); return -1; } } // thread has completed os->flags |= OSF_JOINED; LOG(LOG_INFO, "op-thread returned %d\n", os->op_rc); if ((os->op_rc == CURLE_ABORTED_BY_CALLBACK) && (os->iob.read_pos == (char*)1)) { LOG(LOG_INFO, "op-thread return is as expected for ABORT\n"); return 0; } else { errno = (os->op_rc ? EINVAL : 0); return os->op_rc; } }
static int Open(vlc_object_t *obj) { demux_t *demux = (demux_t *)obj; demux_sys_t *sys = malloc(sizeof (*sys)); if (unlikely(sys == NULL)) return VLC_ENOMEM; sys->context = vlc_pa_connect(obj, &sys->mainloop); if (sys->context == NULL) { free(sys); return VLC_EGENERIC; } sys->stream = NULL; sys->es = NULL; sys->discontinuity = false; sys->caching = INT64_C(1000) * var_InheritInteger(obj, "live-caching"); demux->p_sys = sys; /* Stream parameters */ struct pa_sample_spec ss; ss.format = PA_SAMPLE_S16NE; ss.rate = 48000; ss.channels = 2; assert(pa_sample_spec_valid(&ss)); struct pa_channel_map map; map.channels = 2; map.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT; map.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT; assert(pa_channel_map_valid(&map)); const pa_stream_flags_t flags = PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_ADJUST_LATENCY | PA_STREAM_FIX_FORMAT | PA_STREAM_FIX_RATE /*| PA_STREAM_FIX_CHANNELS*/; const char *dev = NULL; if (demux->psz_location != NULL && demux->psz_location[0] != '\0') dev = demux->psz_location; struct pa_buffer_attr attr = { .maxlength = -1, .fragsize = pa_usec_to_bytes(sys->caching, &ss) / 2, }; es_format_t fmt; /* Create record stream */ pa_stream *s; pa_operation *op; pa_threaded_mainloop_lock(sys->mainloop); s = pa_stream_new(sys->context, "audio stream", &ss, &map); if (s == NULL) goto error; sys->stream = s; pa_stream_set_state_callback(s, stream_state_cb, sys->mainloop); pa_stream_set_read_callback(s, stream_read_cb, demux); pa_stream_set_buffer_attr_callback(s, stream_buffer_attr_cb, demux); pa_stream_set_moved_callback(s, stream_moved_cb, demux); pa_stream_set_overflow_callback(s, stream_overflow_cb, demux); pa_stream_set_started_callback(s, stream_started_cb, demux); pa_stream_set_suspended_callback(s, stream_suspended_cb, demux); pa_stream_set_underflow_callback(s, stream_underflow_cb, demux); if (pa_stream_connect_record(s, dev, &attr, flags) < 0 || stream_wait(s, sys->mainloop)) { vlc_pa_error(obj, "cannot connect record stream", sys->context); goto error; } /* The ES should be initialized before stream_read_cb(), but how? */ const struct pa_sample_spec *pss = pa_stream_get_sample_spec(s); if ((unsigned)pss->format >= sizeof (fourccs) / sizeof (fourccs[0])) { msg_Err(obj, "unknown PulseAudio sample format %u", (unsigned)pss->format); goto error; } vlc_fourcc_t format = fourccs[pss->format]; if (format == 0) { /* FIXME: should renegotiate something else */ msg_Err(obj, "unsupported PulseAudio sample format %u", (unsigned)pss->format); goto error; } es_format_Init(&fmt, AUDIO_ES, format); fmt.audio.i_physical_channels = fmt.audio.i_original_channels = AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT; fmt.audio.i_channels = ss.channels; fmt.audio.i_rate = pss->rate; fmt.audio.i_bitspersample = aout_BitsPerSample(format); fmt.audio.i_blockalign = fmt.audio.i_bitspersample * ss.channels / 8; fmt.i_bitrate = fmt.audio.i_bitspersample * ss.channels * pss->rate; sys->framesize = fmt.audio.i_blockalign; sys->es = es_out_Add (demux->out, &fmt); /* Update the buffer attributes according to actual format */ attr.fragsize = pa_usec_to_bytes(sys->caching, pss) / 2; op = pa_stream_set_buffer_attr(s, &attr, stream_success_cb, sys->mainloop); if (likely(op != NULL)) { while (pa_operation_get_state(op) == PA_OPERATION_RUNNING) pa_threaded_mainloop_wait(sys->mainloop); pa_operation_unref(op); } stream_buffer_attr_cb(s, demux); pa_threaded_mainloop_unlock(sys->mainloop); demux->pf_demux = NULL; demux->pf_control = Control; return VLC_SUCCESS; error: pa_threaded_mainloop_unlock(sys->mainloop); Close(obj); return VLC_EGENERIC; } static void Close (vlc_object_t *obj) { demux_t *demux = (demux_t *)obj; demux_sys_t *sys = demux->p_sys; pa_stream *s = sys->stream; if (likely(s != NULL)) { pa_threaded_mainloop_lock(sys->mainloop); pa_stream_disconnect(s); pa_stream_set_state_callback(s, NULL, NULL); pa_stream_set_read_callback(s, NULL, NULL); pa_stream_set_buffer_attr_callback(s, NULL, NULL); pa_stream_set_moved_callback(s, NULL, NULL); pa_stream_set_overflow_callback(s, NULL, NULL); pa_stream_set_started_callback(s, NULL, NULL); pa_stream_set_suspended_callback(s, NULL, NULL); pa_stream_set_underflow_callback(s, NULL, NULL); pa_stream_unref(s); pa_threaded_mainloop_unlock(sys->mainloop); } vlc_pa_disconnect(obj, sys->context, sys->mainloop); free(sys); }