PR #2125: fix off-by-one truncation detection in VADoRawLog and DoRawLog

Imported from GitHub PR https://github.com/abseil/abseil-cpp/pull/2125

treat n == size as truncation in VADoRawLog and DoRawLog: vsnprintf writes only size-1 chars at that boundary, so an exactly-buffer-filling raw log message was losing its last byte and being emitted without the `(message truncated)` marker or trailing newline, letting a following line concatenate onto it.
Merge 05db3936acd6d53167e224520fb5b55764e48c58 into d522178b54087d2c3855484c1899ce99ad278169

Merging this change closes #2125

PiperOrigin-RevId: 964803816
diff --git a/absl/base/internal/raw_logging.cc b/absl/base/internal/raw_logging.cc
index 848e69f..f705a38 100644
--- a/absl/base/internal/raw_logging.cc
+++ b/absl/base/internal/raw_logging.cc
@@ -95,7 +95,7 @@
   if (*size < 0) return false;
   int n = vsnprintf(*buf, static_cast<size_t>(*size), format, ap);
   bool result = true;
-  if (n < 0 || n > *size) {
+  if (n < 0 || n >= *size) {
     result = false;
     if (static_cast<size_t>(*size) > sizeof(kTruncated)) {
       n = *size - static_cast<int>(sizeof(kTruncated));
@@ -125,7 +125,7 @@
   va_start(ap, format);
   int n = vsnprintf(*buf, static_cast<size_t>(*size), format, ap);
   va_end(ap);
-  if (n < 0 || n > *size) return false;
+  if (n < 0 || n >= *size) return false;
   *size -= n;
   *buf += n;
   return true;
diff --git a/absl/base/raw_logging_test.cc b/absl/base/raw_logging_test.cc
index 48158c0..6d76d7b 100644
--- a/absl/base/raw_logging_test.cc
+++ b/absl/base/raw_logging_test.cc
@@ -18,10 +18,17 @@
 
 #include "absl/base/internal/raw_logging.h"
 
+#include <cstddef>
 #include <string>
-#include <tuple>
+
+#if (defined(__unix__) || (defined(__APPLE__) && defined(__MACH__))) && \
+    !defined(__EMSCRIPTEN__)
+#include <unistd.h>
+
+#endif
 
 #include "gtest/gtest.h"
+#include "absl/strings/match.h"
 #include "absl/strings/str_cat.h"
 
 namespace {
@@ -75,6 +82,47 @@
                             kExpectedDeathOutput);
 }
 
+// Raw logging writes to STDERR_FILENO via write()/syscall on POSIX platforms,
+// so the truncation path can be exercised directly by capturing stderr, without
+// relying on death tests.
+#if (defined(__unix__) || (defined(__APPLE__) && defined(__MACH__))) && \
+    !defined(__EMSCRIPTEN__)
+TEST(RawLoggingTest, TruncationMarkerAtExactBufferBoundary) {
+  if (!absl::raw_log_internal::RawLoggingFullySupported()) {
+    GTEST_SKIP() << "Raw logging output is not supported on this platform.";
+  }
+  // kLogBufSize in raw_logging.cc.
+  constexpr int kLogBufSize = 3000;
+
+  // The space left for the message depends on the log prefix, which a prefix
+  // hook can change, so try every length up to the buffer size.  Each message
+  // must come out either whole or ending in the truncation marker.
+  for (int len = 1; len <= kLogBufSize; ++len) {
+    int fds[2];
+    ASSERT_EQ(pipe(fds), 0);
+    int saved_stderr = dup(STDERR_FILENO);
+    ASSERT_NE(saved_stderr, -1);
+    ASSERT_NE(dup2(fds[1], STDERR_FILENO), -1);
+
+    const std::string msg(static_cast<size_t>(len), 'x');
+    ABSL_RAW_LOG(ERROR, "%s", msg.c_str());
+
+    ASSERT_NE(dup2(saved_stderr, STDERR_FILENO), -1);
+    close(saved_stderr);
+    close(fds[1]);
+    char buf[kLogBufSize + 64];
+    ssize_t n = read(fds[0], buf, sizeof(buf));
+    close(fds[0]);
+    ASSERT_GT(n, 0);
+    const std::string output(buf, static_cast<size_t>(n));
+
+    ASSERT_TRUE(absl::StrContains(output, msg) ||
+                absl::EndsWith(output, " ... (message truncated)\n"))
+        << "message length " << len;
+  }
+}
+#endif
+
 TEST(InternalLog, CompilationTest) {
   ABSL_INTERNAL_LOG(INFO, "Internal Log");
   std::string log_msg = "Internal Log";