Skip to content

Commit 14dadda

Browse files
summeroffclaude
andcommitted
fix: only apply Windows path-equivalence rules on Windows
claim_key folded backslashes into forward slashes on every platform. On POSIX a backslash is an ordinary filename character, so "a\b.mp4" and "a/b.mp4" are different files that would have compared equal -- a false collision, and a rename the caller never asked for. Case folding was already Windows-only; make the separator handling match and compare byte-exact elsewhere. The unit test asserted the Windows rules unconditionally and so failed on the macOS runners. Split it: Windows keeps the equivalence assertion, POSIX asserts the two paths stay distinct. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 8a494c0 commit 14dadda

2 files changed

Lines changed: 19 additions & 4 deletions

File tree

obs-studio-server/source/osn-file-output.cpp

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,20 @@
2929
// as a unit -- the manager's own lock is dropped when for_each returns, so it is not enough.
3030
static std::mutex s_filenameClaimMutex;
3131

32-
// Windows paths are case-insensitive and accept either separator, so compare on a folded key
33-
// while the claim itself keeps the exact string the muxer was given.
32+
// Windows paths are case-insensitive and accept either separator, so compare on a folded key while
33+
// the claim itself keeps the exact string the muxer was given. Elsewhere the comparison is exact:
34+
// on POSIX a backslash is an ordinary filename character, so folding it into a separator would make
35+
// two different files look like one.
3436
static std::string claim_key(const std::string &path)
3537
{
38+
#ifdef WIN32
3639
std::string key = path;
3740
std::replace(key.begin(), key.end(), '\\', '/');
38-
#ifdef WIN32
3941
std::transform(key.begin(), key.end(), key.begin(), [](unsigned char c) { return (char)tolower(c); });
40-
#endif
4142
return key;
43+
#else
44+
return path;
45+
#endif
4246
}
4347

4448
// Caller must hold s_filenameClaimMutex.

obs-studio-server/tests/test-osn-file-output.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,22 @@ TEST_CASE("FindBestFilename avoids a path claimed by another live output", "[fil
5858
REQUIRE(resolve(wanted, second.get(), false, true) == "C:/osn-test/2026-08-08 14-30-12 (2).mp4");
5959
}
6060

61+
#ifdef WIN32
62+
// Windows only: separator and case are not part of path identity there. On POSIX a backslash
63+
// is an ordinary filename character, so these really are different paths.
6164
SECTION("separator and case differences do not evade the check")
6265
{
6366
REQUIRE(resolve(wanted, first.get()) == wanted);
6467
REQUIRE(resolve("C:\\OSN-Test\\2026-08-08 14-30-12.mp4", second.get()) == "C:\\OSN-Test\\2026-08-08 14-30-12 (2).mp4");
6568
}
69+
#else
70+
SECTION("paths differing only by separator are distinct files")
71+
{
72+
REQUIRE(resolve(wanted, first.get()) == wanted);
73+
const std::string backslashed = "C:\\osn-test\\2026-08-08 14-30-12.mp4";
74+
REQUIRE(resolve(backslashed, second.get()) == backslashed);
75+
}
76+
#endif
6677

6778
SECTION("the name is reusable once the first output stops")
6879
{

0 commit comments

Comments
 (0)