Linux: implement pathCopy and fileRename, and fix three string functions - #86
Merged
Conversation
Both were stubs returning false on the Unix back-end while Windows and macOS have had real ones all along, and a copy that never happens is not something a caller can see: pathCopy returns a bool that almost nobody checks, so the work simply did not get done and everything downstream blamed the missing files. That reaches further than it sounds. The Project Manager stamps a new project out of a template with a recursive pathCopy, and the editor's new project dialog copies the theme library the same way, so creating a project on Linux produced a folder with nothing in it. AppCore and the Gui Editor both give a theme its own copy of the stock cursor art, which is why the Gui Editor's cursor pane had no art to measure. fileRename is not exposed to script at all, but ModuleManager renames a module definition with it and ZipArchive swaps a rebuilt archive into place, and both were quietly failing. pathCopy takes a file or a whole tree, since both callers exist. Paths are used as given rather than sent through MungePath into the pref directory, matching isFile, isDirectory and fileDelete beside it: every caller builds an absolute path and then asks isFile whether the copy arrived, so a copy routed elsewhere would read as a failure. The directories above a destination are made on that same raw path for the same reason -- Platform::createPath would munge a relative one into the pref directory and leave the open to fail on a parent it had just created somewhere else. Details worth keeping: the mode is carried across so a copied executable is still executable; a copy that fails partway is unlinked rather than left for the next run to mistake for good art; the recursion asks isDirectory rather than trusting dirent::d_type, which is DT_UNKNOWN on filesystems that do not carry the kind; and a tree is refused if the destination is inside it, which would otherwise recurse until the path outgrew MaxPath. Platform::isSubDirectory looks like it would answer that last question and does not -- it matches a bare child name against the parent's entries, not one path inside another. fileRename is rename(2) with a copy-and-delete fallback on EXDEV, because the pref directory and the game directory are not always on one filesystem. dPathCopy goes: it was dead code, and it returned CopyFile's error flag as though it were a success flag, so anyone who had reached for it would have got the answer backwards. Covered by PlatformFileIOTests.PathCopyAndRename, which is also the only way to reach fileRename, having no script binding. The three failures left in PlatformStringTests on Linux -- dStrcatl, dStrrev and dItoa -- are separate pre-existing bugs in x86UNIXStrings.cc and are not touched here. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three bugs in the Unix string back-end, each failing its own test in PlatformStringTests on every Linux run since the tests were written. dStrcatl walked one byte too far looking for the end of dst. "while (dstSize && *p++)" increments on the iteration that ends the loop too, so p came to rest past the terminator rather than on it and the append landed in the wrong place: "Garage" and "Games" concatenated to "Garage\0Games", which reads back as "Garage". The test and the pointer walk now agree. dStrrev reversed nothing. The loop read "x < 1" where it meant "x < l", so it ran a single iteration and swapped only the first and last character -- "GarageGames" came back as "sarageGameG". The corrected bound also keeps the XOR swap off the middle character of an odd-length string, which it would zero rather than leave alone, and off str[-1] for an empty one. dItoa never terminated the string it had just built. dStrrev measures with dStrlen, so it read past the digits into whatever the caller's buffer happened to hold and reversed that: dItoa(16384) produced "\x7F83614". Terminating before the reverse is what the K&R original this is copied from does. While here, the digits are taken off an unsigned copy, because the old "n = -n" is undefined for S32_MIN -- the one input that could not be printed at all. None of this is exotic; it is the sort of thing that survives because the Windows and macOS back-ends have their own copies of these functions and nobody runs the unit tests on Linux. PlatformStringTests now passes, and with the copy work in the preceding commit the whole suite is green on Linux for the first time: 88 tests, no failures. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Five fixes to the Unix platform back-end, all of them things the other two back-ends have had working all along. Found by running the
tests/suites and the GoogleTest unit tests on Linux; none of this is specific to the branch that surfaced it.Platform::pathCopyandPlatform::fileRenamewere stubsBoth were
return false;inplatformX86UNIX/x86UNIXFileio.ccwhile Windows (winFileio.cc) and macOS (osxFileIO.mm) implement them.pathCopyreturns a bool that almost nobody checks, so the work simply did not happen and everything downstream blamed the missing files.What that broke on Linux:
pathCopy(ProjectGamePanel.cs:100), and the new-project dialog copies the theme library the same way (NewProjectDialog.cs:146).themes.cs:130,GuiProfileEditorLibrary.cs:181).fileRenamefailed silently inModuleManager(renaming a module definition) andZipArchive(swapping a rebuilt archive into place). It has no script binding, so nothing script-side could see it either.pathCopynow takes a file or a whole tree, since both callers exist. Paths are used as given rather than routed throughMungePathinto the pref directory, matchingisFile/isDirectory/fileDeletebeside it — every caller builds an absolute path and then asksisFilewhether the copy arrived, so a copy landing elsewhere would read as a failure. Parent directories are created on that same raw path, becausePlatform::createPathwould munge a relative one into the pref directory and leave theopento fail on a parent it had just made somewhere else.Details worth keeping: the mode is carried across so a copied executable stays executable; a copy that fails partway is unlinked rather than left for the next run to mistake for real content; the recursion asks
isDirectoryrather than trustingdirent::d_type, which isDT_UNKNOWNon filesystems that do not carry the kind; and a tree is refused if the destination is inside it, which would otherwise recurse until the path outgrewMaxPath.Platform::isSubDirectorylooks like it would answer that last question and does not — it matches a bare child name against the parent's entries, not one path inside another.fileRenameisrename(2)with a copy-and-delete fallback onEXDEV, since the pref directory and the game directory are not always on one filesystem.Also removes
dPathCopy: dead code that returnedCopyFile's error flag as though it were a success flag, so anyone reaching for it would have got the answer backwards.Three bugs in
x86UNIXStrings.ccEach was failing its own test in
PlatformStringTestson every Linux run.dStrcatlwalked one byte too far looking for the end ofdst.while (dstSize && *p++)increments on the iteration that ends the loop too, sopcame to rest past the terminator and the append landed in the wrong place:"Garage"+"Games"produced"Garage\0Games", which reads back as"Garage".dStrrevreversed nothing. The loop readx < 1where it meantx < l, so it ran once and swapped only the first and last character —"GarageGames"came back as"sarageGameG". The corrected bound also keeps the XOR swap off the middle character of an odd-length string (which it would zero) and offstr[-1]for an empty one.dItoanever terminated the string it had just built, sodStrrev'sdStrlenread past the digits into whatever the caller's buffer held and reversed that:dItoa(16384)produced"\x7F83614". Terminating before the reverse is what the K&R original this is copied from does. While there, the digits are taken off an unsigned copy, because the oldn = -nis undefined forS32_MIN— the one input that could not be printed at all.These survive because Windows and macOS have their own copies of these functions and nobody runs the unit tests on Linux.
Testing
PlatformFileIOTests.PathCopyAndRenamecovers file copy,nooverwritein both directions, a missing source leaving nothing behind, recursive tree copy, refusing a copy into itself, and rename. It is also the only way to reachfileRename, which has no script binding.runAllUnitTests()on Linux: 88 tests, 0 failures. Previously 3 failed.tests/run.sh: all 14 smoke suites green, no regressions.cursorPanefrom 52 passed / 17 failed to 69 passed, 0 failed, and the full suite to 37/37.Read the diff with whitespace shown if it looks odd — both files are CRLF in-tree and stay that way.
🤖 Generated with Claude Code