Preserve Queue entries across spool failures
Debian Trixie package bundle / packages (push) Failing after 13m33s
Debian Trixie package bundle / packages (push) Failing after 13m33s
This commit is contained in:
@@ -94,6 +94,20 @@ reserve was subtracted without 32-bit truncation:
|
||||
./contrib/testing/queue-disk-space-check.sh
|
||||
```
|
||||
|
||||
The Queue failure-safety check retains one isolated message while it first
|
||||
raises the operative reserve above the filesystem's available bytes and then
|
||||
makes the spool unreadable. It verifies rejection/defer responses, restores
|
||||
the configuration and permissions through traps, and compares the retained
|
||||
message byte-for-byte after both recoveries:
|
||||
|
||||
```sh
|
||||
./contrib/testing/queue-failure-safety-check.sh
|
||||
```
|
||||
|
||||
It requires passwordless permission for `bongo-admin __config-read` and
|
||||
`__config-replace`, Bongo service restarts, and `stat`/`chmod` on the
|
||||
dedicated test spool. Run it only on a disposable test installation.
|
||||
|
||||
The Debian source probes and package-build commands used by BLD-14/BLD-15 are
|
||||
consolidated in `contrib/debian/build-trixie-bundle.sh`; they are not maintained
|
||||
as duplicate container-only snippets here.
|
||||
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Verify that a simulated full Queue filesystem rejects new submissions and
|
||||
# that an unreadable spool defers access without changing an existing message.
|
||||
|
||||
set -eu
|
||||
|
||||
queue_tool=${BONGO_QUEUE_TOOL:-/usr/bin/bongo-queuetool}
|
||||
admin_tool=${BONGO_ADMIN_TOOL:-/usr/bin/bongo-admin}
|
||||
service=${BONGO_SERVICE:-bongo.service}
|
||||
spool=${BONGO_QUEUE_SPOOL:-/var/lib/bongo/spool}
|
||||
run_as=${BONGO_TEST_RUN_AS:-bongo}
|
||||
as_root=${BONGO_TEST_AS_ROOT:-sudo -n}
|
||||
|
||||
if [ "$(id -un)" = "$run_as" ]; then
|
||||
as_bongo=
|
||||
else
|
||||
as_bongo="sudo -n -u $run_as"
|
||||
fi
|
||||
|
||||
work=$(mktemp -d "${TMPDIR:-/tmp}/bongo-stq04.XXXXXX")
|
||||
chmod 0755 "$work"
|
||||
queue_id=
|
||||
spool_mode=
|
||||
config_changed=0
|
||||
|
||||
restore()
|
||||
{
|
||||
status=$?
|
||||
if [ -n "$spool_mode" ]; then
|
||||
$as_root /usr/bin/chmod "$spool_mode" "$spool" >/dev/null 2>&1 || true
|
||||
fi
|
||||
if [ "$config_changed" -eq 1 ]; then
|
||||
$as_root "$admin_tool" __config-replace queue \
|
||||
<"$work/queue.original" >/dev/null 2>&1 || true
|
||||
$as_root /usr/bin/systemctl restart "$service" >/dev/null 2>&1 || true
|
||||
fi
|
||||
if [ -n "$queue_id" ]; then
|
||||
$as_bongo "$queue_tool" delete "$queue_id" >/dev/null 2>&1 || true
|
||||
fi
|
||||
rm -rf "$work"
|
||||
exit "$status"
|
||||
}
|
||||
trap restore EXIT HUP INT TERM
|
||||
|
||||
printf '%s\r\n' \
|
||||
'From: STQ-04 <stq04@bongo.test>' \
|
||||
'To: stqqueue@bongo.test' \
|
||||
'Subject: STQ-04 retained queue fixture' \
|
||||
'Date: Thu, 23 Jul 2026 12:34:56 +0200' \
|
||||
'Message-ID: <stq04-retained@bongo.test>' \
|
||||
'' \
|
||||
'This exact message must survive both simulated failures.' \
|
||||
>"$work/message.eml"
|
||||
chmod 0644 "$work/message.eml"
|
||||
|
||||
$as_root "$admin_tool" __config-read queue >"$work/queue.original"
|
||||
queue_id=$($as_bongo "$queue_tool" hold-local "$work/message.eml")
|
||||
$as_bongo "$queue_tool" message "$queue_id" >"$work/before.eml"
|
||||
before_hash=$(sha256sum "$work/before.eml" | awk '{ print $1 }')
|
||||
|
||||
set -- $($as_bongo "$queue_tool" space details)
|
||||
free_before=$1
|
||||
free_after=$2
|
||||
block=$5
|
||||
python3 - "$work/queue.original" "$work/queue.full" \
|
||||
"$free_before" "$free_after" "$block" <<'PY'
|
||||
import json
|
||||
import sys
|
||||
|
||||
source, destination = sys.argv[1:3]
|
||||
free_before, free_after, block = map(int, sys.argv[3:])
|
||||
with open(source, "r", encoding="utf-8") as stream:
|
||||
config = json.load(stream)
|
||||
config["minimumfreespace"] = max(free_before, free_after) + block + 1
|
||||
with open(destination, "w", encoding="utf-8") as stream:
|
||||
json.dump(config, stream, indent=2, sort_keys=True)
|
||||
stream.write("\n")
|
||||
PY
|
||||
|
||||
$as_root "$admin_tool" __config-replace queue <"$work/queue.full"
|
||||
config_changed=1
|
||||
$as_root /usr/bin/systemctl restart "$service"
|
||||
|
||||
reported=$($as_bongo "$queue_tool" space)
|
||||
if [ "$reported" -ne 0 ]; then
|
||||
echo "STQ-04: simulated full spool reported $reported usable bytes" >&2
|
||||
exit 1
|
||||
fi
|
||||
if $as_bongo "$queue_tool" hold-local "$work/message.eml" \
|
||||
>"$work/full.out" 2>"$work/full.err"; then
|
||||
echo "STQ-04: Queue accepted a message below its free-space reserve" >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! grep -F 'Free disk space too low' "$work/full.err" >/dev/null; then
|
||||
echo "STQ-04: full-spool rejection did not report the expected error" >&2
|
||||
sed -n '1,20p' "$work/full.err" >&2
|
||||
exit 1
|
||||
fi
|
||||
$as_bongo "$queue_tool" message "$queue_id" >"$work/after-full.eml"
|
||||
|
||||
$as_root "$admin_tool" __config-replace queue <"$work/queue.original"
|
||||
config_changed=0
|
||||
$as_root /usr/bin/systemctl restart "$service"
|
||||
|
||||
spool_mode=$($as_root /usr/bin/stat -c %a "$spool")
|
||||
$as_root /usr/bin/chmod 000 "$spool"
|
||||
if $as_bongo "$queue_tool" message "$queue_id" \
|
||||
>"$work/unreadable.out" 2>"$work/unreadable.err"; then
|
||||
echo "STQ-04: Queue read a deliberately unreadable spool" >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! grep -F "Can't read queue data" "$work/unreadable.err" >/dev/null; then
|
||||
echo "STQ-04: unreadable spool did not report the expected error" >&2
|
||||
sed -n '1,20p' "$work/unreadable.err" >&2
|
||||
exit 1
|
||||
fi
|
||||
$as_root /usr/bin/chmod "$spool_mode" "$spool"
|
||||
spool_mode=
|
||||
$as_bongo "$queue_tool" message "$queue_id" >"$work/after-unreadable.eml"
|
||||
|
||||
if ! cmp -s "$work/before.eml" "$work/after-full.eml" ||
|
||||
! cmp -s "$work/before.eml" "$work/after-unreadable.eml"; then
|
||||
echo "STQ-04: retained Queue message changed during a failure" >&2
|
||||
exit 1
|
||||
fi
|
||||
after_hash=$(sha256sum "$work/after-unreadable.eml" | awk '{ print $1 }')
|
||||
if [ "$before_hash" != "$after_hash" ]; then
|
||||
echo "STQ-04: retained Queue message digest changed" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ "$($as_root /usr/bin/systemctl is-active "$service")" != active ]; then
|
||||
echo "STQ-04: Bongo service is not active after recovery" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf 'STQ-04 PASS queue=%s sha256=%s full-reported=%s spool-mode=%s\n' \
|
||||
"$queue_id" "$after_hash" "$reported" \
|
||||
"$($as_root /usr/bin/stat -c %a "$spool")"
|
||||
@@ -38,6 +38,12 @@ for the configured retry path. Likewise, a per-recipient LMTP temporary error
|
||||
must preserve only that recipient for retry and must not redeliver recipients
|
||||
which already returned a final success.
|
||||
|
||||
Queue control-file rewrites use a same-directory work file and an atomic
|
||||
rename. The original is retained unless every buffered write and close
|
||||
succeeds. A failed submission flush or final rename is rejected and its
|
||||
partial entry discarded, so the submitting SMTP peer remains responsible for
|
||||
retrying the message instead of receiving a false success.
|
||||
|
||||
## Administration and shutdown
|
||||
|
||||
Use `bongo-queuetool` to inspect and perform documented queue operations; do
|
||||
|
||||
@@ -7,6 +7,7 @@ add_executable(bongoqueue
|
||||
queued.c
|
||||
mime.c
|
||||
queue.c
|
||||
spool-file.c
|
||||
store-delivery-format.c
|
||||
)
|
||||
|
||||
@@ -56,4 +57,13 @@ if(BUILD_TESTING)
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
add_test(NAME queue-store-delivery-format COMMAND queue-store-delivery-format-test)
|
||||
|
||||
add_executable(queue-spool-file-test
|
||||
tests/spool-file-test.c
|
||||
spool-file.c
|
||||
)
|
||||
target_include_directories(queue-spool-file-test PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
add_test(NAME queue-spool-file COMMAND queue-spool-file-test)
|
||||
endif()
|
||||
|
||||
+137
-48
@@ -32,6 +32,7 @@
|
||||
#include "messages.h"
|
||||
#include "dsn-format.h"
|
||||
#include "envelope-format.h"
|
||||
#include "spool-file.h"
|
||||
#include "store-delivery-format.h"
|
||||
|
||||
static BOOL
|
||||
@@ -133,10 +134,17 @@ int
|
||||
fclose_check(FILE **fh, int line)
|
||||
{
|
||||
int ret;
|
||||
ret = fclose(*fh);
|
||||
FILE *stream;
|
||||
|
||||
if (fh == NULL || *fh == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
stream = *fh;
|
||||
*fh = NULL;
|
||||
ret = fclose(stream);
|
||||
if (ret == 0) {
|
||||
// LogFailureF("File close: handle %ld", *fh);
|
||||
*fh = NULL;
|
||||
// LogFailureF("File close: handle %ld", stream);
|
||||
} else {
|
||||
LogFailureF("File close failed on line %d: %d", line, errno);
|
||||
}
|
||||
@@ -951,12 +959,15 @@ StartOver:
|
||||
|
||||
MemFree(qEnvelope);
|
||||
|
||||
FCLOSE_CHECK(newFH);
|
||||
|
||||
UNLINK_CHECK(path);
|
||||
|
||||
QueueFormat(path2, "%s/w%s.%03d", Conf.spoolPath, entry, queue);
|
||||
RENAME_CHECK(path2, path);
|
||||
if (BongoQueueFileCommit(&newFH, path2, path) != 0) {
|
||||
Log(LOG_WARNING,
|
||||
"Could not atomically replace queue control file "
|
||||
"'%s': %s; original retained",
|
||||
path, strerror(errno));
|
||||
ProcessQueueEntryCleanUp(entryID, report);
|
||||
return(TRUE);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -3069,41 +3080,53 @@ CommandQaddq(void *param)
|
||||
return(ConnWrite(client->conn, MSG1000ENTRYMADE, sizeof(MSG1000ENTRYMADE) - 1));
|
||||
}
|
||||
|
||||
int
|
||||
CommandQabrt(void *param)
|
||||
static void
|
||||
DiscardClientEntry(QueueClient *client)
|
||||
{
|
||||
int ccode;
|
||||
QueueClient *client = (QueueClient *)param;
|
||||
|
||||
if (client->entry.control) {
|
||||
FCLOSE_CHECK(client->entry.control);
|
||||
client->entry.control = NULL;
|
||||
|
||||
QueueFormat(client->path,"%s/c%07lx.in", Conf.spoolPath, client->entry.id);
|
||||
UNLINK_CHECK(client->path);
|
||||
}
|
||||
|
||||
if (client->entry.id) {
|
||||
QueueFormat(client->path,"%s/c%07lx.in", Conf.spoolPath, client->entry.id);
|
||||
if (unlink(client->path) != 0 && errno != ENOENT) {
|
||||
LogFailureF("Unable to discard queue control file %s: %d",
|
||||
client->path, errno);
|
||||
}
|
||||
}
|
||||
|
||||
if (client->entry.data) {
|
||||
FCLOSE_CHECK(client->entry.data);
|
||||
client->entry.data = NULL;
|
||||
|
||||
}
|
||||
if (client->entry.id) {
|
||||
QueueFormat(client->path,"%s/d%07lx.msg",Conf.spoolPath, client->entry.id);
|
||||
UNLINK_CHECK(client->path);
|
||||
if (unlink(client->path) != 0 && errno != ENOENT) {
|
||||
LogFailureF("Unable to discard queue data file %s: %d",
|
||||
client->path, errno);
|
||||
}
|
||||
}
|
||||
if (client->entry.work) {
|
||||
FCLOSE_CHECK(client->entry.work);
|
||||
client->entry.work = NULL;
|
||||
|
||||
if (client->entry.workQueue[0] != '\0') {
|
||||
QueueFormat(client->path,"%s/w%s.%s",Conf.spoolPath, client->entry.workQueue + 4, client->entry.workQueue);
|
||||
UNLINK_CHECK(client->path);
|
||||
}
|
||||
if (client->entry.workQueue[0] != '\0') {
|
||||
QueueFormat(client->path,"%s/w%s.%s",Conf.spoolPath, client->entry.workQueue + 4, client->entry.workQueue);
|
||||
if (unlink(client->path) != 0 && errno != ENOENT) {
|
||||
LogFailureF("Unable to discard queue work file %s: %d",
|
||||
client->path, errno);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
client->entry.id = 0;
|
||||
|
||||
ccode = ConnWrite(client->conn, MSG1000OK, sizeof(MSG1000OK) - 1);
|
||||
return(ccode);
|
||||
client->entry.target = 0;
|
||||
client->entry.workQueue[0] = '\0';
|
||||
}
|
||||
|
||||
int
|
||||
CommandQabrt(void *param)
|
||||
{
|
||||
QueueClient *client = (QueueClient *)param;
|
||||
|
||||
DiscardClientEntry(client);
|
||||
return(ConnWrite(client->conn, MSG1000OK, sizeof(MSG1000OK) - 1));
|
||||
}
|
||||
|
||||
int
|
||||
@@ -3375,7 +3398,15 @@ CommandQcrea(void *param)
|
||||
QueueFormat(client->path, "%s/c%07lx.in", Conf.spoolPath, id);
|
||||
FOPEN_CHECK(client->entry.control, client->path, "wb");
|
||||
if (client->entry.control) {
|
||||
fprintf(client->entry.control, QUEUES_DATE"%" PRIdMAX "\r\n", (intmax_t)time(NULL));
|
||||
if (BongoQueueFilePrintf(
|
||||
client->entry.control, QUEUES_DATE"%" PRIdMAX "\r\n",
|
||||
(intmax_t)time(NULL)) != 0 ||
|
||||
BongoQueueFileFlush(client->entry.control) != 0) {
|
||||
FCLOSE_CHECK(client->entry.control);
|
||||
unlink(client->path);
|
||||
return(ConnWrite(client->conn, MSG5221SPACELOW,
|
||||
sizeof(MSG5221SPACELOW) - 1));
|
||||
}
|
||||
} else {
|
||||
return(ConnWrite(client->conn, MSG5221SPACELOW, sizeof(MSG5221SPACELOW) - 1));
|
||||
}
|
||||
@@ -4184,18 +4215,34 @@ CommandQrun(void *param)
|
||||
/* QRUN[ <queue>-<id>] */
|
||||
if (*ptr == '\0') {
|
||||
if (client->entry.control && client->entry.data && client->entry.id) {
|
||||
FCLOSE_CHECK(client->entry.data);
|
||||
client->entry.data = NULL;
|
||||
int dataStatus = BongoQueueFileFinish(&client->entry.data);
|
||||
int controlStatus =
|
||||
BongoQueueFileFinish(&client->entry.control);
|
||||
|
||||
FCLOSE_CHECK(client->entry.control);
|
||||
client->entry.control = NULL;
|
||||
if (dataStatus != 0 || controlStatus != 0) {
|
||||
Log(LOG_ERROR,
|
||||
"Could not flush queue entry %07lx: %s; "
|
||||
"submission rejected",
|
||||
client->entry.id, strerror(errno));
|
||||
DiscardClientEntry(client);
|
||||
return(ConnWrite(client->conn, MSG5221SPACELOW,
|
||||
sizeof(MSG5221SPACELOW) - 1));
|
||||
}
|
||||
} else {
|
||||
return(ConnWrite(client->conn, MSG4000CANTUNLOCKENTRY, sizeof(MSG4000CANTUNLOCKENTRY) - 1));
|
||||
}
|
||||
|
||||
QueueFormat(client->path,"%s/c%07lx.in",Conf.spoolPath, client->entry.id);
|
||||
QueueFormat(path, "%s/c%07lx.%03ld", Conf.spoolPath, client->entry.id, client->entry.target);
|
||||
RENAME_CHECK(client->path, path);
|
||||
if (rename(client->path, path) != 0) {
|
||||
Log(LOG_ERROR,
|
||||
"Could not commit queue entry %07lx: %s; "
|
||||
"submission rejected",
|
||||
client->entry.id, strerror(errno));
|
||||
DiscardClientEntry(client);
|
||||
return(ConnWrite(client->conn, MSG5221SPACELOW,
|
||||
sizeof(MSG5221SPACELOW) - 1));
|
||||
}
|
||||
|
||||
XplSafeIncrement(Queue.queuedLocal);
|
||||
|
||||
@@ -4571,7 +4618,13 @@ CommandQstorCal(void *param)
|
||||
/* QSTOR CAL <recipient>[ <calendar name>][ <routing envelope flags>] */
|
||||
if ((*ptr++ == ' ')
|
||||
&& (!isspace(*ptr))) {
|
||||
fprintf(client->entry.control, QUEUES_CALENDAR_LOCAL"%s\r\n", ptr);
|
||||
if (BongoQueueFilePrintf(
|
||||
client->entry.control, QUEUES_CALENDAR_LOCAL"%s\r\n",
|
||||
ptr) != 0) {
|
||||
DiscardClientEntry(client);
|
||||
return(ConnWrite(client->conn, MSG5221SPACELOW,
|
||||
sizeof(MSG5221SPACELOW) - 1));
|
||||
}
|
||||
|
||||
return(ConnWrite(client->conn, MSG1000OK, sizeof(MSG1000OK) - 1));
|
||||
}
|
||||
@@ -4594,7 +4647,12 @@ CommandQstorFlags(void *param)
|
||||
/* QSTOR FLAGS <value> */
|
||||
if ((*ptr++ == ' ')
|
||||
&& (!isspace(*ptr))) {
|
||||
fprintf(client->entry.control, QUEUES_FLAGS"%s\r\n", ptr);
|
||||
if (BongoQueueFilePrintf(
|
||||
client->entry.control, QUEUES_FLAGS"%s\r\n", ptr) != 0) {
|
||||
DiscardClientEntry(client);
|
||||
return(ConnWrite(client->conn, MSG5221SPACELOW,
|
||||
sizeof(MSG5221SPACELOW) - 1));
|
||||
}
|
||||
|
||||
return(ConnWrite(client->conn, MSG1000OK, sizeof(MSG1000OK) - 1));
|
||||
}
|
||||
@@ -4616,7 +4674,12 @@ CommandQstorFrom(void *param)
|
||||
/* QSTOR FROM <sender> <authenticated sender | -> */
|
||||
if ((*ptr++ == ' ')
|
||||
&& (!isspace(*ptr))) {
|
||||
fprintf(client->entry.control, QUEUES_FROM"%s\r\n", ptr);
|
||||
if (BongoQueueFilePrintf(
|
||||
client->entry.control, QUEUES_FROM"%s\r\n", ptr) != 0) {
|
||||
DiscardClientEntry(client);
|
||||
return(ConnWrite(client->conn, MSG5221SPACELOW,
|
||||
sizeof(MSG5221SPACELOW) - 1));
|
||||
}
|
||||
|
||||
return(ConnWrite(client->conn, MSG1000OK, sizeof(MSG1000OK) - 1));
|
||||
}
|
||||
@@ -4639,7 +4702,13 @@ CommandQstorLocal(void *param)
|
||||
/* QSTOR LOCAL <recipient> <original recipient> <routing envelope flags> */
|
||||
if ((*ptr++ == ' ')
|
||||
&& (!isspace(*ptr))) {
|
||||
fprintf(client->entry.control, QUEUES_RECIP_LOCAL"%s\r\n", ptr);
|
||||
if (BongoQueueFilePrintf(
|
||||
client->entry.control, QUEUES_RECIP_LOCAL"%s\r\n",
|
||||
ptr) != 0) {
|
||||
DiscardClientEntry(client);
|
||||
return(ConnWrite(client->conn, MSG5221SPACELOW,
|
||||
sizeof(MSG5221SPACELOW) - 1));
|
||||
}
|
||||
|
||||
return(ConnWrite(client->conn, MSG1000OK, sizeof(MSG1000OK) - 1));
|
||||
}
|
||||
@@ -4678,15 +4747,19 @@ CommandQstorMessage(void *param)
|
||||
}
|
||||
|
||||
MsgGetRFC822Date(-1, 0, TimeBuf);
|
||||
fprintf(client->entry.data,
|
||||
"Received: from %s (%d.%d.%d.%d) by %s\r\n\twith NMAP (bongoqueue Agent); %s\r\n",
|
||||
BongoGlobals.hostname, /* FIXME: this should eventually be the remote hsotname of the agent */
|
||||
if (BongoQueueFilePrintf(
|
||||
client->entry.data,
|
||||
"Received: from %s (%d.%d.%d.%d) by %s\r\n"
|
||||
"\twith NMAP (bongoqueue Agent); %s\r\n",
|
||||
BongoGlobals.hostname,
|
||||
client->conn->socketAddress.sin_addr.s_net,
|
||||
client->conn->socketAddress.sin_addr.s_host,
|
||||
client->conn->socketAddress.sin_addr.s_lh,
|
||||
client->conn->socketAddress.sin_addr.s_impno,
|
||||
BongoGlobals.hostname,
|
||||
TimeBuf);
|
||||
BongoGlobals.hostname, TimeBuf) != 0) {
|
||||
DiscardClientEntry(client);
|
||||
return -1;
|
||||
}
|
||||
/*
|
||||
fprintf(client->entry.data,
|
||||
"Received: from %d.%d.%d.%d [%d.%d.%d.%d] by %s\r\n\twith NMAP (%s); %s\r\n",
|
||||
@@ -4710,8 +4783,14 @@ CommandQstorMessage(void *param)
|
||||
ccode = ConnReadToFileUntilEOS(client->conn, client->entry.data);
|
||||
}
|
||||
|
||||
if (ccode != -1) {
|
||||
if (ccode != -1 && BongoQueueFileFlush(client->entry.data) == 0) {
|
||||
ccode = ConnWrite(client->conn, MSG1000OK, sizeof(MSG1000OK) - 1);
|
||||
} else {
|
||||
DiscardClientEntry(client);
|
||||
if (ccode != -1) {
|
||||
ccode = ConnWrite(client->conn, MSG5221SPACELOW,
|
||||
sizeof(MSG5221SPACELOW) - 1);
|
||||
}
|
||||
}
|
||||
|
||||
return(ccode);
|
||||
@@ -4732,7 +4811,11 @@ CommandQstorRaw(void *param)
|
||||
/* QSTOR RAW <value> */
|
||||
if ((*ptr++ == ' ')
|
||||
&& (!isspace(*ptr))) {
|
||||
fprintf(client->entry.control,"%s\r\n", ptr);
|
||||
if (BongoQueueFilePrintf(client->entry.control, "%s\r\n", ptr) != 0) {
|
||||
DiscardClientEntry(client);
|
||||
return(ConnWrite(client->conn, MSG5221SPACELOW,
|
||||
sizeof(MSG5221SPACELOW) - 1));
|
||||
}
|
||||
|
||||
return(ConnWrite(client->conn, MSG1000OK, sizeof(MSG1000OK) - 1));
|
||||
}
|
||||
@@ -4755,7 +4838,13 @@ CommandQstorTo(void *param)
|
||||
/* QSTOR TO <value> */
|
||||
if ((*ptr++ == ' ')
|
||||
&& (!isspace(*ptr))) {
|
||||
fprintf(client->entry.control,QUEUES_RECIP_REMOTE"%s\r\n", ptr);
|
||||
if (BongoQueueFilePrintf(
|
||||
client->entry.control, QUEUES_RECIP_REMOTE"%s\r\n",
|
||||
ptr) != 0) {
|
||||
DiscardClientEntry(client);
|
||||
return(ConnWrite(client->conn, MSG5221SPACELOW,
|
||||
sizeof(MSG5221SPACELOW) - 1));
|
||||
}
|
||||
|
||||
return(ConnWrite(client->conn, MSG1000OK, sizeof(MSG1000OK) - 1));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
/****************************************************************************
|
||||
* <Novell-copyright>
|
||||
* Copyright (c) 2001 Novell, Inc. All Rights Reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of version 2 of the GNU General Public License
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, contact Novell, Inc.
|
||||
*
|
||||
* To contact Novell about this file by physical or electronic mail, you
|
||||
* may find current contact information at www.novell.com.
|
||||
* </Novell-copyright>
|
||||
****************************************************************************/
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "spool-file.h"
|
||||
|
||||
int
|
||||
BongoQueueFileFlush(FILE *stream)
|
||||
{
|
||||
if (stream == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
if (ferror(stream) || fflush(stream) != 0 || ferror(stream)) {
|
||||
if (errno == 0) errno = EIO;
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
BongoQueueFileFinish(FILE **stream)
|
||||
{
|
||||
FILE *openStream;
|
||||
int savedError = 0;
|
||||
|
||||
if (stream == NULL || *stream == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
openStream = *stream;
|
||||
*stream = NULL;
|
||||
if (BongoQueueFileFlush(openStream) != 0) savedError = errno;
|
||||
if (fclose(openStream) != 0 && savedError == 0) savedError = errno;
|
||||
if (savedError != 0) {
|
||||
errno = savedError;
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
BongoQueueFileCommit(FILE **stream, const char *workPath,
|
||||
const char *destinationPath)
|
||||
{
|
||||
int savedError;
|
||||
|
||||
if (stream == NULL || *stream == NULL || workPath == NULL ||
|
||||
destinationPath == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
if (BongoQueueFileFinish(stream) != 0) {
|
||||
savedError = errno;
|
||||
unlink(workPath);
|
||||
errno = savedError;
|
||||
return -1;
|
||||
}
|
||||
if (rename(workPath, destinationPath) != 0) {
|
||||
savedError = errno;
|
||||
unlink(workPath);
|
||||
errno = savedError;
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
BongoQueueFileWrite(FILE *stream, const void *data, size_t length)
|
||||
{
|
||||
if (stream == NULL || (data == NULL && length != 0U)) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
if (length != 0U && fwrite(data, 1U, length, stream) != length) {
|
||||
if (errno == 0) errno = EIO;
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
BongoQueueFileVPrintf(FILE *stream, const char *format, va_list arguments)
|
||||
{
|
||||
if (stream == NULL || format == NULL) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
if (vfprintf(stream, format, arguments) < 0 || ferror(stream)) {
|
||||
if (errno == 0) errno = EIO;
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
BongoQueueFilePrintf(FILE *stream, const char *format, ...)
|
||||
{
|
||||
va_list arguments;
|
||||
int result;
|
||||
|
||||
va_start(arguments, format);
|
||||
result = BongoQueueFileVPrintf(stream, format, arguments);
|
||||
va_end(arguments);
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/****************************************************************************
|
||||
* <Novell-copyright>
|
||||
* Copyright (c) 2001 Novell, Inc. All Rights Reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of version 2 of the GNU General Public License
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, contact Novell, Inc.
|
||||
*
|
||||
* To contact Novell about this file by physical or electronic mail, you
|
||||
* may find current contact information at www.novell.com.
|
||||
* </Novell-copyright>
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef BONGO_QUEUE_SPOOL_FILE_H
|
||||
#define BONGO_QUEUE_SPOOL_FILE_H
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
|
||||
int BongoQueueFileFlush(FILE *stream);
|
||||
int BongoQueueFileFinish(FILE **stream);
|
||||
int BongoQueueFileCommit(FILE **stream, const char *workPath,
|
||||
const char *destinationPath);
|
||||
int BongoQueueFileWrite(FILE *stream, const void *data, size_t length);
|
||||
int BongoQueueFileVPrintf(FILE *stream, const char *format,
|
||||
va_list arguments);
|
||||
int BongoQueueFilePrintf(FILE *stream, const char *format, ...);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,104 @@
|
||||
/****************************************************************************
|
||||
* <Novell-copyright>
|
||||
* Copyright (c) 2001 Novell, Inc. All Rights Reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of version 2 of the GNU General Public License
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, contact Novell, Inc.
|
||||
*
|
||||
* To contact Novell about this file by physical or electronic mail, you
|
||||
* may find current contact information at www.novell.com.
|
||||
* </Novell-copyright>
|
||||
****************************************************************************/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "spool-file.h"
|
||||
|
||||
#define CHECK(condition) \
|
||||
do { \
|
||||
if (!(condition)) { \
|
||||
fprintf(stderr, "check failed at line %d: %s\n", \
|
||||
__LINE__, #condition); \
|
||||
return 1; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
static int
|
||||
ReadText(const char *path, char *buffer, size_t size)
|
||||
{
|
||||
FILE *stream = fopen(path, "rb");
|
||||
size_t length;
|
||||
|
||||
if (stream == NULL) return -1;
|
||||
length = fread(buffer, 1U, size - 1U, stream);
|
||||
buffer[length] = '\0';
|
||||
return fclose(stream);
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
char directory[] = "/tmp/bongo-queue-spool-test.XXXXXX";
|
||||
char destination[256];
|
||||
char missingDestination[256];
|
||||
char work[256];
|
||||
char buffer[32];
|
||||
FILE *stream;
|
||||
|
||||
CHECK(mkdtemp(directory) != NULL);
|
||||
snprintf(destination, sizeof(destination), "%s/control", directory);
|
||||
snprintf(work, sizeof(work), "%s/work", directory);
|
||||
snprintf(missingDestination, sizeof(missingDestination),
|
||||
"%s/missing/control", directory);
|
||||
|
||||
stream = fopen(destination, "wb");
|
||||
CHECK(stream != NULL);
|
||||
CHECK(BongoQueueFileWrite(stream, "original", 8U) == 0);
|
||||
CHECK(BongoQueueFileFinish(&stream) == 0);
|
||||
CHECK(stream == NULL);
|
||||
|
||||
stream = fopen(work, "wb");
|
||||
CHECK(stream != NULL);
|
||||
CHECK(BongoQueueFilePrintf(stream, "%s", "replacement") == 0);
|
||||
CHECK(BongoQueueFileCommit(&stream, work, destination) == 0);
|
||||
CHECK(stream == NULL);
|
||||
CHECK(ReadText(destination, buffer, sizeof(buffer)) == 0);
|
||||
CHECK(strcmp(buffer, "replacement") == 0);
|
||||
|
||||
stream = fopen(work, "wb");
|
||||
CHECK(stream != NULL);
|
||||
CHECK(BongoQueueFileWrite(stream, "discard", 7U) == 0);
|
||||
CHECK(BongoQueueFileCommit(
|
||||
&stream, work, missingDestination) != 0);
|
||||
CHECK(access(work, F_OK) != 0);
|
||||
CHECK(ReadText(destination, buffer, sizeof(buffer)) == 0);
|
||||
CHECK(strcmp(buffer, "replacement") == 0);
|
||||
|
||||
if (access("/dev/full", W_OK) == 0) {
|
||||
CHECK(symlink("/dev/full", work) == 0);
|
||||
stream = fopen(work, "wb");
|
||||
CHECK(stream != NULL);
|
||||
CHECK(BongoQueueFilePrintf(stream, "%s", "must fail") == 0);
|
||||
CHECK(BongoQueueFileCommit(&stream, work, destination) != 0);
|
||||
CHECK(stream == NULL);
|
||||
CHECK(access(work, F_OK) != 0);
|
||||
CHECK(ReadText(destination, buffer, sizeof(buffer)) == 0);
|
||||
CHECK(strcmp(buffer, "replacement") == 0);
|
||||
}
|
||||
|
||||
CHECK(unlink(destination) == 0);
|
||||
CHECK(rmdir(directory) == 0);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user