Merge feat/p6-stop: --stop-after/--stop-at deadline stop
This commit is contained in:
@@ -178,6 +178,9 @@ static void config_set_defaults(Config* config) {
|
||||
config->use_xattrs = false;
|
||||
config->fake_super = false;
|
||||
config->trust_sender = false;
|
||||
config->stop_after_mins = 0;
|
||||
config->stop_at = 0;
|
||||
config->stop_at_set = false;
|
||||
}
|
||||
|
||||
static bool valid_wire_bool(int value) {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
|
||||
typedef enum { TRANSPORT_TCP, TRANSPORT_SSH } TransportType;
|
||||
|
||||
@@ -457,6 +458,18 @@ typedef struct Config {
|
||||
* authorized root (see the phase-5 notes in RSYNC_COMPAT.md). Off by
|
||||
* default; only relaxes validation when explicitly requested. */
|
||||
bool trust_sender;
|
||||
|
||||
// Phase 6: --stop-after / --stop-at
|
||||
/* Client-only sender-side transfer stop deadlines. --stop-after=MINS stops
|
||||
* the transfer after a number of elapsed minutes (checked against
|
||||
* CLOCK_MONOTONIC so clock changes do not skew it); --stop-at=TIME stops at
|
||||
* an absolute wall-clock time (HH:MM, HH:MM:SS, or now+N[smhd]). At the
|
||||
* deadline the run stops elegantly at the next chunk/file boundary and the
|
||||
* completion tail still runs (exit 0). Both are LOCAL to the sending
|
||||
* process and are NEVER serialized into the config frame. */
|
||||
int stop_after_mins; /* --stop-after=MINS minutes; 0 when unset */
|
||||
time_t stop_at; /* --stop-at=... absolute wall-clock deadline */
|
||||
bool stop_at_set; /* true when --stop-at was given */
|
||||
} Config;
|
||||
|
||||
/* Phase 5 (remote-option wave): 2.13.0 -> 2.14.0.
|
||||
|
||||
@@ -31,6 +31,7 @@ PipelineContextSender* pipeline_context_sender_create(Config* config, Queue* que
|
||||
context->scan_had_io_error = false;
|
||||
context->remove_source_files = NULL;
|
||||
context->early_delete = false;
|
||||
context->scan_stopped_early = false;
|
||||
context->total_files = 0;
|
||||
context->progress_bytes = 0;
|
||||
context->total_bytes = 0;
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "protocol.h"
|
||||
#include "queue.h"
|
||||
#include "receiver.h"
|
||||
#include "stop_condition.h"
|
||||
#include <openssl/ssl.h>
|
||||
|
||||
typedef struct {
|
||||
@@ -56,6 +57,16 @@ typedef struct {
|
||||
bool sender_done;
|
||||
atomic_bool cancelled;
|
||||
ProtocolSession allocation_session;
|
||||
/* Phase 6: client-only sender stop deadline, computed once before the worker
|
||||
* threads start and shared read-only by the scanner and the sender thread. */
|
||||
StopCondition stop_condition;
|
||||
/* Phase 6: set when the scanner/sender reached the stop deadline before the
|
||||
* scan (and thus the keep-set manifest) completed naturally. When true the
|
||||
* completion tail must NOT transmit the partial manifest, or the receiver
|
||||
* would delete unscanned source mirrors. Written by the sender thread
|
||||
* before it reads the manifest, so no additional synchronization is needed
|
||||
* to suppress the manifest. */
|
||||
bool scan_stopped_early;
|
||||
} PipelineContextSender;
|
||||
|
||||
typedef struct PipelineContextReceiver {
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
#include "stop_condition.h"
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* Parse a strictly positive decimal integer: only ASCII digits, no leading
|
||||
* whitespace, sign or trailing garbage. */
|
||||
static bool parse_positive_minutes(const char* value, long* out) {
|
||||
if (!value || *value == '\0')
|
||||
return false;
|
||||
if (*value < '0' || *value > '9')
|
||||
return false;
|
||||
long v = 0;
|
||||
for (const char* p = value; *p != '\0'; p++) {
|
||||
if (*p < '0' || *p > '9')
|
||||
return false;
|
||||
int digit = *p - '0';
|
||||
if (v > (LONG_MAX - digit) / 10)
|
||||
return false;
|
||||
v = v * 10 + digit;
|
||||
}
|
||||
if (v <= 0 || v > INT_MAX)
|
||||
return false;
|
||||
*out = v;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool stop_parse_after_minutes(const char* value, int* out_minutes) {
|
||||
if (!out_minutes)
|
||||
return false;
|
||||
long minutes = 0;
|
||||
if (!parse_positive_minutes(value, &minutes))
|
||||
return false;
|
||||
*out_minutes = (int)minutes;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Two consecutive ASCII digits -> 0..99. */
|
||||
static bool parse_two_digits(const char* s, int* out) {
|
||||
if (s[0] < '0' || s[0] > '9' || s[1] < '0' || s[1] > '9')
|
||||
return false;
|
||||
*out = (s[0] - '0') * 10 + (s[1] - '0');
|
||||
return true;
|
||||
}
|
||||
|
||||
bool stop_parse_at_time(const char* value, time_t now, time_t* out_deadline) {
|
||||
if (!value || !out_deadline)
|
||||
return false;
|
||||
|
||||
/* now+N[smhd]: N whole units from the current wall clock. */
|
||||
if (strncmp(value, "now+", 4) == 0) {
|
||||
const char* p = value + 4;
|
||||
/* The count must be a bare non-negative digit run: reject leading
|
||||
whitespace ('now+ 5s') and a leading sign ('now++5s'). */
|
||||
if (*p < '0' || *p > '9')
|
||||
return false;
|
||||
errno = 0;
|
||||
char* end = NULL;
|
||||
long amount = strtol(p, &end, 10);
|
||||
if (errno != 0 || end == p || amount < 0)
|
||||
return false;
|
||||
long unit_seconds;
|
||||
switch (*end) {
|
||||
case 's':
|
||||
unit_seconds = 1;
|
||||
break;
|
||||
case 'm':
|
||||
unit_seconds = 60;
|
||||
break;
|
||||
case 'h':
|
||||
unit_seconds = 3600;
|
||||
break;
|
||||
case 'd':
|
||||
unit_seconds = 86400;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
if (end[1] != '\0')
|
||||
return false;
|
||||
if (amount > LONG_MAX / unit_seconds)
|
||||
return false;
|
||||
long long delta = (long long)amount * unit_seconds;
|
||||
/* Guard against signed overflow of now + delta. */
|
||||
if ((long long)now > 0 && delta > (long long)LLONG_MAX - (long long)now)
|
||||
return false;
|
||||
if ((long long)now < 0 && delta < (long long)LLONG_MIN - (long long)now)
|
||||
return false;
|
||||
*out_deadline = now + (time_t)delta;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* HH:MM or HH:MM:SS on the current local day. */
|
||||
size_t len = strlen(value);
|
||||
if (len != 5 && len != 8)
|
||||
return false;
|
||||
if (value[2] != ':' || (len == 8 && value[5] != ':'))
|
||||
return false;
|
||||
int hh, mm, ss = 0;
|
||||
if (!parse_two_digits(value, &hh) || !parse_two_digits(value + 3, &mm))
|
||||
return false;
|
||||
if (len == 8 && !parse_two_digits(value + 6, &ss))
|
||||
return false;
|
||||
if (hh > 23 || mm > 59 || ss > 59)
|
||||
return false;
|
||||
|
||||
struct tm today;
|
||||
if (!localtime_r(&now, &today))
|
||||
return false;
|
||||
today.tm_hour = hh;
|
||||
today.tm_min = mm;
|
||||
today.tm_sec = ss;
|
||||
today.tm_isdst = -1;
|
||||
time_t deadline = mktime(&today);
|
||||
if (deadline == (time_t)-1)
|
||||
return false;
|
||||
*out_deadline = deadline;
|
||||
return true;
|
||||
}
|
||||
|
||||
StopCondition stop_condition_make(bool has_after, int after_minutes, bool has_at, time_t at_time,
|
||||
struct timespec now_mono) {
|
||||
StopCondition condition;
|
||||
condition.has_monotonic = false;
|
||||
condition.monotonic_deadline.tv_sec = 0;
|
||||
condition.monotonic_deadline.tv_nsec = 0;
|
||||
condition.has_wall = false;
|
||||
condition.wall_deadline = 0;
|
||||
if (has_after && after_minutes > 0) {
|
||||
condition.has_monotonic = true;
|
||||
condition.monotonic_deadline.tv_sec = now_mono.tv_sec + (time_t)after_minutes * 60;
|
||||
condition.monotonic_deadline.tv_nsec = now_mono.tv_nsec;
|
||||
}
|
||||
if (has_at) {
|
||||
condition.has_wall = true;
|
||||
condition.wall_deadline = at_time;
|
||||
}
|
||||
return condition;
|
||||
}
|
||||
|
||||
bool stop_condition_reached(const StopCondition* condition) {
|
||||
if (!condition)
|
||||
return false;
|
||||
if (condition->has_wall && time(NULL) >= condition->wall_deadline)
|
||||
return true;
|
||||
if (condition->has_monotonic) {
|
||||
struct timespec now;
|
||||
if (clock_gettime(CLOCK_MONOTONIC, &now) != 0)
|
||||
return false;
|
||||
if (now.tv_sec > condition->monotonic_deadline.tv_sec ||
|
||||
(now.tv_sec == condition->monotonic_deadline.tv_sec &&
|
||||
now.tv_nsec >= condition->monotonic_deadline.tv_nsec))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
#ifndef STOP_CONDITION_H
|
||||
#define STOP_CONDITION_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <time.h>
|
||||
|
||||
/* Client-only transfer stop conditions (--stop-after=MINS / --stop-at=TIME).
|
||||
* Both are local sender-side deadlines: they are never serialized into the
|
||||
* config frame and never bump PROTOCOL_VERSION. A transfer checks the
|
||||
* condition at natural chunk/file boundaries and, once reached, stops
|
||||
* elegantly (everything already sent is finalized normally, exit 0).
|
||||
*
|
||||
* A condition combines an optional CLOCK_MONOTONIC instant (the relative
|
||||
* --stop-after duration, immune to wall-clock changes) with an optional
|
||||
* wall-clock instant (the absolute --stop-at form). Either one being reached
|
||||
* ends the transfer. */
|
||||
typedef struct StopCondition {
|
||||
bool has_monotonic;
|
||||
struct timespec monotonic_deadline;
|
||||
bool has_wall;
|
||||
time_t wall_deadline;
|
||||
} StopCondition;
|
||||
|
||||
/* Parse --stop-after=MINS: a positive integer count of minutes. Zero,
|
||||
* negative, empty and non-numeric values are rejected. Returns true when
|
||||
* accepted and stores the value in *out_minutes. */
|
||||
bool stop_parse_after_minutes(const char* value, int* out_minutes);
|
||||
|
||||
/* Parse --stop-at=TIME. Accepted forms are HH:MM, HH:MM:SS and
|
||||
* now+N[smhd] (seconds/minutes/hours/days from now). The absolute forms are
|
||||
* resolved against `now` (local wall clock) and written to *out_deadline; a
|
||||
* time already in the past yields a deadline <= now ("stop immediately").
|
||||
* Returns false on any malformed value. */
|
||||
bool stop_parse_at_time(const char* value, time_t now, time_t* out_deadline);
|
||||
|
||||
/* Build the runtime condition at transfer start. after_minutes is the
|
||||
* relative --stop-after duration (<= 0 disables it); at_time is the absolute
|
||||
* --stop-at deadline (only consulted when has_at is true); now_mono is the
|
||||
* CLOCK_MONOTONIC reading at start. */
|
||||
StopCondition stop_condition_make(bool has_after, int after_minutes, bool has_at, time_t at_time,
|
||||
struct timespec now_mono);
|
||||
|
||||
/* True once either deadline has passed (wall clock first, then monotonic). */
|
||||
bool stop_condition_reached(const StopCondition* condition);
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user