mirror of
https://github.com/ZoneMinder/zoneminder.git
synced 2026-10-12 23:03:52 -04:00
* fix: free a stream's monitor and playback buffer when nph-zms exits Every nph-zms exited with ~1 MB still allocated, reported by LeakSanitizer on every stream: - The monitor's zones each hold a shared_ptr back to it, so releasing the stream's reference never freed the Monitor, its zones (a full-frame zone image each), camera or links. zmc breaks the cycle with disconnect() on exit; zms never did. ~StreamBase now calls it. - The buffered-playback SwapImage array (connkey + buffer) was never deleted. MonitorStream now frees it in its destructor. Freed at process exit either way, so nothing grows, but it shows up in every leak report and hides real ones. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: check a stream frees its monitor and zones on destruction refs #5186 A stream holds a monitor with one zone; once the stream is destroyed, a weak_ptr to the monitor must have expired. Each Zone holds a shared_ptr back to its Monitor, so without ~StreamBase breaking the cycle the monitor, its zones and their images outlive the stream. Fails on82901ac1f(REQUIRE( watched.expired() )), passes with the fix. Needs no database or sanitizer, so ci-cpp-tests runs it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor: hold the buffered-playback array in a unique_ptr refs #5186 temp_image_buffer is now a std::unique_ptr<SwapImage[]>, so it is freed with the stream without a hand-written destructor, and the leak cannot come back by dropping that line. The array is private and only allocated in runStream(), so a unit test cannot see it freed without a sanitizer; under LeakSanitizer, buffered nph-zms streams leaked 48,008 bytes from runStream() on82901ac1fand nothing with this branch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
512 lines
20 KiB
C++
512 lines
20 KiB
C++
//
|
|
// ZoneMinder Stream Class Implementation, $Date$, $Revision$
|
|
// Copyright (C) 2001-2008 Philip Coombes
|
|
//
|
|
// 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
|
|
// of the License, 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
//
|
|
|
|
#include "zm_stream.h"
|
|
|
|
#include "zm_box.h"
|
|
#include "zm_monitor.h"
|
|
#include "zm_signal.h"
|
|
|
|
#include <cmath>
|
|
#include <sys/file.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
|
|
constexpr Seconds StreamBase::MAX_STREAM_DELAY;
|
|
constexpr Milliseconds StreamBase::MAX_SLEEP;
|
|
|
|
StreamBase::~StreamBase() {
|
|
delete vid_stream;
|
|
delete[] temp_img_buffer;
|
|
closeComms();
|
|
// The monitor's zones each hold a shared_ptr back to it, so releasing ours
|
|
// alone never frees it. disconnect() clears the zones, as zmc does on exit.
|
|
if (monitor) monitor->disconnect();
|
|
}
|
|
|
|
bool StreamBase::loadMonitor(int p_monitor_id) {
|
|
monitor_id = p_monitor_id;
|
|
|
|
if ( !(monitor or (monitor = Monitor::Load(monitor_id, false, Monitor::QUERY))) ) {
|
|
Error("Unable to load monitor id %d for streaming", monitor_id);
|
|
return false;
|
|
}
|
|
|
|
if (monitor->Capturing() == Monitor::CAPTURING_NONE) {
|
|
Info("Monitor %d has capturing == NONE. Will not be able to connect to it.", monitor_id);
|
|
return false;
|
|
}
|
|
|
|
// Hold monitor_mutex during disconnect/connect to prevent the command
|
|
// processor thread from accessing shared memory while it is remapped.
|
|
std::lock_guard<std::mutex> lck(monitor_mutex);
|
|
|
|
if (monitor->isConnected()) {
|
|
monitor->disconnect();
|
|
}
|
|
|
|
if (!monitor->connect()) {
|
|
Info("Unable to connect to monitor id %d for streaming", monitor_id);
|
|
monitor->disconnect();
|
|
// If we couldn't connect, it might be due to size mismatch in shm. Need to reload
|
|
if ( !(monitor = Monitor::Load(monitor_id, false, Monitor::QUERY)))
|
|
Error("Unable to reload monitor id %d for streaming", monitor_id);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool StreamBase::checkInitialised() {
|
|
if (!monitor) {
|
|
Error("Cannot stream, not initialised");
|
|
return false;
|
|
}
|
|
if (monitor->Capturing() == Monitor::CAPTURING_NONE) {
|
|
Info("Monitor %d has capturing == NONE. Will not be able to connect to it.", monitor_id);
|
|
return false;
|
|
}
|
|
if (!monitor->ShmValid()) {
|
|
Debug(1, "Monitor shm is not connected");
|
|
return false;
|
|
}
|
|
if ((monitor->GetType() == Monitor::FFMPEG) and (monitor->Decoding() == Monitor::DECODING_NONE) ) {
|
|
Debug(1, "Monitor is not decoding.");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void StreamBase::updateFrameRate(double fps) {
|
|
if ( (fps < 0) || !fps || std::isinf(fps) ) {
|
|
Debug(1, "Zero or negative fps %f in updateFrameRate. Setting frame_mod=1 and effective_fps=0.0", fps);
|
|
effective_fps = 0.0;
|
|
base_fps = 0.0;
|
|
return;
|
|
}
|
|
|
|
// Snapshot once so the loop below cannot see maxfps change underneath it.
|
|
const double max_fps = maxfps.load();
|
|
const int rate = replay_rate.load();
|
|
|
|
base_fps = fps;
|
|
effective_fps = (base_fps*abs(rate))/ZM_RATE_BASE;
|
|
frame_mod = 1;
|
|
Debug(3, "FPS:%.2f, MaxFPS:%.2f, BaseFPS:%.2f, EffectiveFPS:%.2f, FrameMod:%d, replay_rate(%d)",
|
|
fps, max_fps, base_fps, effective_fps, frame_mod, rate);
|
|
if (max_fps > 0.0) {
|
|
// Min frame repeat?
|
|
// We want to keep the frame skip easy... problem is ... if effective = 31 and max = 30 then we end up with 15.5 fps.
|
|
while ( (int)effective_fps > (int)max_fps ) {
|
|
effective_fps /= 2.0;
|
|
frame_mod *= 2;
|
|
Debug(3, "Changing fps to be < max %.2f EffectiveFPS:%.2f, FrameMod:%d",
|
|
max_fps, effective_fps, frame_mod);
|
|
}
|
|
}
|
|
} // void StreamBase::updateFrameRate(double fps)
|
|
|
|
void StreamBase::checkCommandQueue() {
|
|
while (!zm_terminate) {
|
|
// Update modified time of the socket .lock file so that we can tell which ones are stale.
|
|
if (now - last_comm_update > Hours(1)) {
|
|
touch(sock_path_lock);
|
|
last_comm_update = now;
|
|
}
|
|
|
|
if (sd >= 0) {
|
|
CmdMsg msg;
|
|
memset(&msg, 0, sizeof(msg));
|
|
int nbytes = recvfrom(sd, &msg, sizeof(msg), 0, /*MSG_DONTWAIT*/ 0, 0);
|
|
if (nbytes < 0) {
|
|
if (errno != EAGAIN) {
|
|
Error("recvfrom(), errno = %d, error = %s", errno, strerror(errno));
|
|
}
|
|
} else {
|
|
Debug(2, "Message length is (%d)", nbytes);
|
|
processCommand(&msg);
|
|
got_command = true;
|
|
}
|
|
} else if (connkey) {
|
|
Warning("No sd in checkCommandQueue, comms not open for connkey %06d?", connkey);
|
|
} else {
|
|
// Perfectly valid if only getting a snapshot
|
|
Debug(1, "No sd in checkCommandQueue, comms not open.");
|
|
}
|
|
} // end while !zm_terminate
|
|
} // end void StreamBase::checkCommandQueue()
|
|
|
|
int StreamBase::preScaleDimensions(int base_width, int base_height, int &width, int &height) const {
|
|
// Any zoom crops before scaling and needs the full resolution to crop out of.
|
|
if (zoom.load() != ZM_SCALE_BASE) return 0;
|
|
|
|
const int cur_scale = scale.load();
|
|
if (cur_scale == ZM_SCALE_BASE) return 0;
|
|
|
|
const int scaled_width = (base_width * cur_scale) / ZM_SCALE_BASE;
|
|
const int scaled_height = (base_height * cur_scale) / ZM_SCALE_BASE;
|
|
// CMD_SCALE stores whatever the client sent, so 0 gets here. Image::Scale
|
|
// refuses a zero factor, but converting straight to 0x0 would instead put a
|
|
// negative av_image_get_buffer_size result into an unsigned size and allocate
|
|
// on it, so leave anything degenerate to the path that already rejects it.
|
|
if ((scaled_width <= 0) or (scaled_height <= 0)) return 0;
|
|
|
|
width = scaled_width;
|
|
height = scaled_height;
|
|
return cur_scale;
|
|
}
|
|
|
|
Image *StreamBase::prepareImage(Image *image, int pre_scaled_by) {
|
|
if (pre_scaled_by) {
|
|
// The caller produced the image at exactly the size to send, folding the
|
|
// scale into a colour conversion it had to do anyway. Send it untouched.
|
|
//
|
|
// The command thread can change scale or zoom between the caller reading
|
|
// them and this call, in which case this one frame goes out at the scale it
|
|
// was built for and without any new zoom, and the next frame is built from
|
|
// the new values. Scaling here instead would apply the scale a second time
|
|
// (a 50% frame of a 640px source would go out at 160px, not 320px), and
|
|
// cropping here would do it in the reduced image's coordinates and leave
|
|
// last_crop in the wrong units for every later frame. refs #3681
|
|
Debug(3, "Sending pre-scaled %dx%d, scale %d", image->Width(), image->Height(), pre_scaled_by);
|
|
last_scale = pre_scaled_by;
|
|
last_zoom = ZM_SCALE_BASE;
|
|
return image;
|
|
}
|
|
|
|
/* zooming should happen before scaling to preserve quality
|
|
* scale is relative to base dimensions, and represents the rough ratio between desired view size and base dimensions
|
|
*/
|
|
bool image_copied = false;
|
|
|
|
// Snapshot the pan/zoom/scale state once. The command thread can change any
|
|
// of these at any point, and the crop below is derived from all three
|
|
// together, so re-reading them mid-calculation could mix a new zoom with an
|
|
// old click position and produce a crop that matches neither.
|
|
const int cur_zoom = zoom.load();
|
|
const int cur_scale = scale.load();
|
|
const unsigned short cur_x = x.load();
|
|
const unsigned short cur_y = y.load();
|
|
|
|
if (cur_zoom != 100) {
|
|
int base_image_width = image->Width(),
|
|
base_image_height = image->Height(),
|
|
disp_image_width = image->Width() * cur_scale/ZM_SCALE_BASE,
|
|
disp_image_height = image->Height() * cur_scale / ZM_SCALE_BASE;
|
|
/* x and y are scaled by web UI to base dimensions units.
|
|
* When zooming, we blow up the image by the amount 150 for first zoom, right? 150%, then cut out a base sized chunk
|
|
* However if we have zoomed before, then we are zooming into the previous cutout
|
|
* The box stored in last_crop should be in base_image units, So we need to turn x,y into percentages, then apply to last_crop
|
|
*/
|
|
if (!last_crop.Hi().x_ && !last_crop.Hi().y_) last_crop = Box({0, 0}, {base_image_width, base_image_height});
|
|
|
|
double x_percent = static_cast<double>(cur_x * ZM_SCALE_BASE) / base_image_width;
|
|
double y_percent = static_cast<double>(cur_y * ZM_SCALE_BASE) / base_image_height;
|
|
Debug(2, "click percent %dx%d => %.2fx%.2f", cur_x, cur_y, x_percent, y_percent);
|
|
|
|
// If we were previously zoomed in, then the coordinate percentages are into the crop, so calculate the click coordinates in base image
|
|
int crop_x = last_crop.Lo().x_ + (x_percent * last_crop.Width() / ZM_SCALE_BASE);
|
|
int crop_y = last_crop.Lo().y_ + (y_percent * last_crop.Height() / ZM_SCALE_BASE);
|
|
Debug(2, "crop click %dx%d => %dx%d out of %dx%d", cur_x, cur_y, crop_x, crop_y, last_crop.Width(), last_crop.Height());
|
|
|
|
int zoom_image_width = base_image_width * cur_zoom / ZM_SCALE_BASE,
|
|
zoom_image_height = base_image_height * cur_zoom / ZM_SCALE_BASE,
|
|
click_x = crop_x * cur_zoom / ZM_SCALE_BASE,
|
|
click_y = crop_y * cur_zoom / ZM_SCALE_BASE;
|
|
Debug(2, "adjusted click %dx%d * %d zoom => %dx%d out of %dx%d", cur_x, cur_y, cur_zoom, click_x, click_y, zoom_image_width, zoom_image_height);
|
|
|
|
// These can go out of image. Resulting size will be less than base image. That's ok.
|
|
// We don't want to center it, we want to keep the relative offset from center where the click is.
|
|
int left_dist = base_image_width * x_percent/ZM_SCALE_BASE;
|
|
int top_dist = base_image_height * y_percent/ZM_SCALE_BASE;
|
|
Debug(2, "Dest at %d,%d", left_dist, top_dist);
|
|
|
|
int lo_x = click_x - left_dist;
|
|
int hi_x = lo_x + base_image_width;
|
|
Debug(2, "hi_x = lo_x %d + base_image_width %d - left_dist %d = %d", lo_x, base_image_width, left_dist, hi_x);
|
|
int lo_y = click_y - top_dist;
|
|
int hi_y = lo_y + base_image_height;
|
|
Debug(2, "hi_y = lo_y %d + base_image_h %d - top_dist %d = %d", lo_y, base_image_height, top_dist, hi_y);
|
|
|
|
int amount_to_shrink_y = 0;
|
|
if (lo_x < 0) {
|
|
amount_to_shrink_y = (-1 * lo_x) * base_image_height / base_image_width;
|
|
lo_x = 0;
|
|
} else if (hi_x >= zoom_image_width) {
|
|
amount_to_shrink_y = (hi_x - zoom_image_width) * base_image_height / base_image_width;
|
|
hi_x = zoom_image_width - 1;
|
|
}
|
|
Debug(1, "Shrinking y by %d from %d,%d to %d,%d", amount_to_shrink_y, lo_y, hi_y, lo_y+(amount_to_shrink_y/2), hi_y-(amount_to_shrink_y/2));
|
|
if (amount_to_shrink_y) {
|
|
lo_y += amount_to_shrink_y/2;
|
|
hi_y -= amount_to_shrink_y/2;
|
|
}
|
|
|
|
int amount_to_shrink_x = 0;
|
|
if (lo_y < 0) {
|
|
amount_to_shrink_x = (-1 * lo_y) * base_image_width / base_image_height;
|
|
Debug(1, "%d to %d = %d", lo_y, -1*lo_y, amount_to_shrink_x);
|
|
lo_y = 0;
|
|
} else if (hi_y >= zoom_image_height) {
|
|
amount_to_shrink_x = (hi_y - zoom_image_height) * base_image_width / base_image_height;
|
|
hi_y = zoom_image_height - 1;
|
|
}
|
|
Debug(1, "Shrinking x by %d from %d,%d to %d,%d", amount_to_shrink_x, lo_x, hi_x, lo_x+(amount_to_shrink_x/2), hi_x-(amount_to_shrink_x/2));
|
|
if (amount_to_shrink_x) {
|
|
lo_x += amount_to_shrink_x/2;
|
|
hi_x -= amount_to_shrink_x/2;
|
|
}
|
|
|
|
Debug(3, "Cropping to %d,%d -> %d,%d %dx%din blown up image", lo_x, lo_y, hi_x, hi_y, hi_x-lo_x, hi_y-lo_y);
|
|
// Scaled back to base_image dimensions
|
|
last_crop = Box({lo_x*ZM_SCALE_BASE/zoom, lo_y*ZM_SCALE_BASE/zoom}, {hi_x*ZM_SCALE_BASE/zoom, hi_y*ZM_SCALE_BASE/zoom});
|
|
|
|
Debug(3, "Cropping to %d,%d -> %d,%d", last_crop.Lo().x_, last_crop.Lo().y_, last_crop.Hi().x_, last_crop.Hi().y_);
|
|
if ( !image_copied ) {
|
|
static Image copy_image;
|
|
copy_image.Assign(*image);
|
|
image = ©_image;
|
|
image_copied = true;
|
|
}
|
|
image->Crop(last_crop);
|
|
image->Scale(disp_image_width, disp_image_height);
|
|
} else if (cur_scale != ZM_SCALE_BASE) {
|
|
Debug(3, "scaling by %d from %dx%d", cur_scale, image->Width(), image->Height());
|
|
static Image copy_image;
|
|
copy_image.Assign(*image);
|
|
image = ©_image;
|
|
image_copied = true;
|
|
image->Scale(cur_scale);
|
|
}
|
|
Debug(3, "Sending %dx%d", image->Width(), image->Height());
|
|
|
|
// Record what was actually rendered, not whatever the command thread may
|
|
// have set since.
|
|
last_scale = cur_scale;
|
|
last_zoom = cur_zoom;
|
|
last_x = cur_x;
|
|
last_y = cur_y;
|
|
|
|
return image;
|
|
} // end Image *StreamBase::prepareImage(Image *image, int pre_scaled_by)
|
|
|
|
bool StreamBase::sendTextFrame(const char *frame_text) {
|
|
int width = 640;
|
|
int height = 480;
|
|
int colours = ZM_COLOUR_RGB32;
|
|
int subpixelorder = ZM_SUBPIX_ORDER_RGBA;
|
|
int labelsize = 2;
|
|
|
|
if (monitor) {
|
|
width = monitor->Width();
|
|
height = monitor->Height();
|
|
colours = monitor->Colours();
|
|
subpixelorder = monitor->SubpixelOrder();
|
|
labelsize = monitor->LabelSize();
|
|
}
|
|
Debug(2, "Sending %dx%dx%dx%d * %d scale text frame '%s'",
|
|
width, height, colours, subpixelorder, scale.load(), frame_text);
|
|
|
|
Image image(width, height, colours, subpixelorder);
|
|
image.Clear();
|
|
image.Annotate(frame_text, image.centreCoord(frame_text, labelsize), labelsize);
|
|
|
|
if (scale != 100) {
|
|
image.Scale(scale);
|
|
Debug(2, "Scaled to %dx%d", image.Width(), image.Height());
|
|
}
|
|
if (type == STREAM_MPEG) {
|
|
if (!vid_stream) {
|
|
vid_stream = new VideoStream("pipe:", format, bitrate, effective_fps, image.Colours(), image.SubpixelOrder(), image.Width(), image.Height());
|
|
fprintf(stdout, "Content-Type: %s\r\n\r\n", vid_stream->MimeType());
|
|
if (!vid_stream->OpenStream()) {
|
|
Error("Failed to open video stream");
|
|
delete vid_stream;
|
|
vid_stream = nullptr;
|
|
return false;
|
|
}
|
|
}
|
|
/* double pts = */ vid_stream->EncodeFrame(image.Buffer(), image.Size());
|
|
} else {
|
|
static unsigned char buffer[ZM_MAX_IMAGE_SIZE];
|
|
size_t n_bytes = 0;
|
|
|
|
image.EncodeJpeg(buffer, &n_bytes);
|
|
|
|
if (type == STREAM_JPEG) {
|
|
if (0 > fputs("--" BOUNDARY "\r\n", stdout)) {
|
|
Debug(1, "Error sending --" BOUNDARY "\r\n");
|
|
return false;
|
|
}
|
|
}
|
|
if (0 > fprintf(stdout, "Content-Type: image/jpeg\r\nContent-Length: %zu\r\n\r\n", n_bytes)) {
|
|
Debug(1, "Error sending Content-Type: image/jpeg\r\nContent-Length: %zu\r\n\r\n", n_bytes);
|
|
return false;
|
|
}
|
|
int rc = fwrite(buffer, n_bytes, 1, stdout);
|
|
if (rc != 1) {
|
|
if (!zm_terminate)
|
|
Error("Unable to send stream text frame: %d %s", rc, strerror(errno));
|
|
return false;
|
|
}
|
|
fputs("\r\n\r\n", stdout);
|
|
fflush(stdout);
|
|
}
|
|
last_frame_sent = now;
|
|
return true;
|
|
}
|
|
|
|
void StreamBase::openComms() {
|
|
if ( connkey > 0 ) {
|
|
|
|
// Have to mkdir because systemd is now chrooting and the dir may not exist
|
|
if ( mkdir(staticConfig.PATH_SOCKS.c_str(), 0755) ) {
|
|
if ( errno != EEXIST ) {
|
|
Error("Can't mkdir ZM_PATH_SOCKS %s: %s.", staticConfig.PATH_SOCKS.c_str(), strerror(errno));
|
|
}
|
|
}
|
|
|
|
unsigned int length = snprintf(
|
|
sock_path_lock,
|
|
sizeof(sock_path_lock),
|
|
"%s/zms-%06d.lock",
|
|
staticConfig.PATH_SOCKS.c_str(),
|
|
connkey
|
|
);
|
|
if ( length >= sizeof(sock_path_lock) ) {
|
|
Warning("Socket lock path was truncated.");
|
|
}
|
|
Debug(1, "Trying to open the lock on %s", sock_path_lock);
|
|
|
|
// Under systemd, we get chrooted to something like /tmp/systemd-apache-blh/ so the dir may not exist.
|
|
if ( mkdir(staticConfig.PATH_SOCKS.c_str(), 0755) ) {
|
|
if ( errno != EEXIST ) {
|
|
Error("Can't mkdir %s: %s", staticConfig.PATH_SOCKS.c_str(), strerror(errno));
|
|
return;
|
|
} else {
|
|
Debug(3, "SOCKS dir %s already exists", staticConfig.PATH_SOCKS.c_str() );
|
|
}
|
|
} else {
|
|
Debug(3, "Success making SOCKS dir %s", staticConfig.PATH_SOCKS.c_str() );
|
|
}
|
|
|
|
lock_fd = open(sock_path_lock, O_CREAT|O_WRONLY, S_IRUSR | S_IWUSR);
|
|
if ( lock_fd < 0 ) {
|
|
Error("Unable to open sock lock file %s: %s", sock_path_lock, strerror(errno));
|
|
lock_fd = -1;
|
|
} else if ( flock(lock_fd, LOCK_EX) != 0 ) {
|
|
Error("Unable to lock sock lock file %s: %s", sock_path_lock, strerror(errno));
|
|
close(lock_fd);
|
|
lock_fd = -1;
|
|
} else {
|
|
Debug(1, "We have obtained a lock on %s fd: %d", sock_path_lock, lock_fd);
|
|
}
|
|
|
|
sd = socket(AF_UNIX, SOCK_DGRAM, 0);
|
|
if ( sd < 0 ) {
|
|
Fatal("Can't create socket: %s", strerror(errno));
|
|
} else {
|
|
Debug(3, "Have socket %d", sd);
|
|
}
|
|
|
|
length = snprintf(
|
|
loc_sock_path,
|
|
sizeof(loc_sock_path),
|
|
"%s/zms-%06ds.sock",
|
|
staticConfig.PATH_SOCKS.c_str(),
|
|
connkey
|
|
);
|
|
if ( length >= sizeof(loc_sock_path) ) {
|
|
Warning("Socket path was truncated.");
|
|
length = sizeof(loc_sock_path)-1;
|
|
}
|
|
// Unlink before bind, in case it already exists
|
|
unlink(loc_sock_path);
|
|
if ( sizeof(loc_addr.sun_path) < length ) {
|
|
Error("Not enough space %zu in loc_addr.sun_path for socket file %s", sizeof(loc_addr.sun_path), loc_sock_path);
|
|
}
|
|
|
|
strncpy(loc_addr.sun_path, loc_sock_path, sizeof(loc_addr.sun_path));
|
|
loc_addr.sun_family = AF_UNIX;
|
|
Debug(3, "Binding to %s", loc_sock_path);
|
|
if ( ::bind(sd, (struct sockaddr *)&loc_addr, strlen(loc_addr.sun_path)+sizeof(loc_addr.sun_family)+1) < 0 ) {
|
|
Fatal("Can't bind: %s", strerror(errno));
|
|
}
|
|
|
|
snprintf(rem_sock_path, sizeof(rem_sock_path), "%s/zms-%06dw.sock", staticConfig.PATH_SOCKS.c_str(), connkey);
|
|
strncpy(rem_addr.sun_path, rem_sock_path, sizeof(rem_addr.sun_path));
|
|
rem_addr.sun_family = AF_UNIX;
|
|
|
|
struct timeval tv {1,0}; /* 1 Secs Timeout */
|
|
setsockopt(sd, SOL_SOCKET, SO_RCVTIMEO,(struct timeval *)&tv, sizeof(struct timeval));
|
|
|
|
last_comm_update = std::chrono::steady_clock::now();
|
|
Debug(3, "comms open at %s", loc_sock_path);
|
|
} // end if connKey > 0
|
|
} // end void StreamBase::openComms()
|
|
|
|
void StreamBase::closeComms() {
|
|
if ( connkey > 0 ) {
|
|
if ( sd >= 0 ) {
|
|
close(sd);
|
|
sd = -1;
|
|
}
|
|
// Remove our command socket, but only while we still hold the lock, and only
|
|
// if we actually got the lock.
|
|
//
|
|
// Note this is zms-<connkey>s.sock, not the .lock file. A second zms sharing
|
|
// this connkey is blocked in flock(LOCK_EX) on the .lock file, and the first
|
|
// thing it does after winning that lock is unlink this very path itself
|
|
// ("Unlink before bind" in openComms()). It never reads our socket file, so
|
|
// removing it here only does early what our successor would do anyway.
|
|
//
|
|
// Ordering matters: if we unlinked after releasing the lock we could delete
|
|
// a socket the successor had already bound, leaving it unreachable.
|
|
//
|
|
// The lock_fd guard matters because openComms() carries on and binds even
|
|
// when it fails to take the lock, so without it a lockless zms could delete
|
|
// a socket belonging to the zms that does hold the lock. Leaking the file in
|
|
// that case is no worse than the behaviour before this check existed.
|
|
//
|
|
// This is worth doing because web/ajax/stream.php uses file_exists() on this
|
|
// path to decide whether zms is listening. A socket left behind by an exited
|
|
// zms makes that check succeed, so the sendto() fails with ECONNREFUSED
|
|
// instead of waiting for the new process to bind, and the command is lost.
|
|
if ( (lock_fd >= 0) && loc_sock_path[0]
|
|
&& (unlink(loc_sock_path) < 0) && (errno != ENOENT) ) {
|
|
Warning("Failed to unlink '%s': %s", loc_sock_path, strerror(errno));
|
|
}
|
|
// Don't unlink sock_path_lock: another zms may already be waiting on it.
|
|
if ( lock_fd >= 0 ) {
|
|
close(lock_fd); //close it rather than unlock it in case it got deleted.
|
|
lock_fd = -1;
|
|
}
|
|
}
|
|
} // end void StreamBase::closeComms
|
|
|
|
void StreamBase::reserveTempImgBuffer(size_t size) {
|
|
if (temp_img_buffer_size < size) {
|
|
Debug(1, "Resizing image buffer from %zu to %zu", temp_img_buffer_size, size);
|
|
delete[] temp_img_buffer;
|
|
temp_img_buffer = new uint8_t[size];
|
|
temp_img_buffer_size = size;
|
|
}
|
|
}
|