├── .editorconfig ├── .gitattributes ├── .gitignore ├── .gitmodules ├── LICENSE ├── MINIXCompat.xcodeproj ├── project.pbxproj └── xcshareddata │ └── xcschemes │ └── MINIXCompat.xcscheme ├── MINIXCompat ├── MINIXCompat.1 ├── MINIXCompat.c ├── MINIXCompat.h ├── MINIXCompat_Emulation.c ├── MINIXCompat_Emulation.h ├── MINIXCompat_EmulationOps.c ├── MINIXCompat_Errors.c ├── MINIXCompat_Errors.h ├── MINIXCompat_Executable.c ├── MINIXCompat_Executable.h ├── MINIXCompat_Filesystem.c ├── MINIXCompat_Filesystem.h ├── MINIXCompat_Logging.c ├── MINIXCompat_Logging.h ├── MINIXCompat_Messages.c ├── MINIXCompat_Messages.h ├── MINIXCompat_Musashi.h ├── MINIXCompat_Processes.c ├── MINIXCompat_Processes.h ├── MINIXCompat_SysCalls.c ├── MINIXCompat_SysCalls.h └── MINIXCompat_Types.h ├── Makefile └── README.md /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_size = 4 6 | indent_style = space 7 | insert_final_newline = true 8 | tab_width = 4 9 | trim_trailing_whitespace = true 10 | 11 | [Makefile] 12 | indent_style = tab 13 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eschaton/MINIXCompat/34856670473e1214680766a7c5728bc957941516/.gitattributes -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.d 3 | MINIXCompat/MINIXCompat 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Musashi"] 2 | path = Musashi 3 | url = https://github.com/eschaton/Musashi.git 4 | branch = eschaton/MINIXCompat 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright © 2024 Christopher M. Hanson. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the “Software”), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /MINIXCompat.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 77; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 9F54062A2CD8429F0054DA28 /* m68k.h in Headers */ = {isa = PBXBuildFile; fileRef = 9F5405F22CD841880054DA28 /* m68k.h */; }; 11 | 9F54062B2CD8429F0054DA28 /* m68kconf.h in Headers */ = {isa = PBXBuildFile; fileRef = 9F5405F42CD841880054DA28 /* m68kconf.h */; }; 12 | 9F54062C2CD8429F0054DA28 /* m68kcpu.h in Headers */ = {isa = PBXBuildFile; fileRef = 9F5405F52CD841880054DA28 /* m68kcpu.h */; }; 13 | 9F54062D2CD8429F0054DA28 /* m68kmmu.h in Headers */ = {isa = PBXBuildFile; fileRef = 9F5405FA2CD841880054DA28 /* m68kmmu.h */; }; 14 | 9F54062F2CD842AE0054DA28 /* m68kcpu.c in Sources */ = {isa = PBXBuildFile; fileRef = 9F5405F62CD841880054DA28 /* m68kcpu.c */; }; 15 | 9F5406302CD842AE0054DA28 /* m68kdasm.c in Sources */ = {isa = PBXBuildFile; fileRef = 9F5405F72CD841880054DA28 /* m68kdasm.c */; }; 16 | 9F5682012CDAD9B8004A53B8 /* m68kmake.c in Sources */ = {isa = PBXBuildFile; fileRef = 9F5405F92CD841880054DA28 /* m68kmake.c */; }; 17 | 9F5682082CDADF65004A53B8 /* libMusashi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9F5406262CD8427C0054DA28 /* libMusashi.a */; }; 18 | 9F56821B2CDC93E2004A53B8 /* softfloat.c in Sources */ = {isa = PBXBuildFile; fileRef = 9F5405EC2CD841880054DA28 /* softfloat.c */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXContainerItemProxy section */ 22 | 9F5682032CDADB08004A53B8 /* PBXContainerItemProxy */ = { 23 | isa = PBXContainerItemProxy; 24 | containerPortal = 9F54F9712CD841200054DA28 /* Project object */; 25 | proxyType = 1; 26 | remoteGlobalIDString = 9F5681F92CDAD996004A53B8; 27 | remoteInfo = m68kmake; 28 | }; 29 | 9F5682052CDADF5F004A53B8 /* PBXContainerItemProxy */ = { 30 | isa = PBXContainerItemProxy; 31 | containerPortal = 9F54F9712CD841200054DA28 /* Project object */; 32 | proxyType = 1; 33 | remoteGlobalIDString = 9F5406252CD8427C0054DA28; 34 | remoteInfo = Musashi; 35 | }; 36 | /* End PBXContainerItemProxy section */ 37 | 38 | /* Begin PBXCopyFilesBuildPhase section */ 39 | 9F54F9772CD841200054DA28 /* Copy Man Page */ = { 40 | isa = PBXCopyFilesBuildPhase; 41 | buildActionMask = 2147483647; 42 | dstPath = "$(INSTALL_DIR)/../share/man/man1"; 43 | dstSubfolderSpec = 0; 44 | files = ( 45 | ); 46 | name = "Copy Man Page"; 47 | runOnlyForDeploymentPostprocessing = 1; 48 | }; 49 | 9F5681F82CDAD996004A53B8 /* CopyFiles */ = { 50 | isa = PBXCopyFilesBuildPhase; 51 | buildActionMask = 2147483647; 52 | dstPath = /usr/share/man/man1/; 53 | dstSubfolderSpec = 0; 54 | files = ( 55 | ); 56 | runOnlyForDeploymentPostprocessing = 1; 57 | }; 58 | /* End PBXCopyFilesBuildPhase section */ 59 | 60 | /* Begin PBXFileReference section */ 61 | 9F34BC042D0156BD00FB162C /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 62 | 9F34BC052D0156C800FB162C /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; 63 | 9F5405E82CD841880054DA28 /* mamesf.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = mamesf.h; sourceTree = ""; }; 64 | 9F5405E92CD841880054DA28 /* milieu.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = milieu.h; sourceTree = ""; }; 65 | 9F5405EB2CD841880054DA28 /* softfloat.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = softfloat.h; sourceTree = ""; }; 66 | 9F5405EC2CD841880054DA28 /* softfloat.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = softfloat.c; sourceTree = ""; }; 67 | 9F5405ED2CD841880054DA28 /* softfloat-macros */ = {isa = PBXFileReference; lastKnownFileType = text; path = "softfloat-macros"; sourceTree = ""; }; 68 | 9F5405EE2CD841880054DA28 /* softfloat-specialize */ = {isa = PBXFileReference; lastKnownFileType = text; path = "softfloat-specialize"; sourceTree = ""; }; 69 | 9F5405F22CD841880054DA28 /* m68k.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = m68k.h; sourceTree = ""; }; 70 | 9F5405F32CD841880054DA28 /* m68k_in.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = m68k_in.c; sourceTree = ""; }; 71 | 9F5405F42CD841880054DA28 /* m68kconf.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = m68kconf.h; sourceTree = ""; }; 72 | 9F5405F52CD841880054DA28 /* m68kcpu.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = m68kcpu.h; sourceTree = ""; }; 73 | 9F5405F62CD841880054DA28 /* m68kcpu.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = m68kcpu.c; sourceTree = ""; }; 74 | 9F5405F72CD841880054DA28 /* m68kdasm.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = m68kdasm.c; sourceTree = ""; }; 75 | 9F5405F82CD841880054DA28 /* m68kfpu.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = m68kfpu.c; sourceTree = ""; }; 76 | 9F5405F92CD841880054DA28 /* m68kmake.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = m68kmake.c; sourceTree = ""; }; 77 | 9F5405FA2CD841880054DA28 /* m68kmmu.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = m68kmmu.h; sourceTree = ""; }; 78 | 9F5406262CD8427C0054DA28 /* libMusashi.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libMusashi.a; sourceTree = BUILT_PRODUCTS_DIR; }; 79 | 9F54F9792CD841200054DA28 /* MINIXCompat */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = MINIXCompat; sourceTree = BUILT_PRODUCTS_DIR; }; 80 | 9F5681FA2CDAD996004A53B8 /* m68kmake */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = m68kmake; sourceTree = BUILT_PRODUCTS_DIR; }; 81 | /* End PBXFileReference section */ 82 | 83 | /* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ 84 | 9F1304E62CE1C5CB00D55B71 /* Exceptions for "MINIXCompat" folder in "MINIXCompat" target */ = { 85 | isa = PBXFileSystemSynchronizedBuildFileExceptionSet; 86 | membershipExceptions = ( 87 | MINIXCompat_EmulationOps.c, 88 | ); 89 | target = 9F54F9782CD841200054DA28 /* MINIXCompat */; 90 | }; 91 | 9F1304E72CE1C5CB00D55B71 /* Exceptions for "MINIXCompat" folder in "Musashi" target */ = { 92 | isa = PBXFileSystemSynchronizedBuildFileExceptionSet; 93 | membershipExceptions = ( 94 | MINIXCompat_EmulationOps.c, 95 | ); 96 | target = 9F5406252CD8427C0054DA28 /* Musashi */; 97 | }; 98 | /* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ 99 | 100 | /* Begin PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet section */ 101 | 9FBBC2A82CF53B1100B3F15A /* Exceptions for "MINIXCompat" folder in "Copy Man Page" phase from "MINIXCompat" target */ = { 102 | isa = PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet; 103 | buildPhase = 9F54F9772CD841200054DA28 /* Copy Man Page */; 104 | membershipExceptions = ( 105 | MINIXCompat.1, 106 | ); 107 | }; 108 | /* End PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet section */ 109 | 110 | /* Begin PBXFileSystemSynchronizedRootGroup section */ 111 | 9F54061F2CD8422D0054DA28 /* MINIXCompat */ = { 112 | isa = PBXFileSystemSynchronizedRootGroup; 113 | exceptions = ( 114 | 9F1304E62CE1C5CB00D55B71 /* Exceptions for "MINIXCompat" folder in "MINIXCompat" target */, 115 | 9FBBC2A82CF53B1100B3F15A /* Exceptions for "MINIXCompat" folder in "Copy Man Page" phase from "MINIXCompat" target */, 116 | 9F1304E72CE1C5CB00D55B71 /* Exceptions for "MINIXCompat" folder in "Musashi" target */, 117 | ); 118 | path = MINIXCompat; 119 | sourceTree = ""; 120 | }; 121 | /* End PBXFileSystemSynchronizedRootGroup section */ 122 | 123 | /* Begin PBXFrameworksBuildPhase section */ 124 | 9F5406242CD8427C0054DA28 /* Frameworks */ = { 125 | isa = PBXFrameworksBuildPhase; 126 | buildActionMask = 2147483647; 127 | files = ( 128 | ); 129 | runOnlyForDeploymentPostprocessing = 0; 130 | }; 131 | 9F54F9762CD841200054DA28 /* Frameworks */ = { 132 | isa = PBXFrameworksBuildPhase; 133 | buildActionMask = 2147483647; 134 | files = ( 135 | 9F5682082CDADF65004A53B8 /* libMusashi.a in Frameworks */, 136 | ); 137 | runOnlyForDeploymentPostprocessing = 0; 138 | }; 139 | 9F5681F72CDAD996004A53B8 /* Frameworks */ = { 140 | isa = PBXFrameworksBuildPhase; 141 | buildActionMask = 2147483647; 142 | files = ( 143 | ); 144 | runOnlyForDeploymentPostprocessing = 0; 145 | }; 146 | /* End PBXFrameworksBuildPhase section */ 147 | 148 | /* Begin PBXGroup section */ 149 | 9F5405EF2CD841880054DA28 /* softfloat */ = { 150 | isa = PBXGroup; 151 | children = ( 152 | 9F5405E82CD841880054DA28 /* mamesf.h */, 153 | 9F5405E92CD841880054DA28 /* milieu.h */, 154 | 9F5405EB2CD841880054DA28 /* softfloat.h */, 155 | 9F5405EC2CD841880054DA28 /* softfloat.c */, 156 | 9F5405ED2CD841880054DA28 /* softfloat-macros */, 157 | 9F5405EE2CD841880054DA28 /* softfloat-specialize */, 158 | ); 159 | path = softfloat; 160 | sourceTree = ""; 161 | }; 162 | 9F5405FD2CD841880054DA28 /* Musashi */ = { 163 | isa = PBXGroup; 164 | children = ( 165 | 9F5405EF2CD841880054DA28 /* softfloat */, 166 | 9F5405F22CD841880054DA28 /* m68k.h */, 167 | 9F5405F32CD841880054DA28 /* m68k_in.c */, 168 | 9F5405F42CD841880054DA28 /* m68kconf.h */, 169 | 9F5405F52CD841880054DA28 /* m68kcpu.h */, 170 | 9F5405F62CD841880054DA28 /* m68kcpu.c */, 171 | 9F5405F72CD841880054DA28 /* m68kdasm.c */, 172 | 9F5405F82CD841880054DA28 /* m68kfpu.c */, 173 | 9F5405F92CD841880054DA28 /* m68kmake.c */, 174 | 9F5405FA2CD841880054DA28 /* m68kmmu.h */, 175 | ); 176 | path = Musashi; 177 | sourceTree = ""; 178 | }; 179 | 9F54F9702CD841200054DA28 = { 180 | isa = PBXGroup; 181 | children = ( 182 | 9F34BC042D0156BD00FB162C /* README.md */, 183 | 9F34BC052D0156C800FB162C /* LICENSE */, 184 | 9F54061F2CD8422D0054DA28 /* MINIXCompat */, 185 | 9F5405FD2CD841880054DA28 /* Musashi */, 186 | 9F54F97A2CD841200054DA28 /* Products */, 187 | ); 188 | sourceTree = ""; 189 | }; 190 | 9F54F97A2CD841200054DA28 /* Products */ = { 191 | isa = PBXGroup; 192 | children = ( 193 | 9F54F9792CD841200054DA28 /* MINIXCompat */, 194 | 9F5406262CD8427C0054DA28 /* libMusashi.a */, 195 | 9F5681FA2CDAD996004A53B8 /* m68kmake */, 196 | ); 197 | name = Products; 198 | sourceTree = ""; 199 | }; 200 | /* End PBXGroup section */ 201 | 202 | /* Begin PBXHeadersBuildPhase section */ 203 | 9F54061A2CD8420F0054DA28 /* Headers */ = { 204 | isa = PBXHeadersBuildPhase; 205 | buildActionMask = 2147483647; 206 | files = ( 207 | ); 208 | runOnlyForDeploymentPostprocessing = 0; 209 | }; 210 | 9F5406222CD8427C0054DA28 /* Headers */ = { 211 | isa = PBXHeadersBuildPhase; 212 | buildActionMask = 2147483647; 213 | files = ( 214 | 9F54062A2CD8429F0054DA28 /* m68k.h in Headers */, 215 | 9F54062B2CD8429F0054DA28 /* m68kconf.h in Headers */, 216 | 9F54062C2CD8429F0054DA28 /* m68kcpu.h in Headers */, 217 | 9F54062D2CD8429F0054DA28 /* m68kmmu.h in Headers */, 218 | ); 219 | runOnlyForDeploymentPostprocessing = 0; 220 | }; 221 | /* End PBXHeadersBuildPhase section */ 222 | 223 | /* Begin PBXNativeTarget section */ 224 | 9F5406252CD8427C0054DA28 /* Musashi */ = { 225 | isa = PBXNativeTarget; 226 | buildConfigurationList = 9F5406272CD8427C0054DA28 /* Build configuration list for PBXNativeTarget "Musashi" */; 227 | buildPhases = ( 228 | 9F5682022CDADAA0004A53B8 /* Generate 68000 Core */, 229 | 9F5406222CD8427C0054DA28 /* Headers */, 230 | 9F5406232CD8427C0054DA28 /* Sources */, 231 | 9F5406242CD8427C0054DA28 /* Frameworks */, 232 | ); 233 | buildRules = ( 234 | ); 235 | dependencies = ( 236 | 9F5682042CDADB08004A53B8 /* PBXTargetDependency */, 237 | ); 238 | name = Musashi; 239 | packageProductDependencies = ( 240 | ); 241 | productName = Musashi; 242 | productReference = 9F5406262CD8427C0054DA28 /* libMusashi.a */; 243 | productType = "com.apple.product-type.library.static"; 244 | }; 245 | 9F54F9782CD841200054DA28 /* MINIXCompat */ = { 246 | isa = PBXNativeTarget; 247 | buildConfigurationList = 9F54F9802CD841200054DA28 /* Build configuration list for PBXNativeTarget "MINIXCompat" */; 248 | buildPhases = ( 249 | 9F54061A2CD8420F0054DA28 /* Headers */, 250 | 9F54F9752CD841200054DA28 /* Sources */, 251 | 9F54F9762CD841200054DA28 /* Frameworks */, 252 | 9F54F9772CD841200054DA28 /* Copy Man Page */, 253 | ); 254 | buildRules = ( 255 | ); 256 | dependencies = ( 257 | 9F5682062CDADF5F004A53B8 /* PBXTargetDependency */, 258 | ); 259 | fileSystemSynchronizedGroups = ( 260 | 9F54061F2CD8422D0054DA28 /* MINIXCompat */, 261 | ); 262 | name = MINIXCompat; 263 | packageProductDependencies = ( 264 | ); 265 | productName = MINIXCompat; 266 | productReference = 9F54F9792CD841200054DA28 /* MINIXCompat */; 267 | productType = "com.apple.product-type.tool"; 268 | }; 269 | 9F5681F92CDAD996004A53B8 /* m68kmake */ = { 270 | isa = PBXNativeTarget; 271 | buildConfigurationList = 9F5682002CDAD996004A53B8 /* Build configuration list for PBXNativeTarget "m68kmake" */; 272 | buildPhases = ( 273 | 9F5681F62CDAD996004A53B8 /* Sources */, 274 | 9F5681F72CDAD996004A53B8 /* Frameworks */, 275 | 9F5681F82CDAD996004A53B8 /* CopyFiles */, 276 | ); 277 | buildRules = ( 278 | ); 279 | dependencies = ( 280 | ); 281 | name = m68kmake; 282 | packageProductDependencies = ( 283 | ); 284 | productName = m68kmake; 285 | productReference = 9F5681FA2CDAD996004A53B8 /* m68kmake */; 286 | productType = "com.apple.product-type.tool"; 287 | }; 288 | /* End PBXNativeTarget section */ 289 | 290 | /* Begin PBXProject section */ 291 | 9F54F9712CD841200054DA28 /* Project object */ = { 292 | isa = PBXProject; 293 | attributes = { 294 | BuildIndependentTargetsInParallel = 1; 295 | LastUpgradeCheck = 1610; 296 | ORGANIZATIONNAME = "Christopher M. Hanson"; 297 | TargetAttributes = { 298 | 9F5406252CD8427C0054DA28 = { 299 | CreatedOnToolsVersion = 16.1; 300 | }; 301 | 9F54F9782CD841200054DA28 = { 302 | CreatedOnToolsVersion = 16.1; 303 | }; 304 | 9F5681F92CDAD996004A53B8 = { 305 | CreatedOnToolsVersion = 16.1; 306 | }; 307 | }; 308 | }; 309 | buildConfigurationList = 9F54F9742CD841200054DA28 /* Build configuration list for PBXProject "MINIXCompat" */; 310 | developmentRegion = en; 311 | hasScannedForEncodings = 0; 312 | knownRegions = ( 313 | en, 314 | Base, 315 | ); 316 | mainGroup = 9F54F9702CD841200054DA28; 317 | minimizedProjectReferenceProxies = 1; 318 | preferredProjectObjectVersion = 77; 319 | productRefGroup = 9F54F97A2CD841200054DA28 /* Products */; 320 | projectDirPath = ""; 321 | projectRoot = ""; 322 | targets = ( 323 | 9F54F9782CD841200054DA28 /* MINIXCompat */, 324 | 9F5406252CD8427C0054DA28 /* Musashi */, 325 | 9F5681F92CDAD996004A53B8 /* m68kmake */, 326 | ); 327 | }; 328 | /* End PBXProject section */ 329 | 330 | /* Begin PBXShellScriptBuildPhase section */ 331 | 9F5682022CDADAA0004A53B8 /* Generate 68000 Core */ = { 332 | isa = PBXShellScriptBuildPhase; 333 | buildActionMask = 2147483647; 334 | files = ( 335 | ); 336 | inputFileListPaths = ( 337 | ); 338 | inputPaths = ( 339 | "$(SRCROOT)/Musashi/m68k_in.c", 340 | ); 341 | name = "Generate 68000 Core"; 342 | outputFileListPaths = ( 343 | ); 344 | outputPaths = ( 345 | "$(PROJECT_DERIVED_FILE_DIR)/Musashi/m68kops.c", 346 | "$(PROJECT_DERIVED_FILE_DIR)/Musashi/m68kops.h", 347 | ); 348 | runOnlyForDeploymentPostprocessing = 0; 349 | shellPath = /bin/sh; 350 | shellScript = "M68KMAKE=\"${BUILT_PRODUCTS_DIR}/m68kmake\"\n\nmkdir -p \"${PROJECT_DERIVED_FILE_DIR}/Musashi\"\n\n${M68KMAKE} \\\n \"${PROJECT_DERIVED_FILE_DIR}/Musashi\" \\\n \"${SRCROOT}/Musashi/m68k_in.c\"\n"; 351 | }; 352 | /* End PBXShellScriptBuildPhase section */ 353 | 354 | /* Begin PBXSourcesBuildPhase section */ 355 | 9F5406232CD8427C0054DA28 /* Sources */ = { 356 | isa = PBXSourcesBuildPhase; 357 | buildActionMask = 2147483647; 358 | files = ( 359 | 9F56821B2CDC93E2004A53B8 /* softfloat.c in Sources */, 360 | 9F54062F2CD842AE0054DA28 /* m68kcpu.c in Sources */, 361 | 9F5406302CD842AE0054DA28 /* m68kdasm.c in Sources */, 362 | ); 363 | runOnlyForDeploymentPostprocessing = 0; 364 | }; 365 | 9F54F9752CD841200054DA28 /* Sources */ = { 366 | isa = PBXSourcesBuildPhase; 367 | buildActionMask = 2147483647; 368 | files = ( 369 | ); 370 | runOnlyForDeploymentPostprocessing = 0; 371 | }; 372 | 9F5681F62CDAD996004A53B8 /* Sources */ = { 373 | isa = PBXSourcesBuildPhase; 374 | buildActionMask = 2147483647; 375 | files = ( 376 | 9F5682012CDAD9B8004A53B8 /* m68kmake.c in Sources */, 377 | ); 378 | runOnlyForDeploymentPostprocessing = 0; 379 | }; 380 | /* End PBXSourcesBuildPhase section */ 381 | 382 | /* Begin PBXTargetDependency section */ 383 | 9F5682042CDADB08004A53B8 /* PBXTargetDependency */ = { 384 | isa = PBXTargetDependency; 385 | target = 9F5681F92CDAD996004A53B8 /* m68kmake */; 386 | targetProxy = 9F5682032CDADB08004A53B8 /* PBXContainerItemProxy */; 387 | }; 388 | 9F5682062CDADF5F004A53B8 /* PBXTargetDependency */ = { 389 | isa = PBXTargetDependency; 390 | target = 9F5406252CD8427C0054DA28 /* Musashi */; 391 | targetProxy = 9F5682052CDADF5F004A53B8 /* PBXContainerItemProxy */; 392 | }; 393 | /* End PBXTargetDependency section */ 394 | 395 | /* Begin XCBuildConfiguration section */ 396 | 9F5406282CD8427C0054DA28 /* Debug */ = { 397 | isa = XCBuildConfiguration; 398 | buildSettings = { 399 | CODE_SIGN_STYLE = Automatic; 400 | EXECUTABLE_PREFIX = lib; 401 | GENERATE_MASTER_OBJECT_FILE = YES; 402 | HEADER_SEARCH_PATHS = ( 403 | "$(PROJECT_DERIVED_FILE_DIR)/Musashi/", 404 | "$(inherited)", 405 | ); 406 | PRODUCT_NAME = "$(TARGET_NAME)"; 407 | SKIP_INSTALL = YES; 408 | }; 409 | name = Debug; 410 | }; 411 | 9F5406292CD8427C0054DA28 /* Release */ = { 412 | isa = XCBuildConfiguration; 413 | buildSettings = { 414 | CODE_SIGN_STYLE = Automatic; 415 | EXECUTABLE_PREFIX = lib; 416 | GENERATE_MASTER_OBJECT_FILE = YES; 417 | HEADER_SEARCH_PATHS = ( 418 | "$(PROJECT_DERIVED_FILE_DIR)/Musashi/", 419 | "$(inherited)", 420 | ); 421 | PRODUCT_NAME = "$(TARGET_NAME)"; 422 | SKIP_INSTALL = YES; 423 | }; 424 | name = Release; 425 | }; 426 | 9F54F97E2CD841200054DA28 /* Debug */ = { 427 | isa = XCBuildConfiguration; 428 | buildSettings = { 429 | ALWAYS_SEARCH_USER_PATHS = NO; 430 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 431 | CLANG_ANALYZER_NONNULL = YES; 432 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 433 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 434 | CLANG_ENABLE_MODULES = YES; 435 | CLANG_ENABLE_OBJC_ARC = YES; 436 | CLANG_ENABLE_OBJC_WEAK = YES; 437 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 438 | CLANG_WARN_BOOL_CONVERSION = YES; 439 | CLANG_WARN_COMMA = YES; 440 | CLANG_WARN_CONSTANT_CONVERSION = YES; 441 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 442 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 443 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 444 | CLANG_WARN_EMPTY_BODY = YES; 445 | CLANG_WARN_ENUM_CONVERSION = YES; 446 | CLANG_WARN_INFINITE_RECURSION = YES; 447 | CLANG_WARN_INT_CONVERSION = YES; 448 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 449 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 450 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 451 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 452 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 453 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 454 | CLANG_WARN_STRICT_PROTOTYPES = YES; 455 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 456 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 457 | CLANG_WARN_UNREACHABLE_CODE = YES; 458 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 459 | COPY_PHASE_STRIP = NO; 460 | DEBUG_INFORMATION_FORMAT = dwarf; 461 | ENABLE_STRICT_OBJC_MSGSEND = YES; 462 | ENABLE_TESTABILITY = YES; 463 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 464 | GCC_C_LANGUAGE_STANDARD = gnu17; 465 | GCC_DYNAMIC_NO_PIC = NO; 466 | GCC_NO_COMMON_BLOCKS = YES; 467 | GCC_OPTIMIZATION_LEVEL = 0; 468 | GCC_PREPROCESSOR_DEFINITIONS = ( 469 | "MUSASHI_CNF=\\\"MINIXCompat_Musashi.h\\\"", 470 | "DEBUG=1", 471 | "$(inherited)", 472 | ); 473 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 474 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 475 | GCC_WARN_UNDECLARED_SELECTOR = YES; 476 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 477 | GCC_WARN_UNUSED_FUNCTION = YES; 478 | GCC_WARN_UNUSED_VARIABLE = YES; 479 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 480 | MACOSX_DEPLOYMENT_TARGET = 15.1; 481 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 482 | MTL_FAST_MATH = YES; 483 | ONLY_ACTIVE_ARCH = YES; 484 | SDKROOT = macosx; 485 | }; 486 | name = Debug; 487 | }; 488 | 9F54F97F2CD841200054DA28 /* Release */ = { 489 | isa = XCBuildConfiguration; 490 | buildSettings = { 491 | ALWAYS_SEARCH_USER_PATHS = NO; 492 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 493 | CLANG_ANALYZER_NONNULL = YES; 494 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 495 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 496 | CLANG_ENABLE_MODULES = YES; 497 | CLANG_ENABLE_OBJC_ARC = YES; 498 | CLANG_ENABLE_OBJC_WEAK = YES; 499 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 500 | CLANG_WARN_BOOL_CONVERSION = YES; 501 | CLANG_WARN_COMMA = YES; 502 | CLANG_WARN_CONSTANT_CONVERSION = YES; 503 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 504 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 505 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 506 | CLANG_WARN_EMPTY_BODY = YES; 507 | CLANG_WARN_ENUM_CONVERSION = YES; 508 | CLANG_WARN_INFINITE_RECURSION = YES; 509 | CLANG_WARN_INT_CONVERSION = YES; 510 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 511 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 512 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 513 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 514 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 515 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 516 | CLANG_WARN_STRICT_PROTOTYPES = YES; 517 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 518 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 519 | CLANG_WARN_UNREACHABLE_CODE = YES; 520 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 521 | COPY_PHASE_STRIP = NO; 522 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 523 | ENABLE_NS_ASSERTIONS = NO; 524 | ENABLE_STRICT_OBJC_MSGSEND = YES; 525 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 526 | GCC_C_LANGUAGE_STANDARD = gnu17; 527 | GCC_NO_COMMON_BLOCKS = YES; 528 | GCC_PREPROCESSOR_DEFINITIONS = ( 529 | "MUSASHI_CNF=\\\"MINIXCompat_Musashi.h\\\"", 530 | "NDEBUG=1", 531 | "$(inherited)", 532 | ); 533 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 534 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 535 | GCC_WARN_UNDECLARED_SELECTOR = YES; 536 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 537 | GCC_WARN_UNUSED_FUNCTION = YES; 538 | GCC_WARN_UNUSED_VARIABLE = YES; 539 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 540 | MACOSX_DEPLOYMENT_TARGET = 15.1; 541 | MTL_ENABLE_DEBUG_INFO = NO; 542 | MTL_FAST_MATH = YES; 543 | SDKROOT = macosx; 544 | }; 545 | name = Release; 546 | }; 547 | 9F54F9812CD841200054DA28 /* Debug */ = { 548 | isa = XCBuildConfiguration; 549 | buildSettings = { 550 | CODE_SIGN_STYLE = Automatic; 551 | ENABLE_HARDENED_RUNTIME = YES; 552 | INSTALL_PATH = /usr/local/bin; 553 | PRODUCT_NAME = "$(TARGET_NAME)"; 554 | }; 555 | name = Debug; 556 | }; 557 | 9F54F9822CD841200054DA28 /* Release */ = { 558 | isa = XCBuildConfiguration; 559 | buildSettings = { 560 | CODE_SIGN_STYLE = Automatic; 561 | ENABLE_HARDENED_RUNTIME = YES; 562 | INSTALL_PATH = /usr/local/bin; 563 | PRODUCT_NAME = "$(TARGET_NAME)"; 564 | }; 565 | name = Release; 566 | }; 567 | 9F5681FE2CDAD996004A53B8 /* Debug */ = { 568 | isa = XCBuildConfiguration; 569 | buildSettings = { 570 | CODE_SIGN_STYLE = Automatic; 571 | ENABLE_HARDENED_RUNTIME = YES; 572 | PRODUCT_NAME = "$(TARGET_NAME)"; 573 | }; 574 | name = Debug; 575 | }; 576 | 9F5681FF2CDAD996004A53B8 /* Release */ = { 577 | isa = XCBuildConfiguration; 578 | buildSettings = { 579 | CODE_SIGN_STYLE = Automatic; 580 | ENABLE_HARDENED_RUNTIME = YES; 581 | PRODUCT_NAME = "$(TARGET_NAME)"; 582 | }; 583 | name = Release; 584 | }; 585 | /* End XCBuildConfiguration section */ 586 | 587 | /* Begin XCConfigurationList section */ 588 | 9F5406272CD8427C0054DA28 /* Build configuration list for PBXNativeTarget "Musashi" */ = { 589 | isa = XCConfigurationList; 590 | buildConfigurations = ( 591 | 9F5406282CD8427C0054DA28 /* Debug */, 592 | 9F5406292CD8427C0054DA28 /* Release */, 593 | ); 594 | defaultConfigurationIsVisible = 0; 595 | defaultConfigurationName = Release; 596 | }; 597 | 9F54F9742CD841200054DA28 /* Build configuration list for PBXProject "MINIXCompat" */ = { 598 | isa = XCConfigurationList; 599 | buildConfigurations = ( 600 | 9F54F97E2CD841200054DA28 /* Debug */, 601 | 9F54F97F2CD841200054DA28 /* Release */, 602 | ); 603 | defaultConfigurationIsVisible = 0; 604 | defaultConfigurationName = Release; 605 | }; 606 | 9F54F9802CD841200054DA28 /* Build configuration list for PBXNativeTarget "MINIXCompat" */ = { 607 | isa = XCConfigurationList; 608 | buildConfigurations = ( 609 | 9F54F9812CD841200054DA28 /* Debug */, 610 | 9F54F9822CD841200054DA28 /* Release */, 611 | ); 612 | defaultConfigurationIsVisible = 0; 613 | defaultConfigurationName = Release; 614 | }; 615 | 9F5682002CDAD996004A53B8 /* Build configuration list for PBXNativeTarget "m68kmake" */ = { 616 | isa = XCConfigurationList; 617 | buildConfigurations = ( 618 | 9F5681FE2CDAD996004A53B8 /* Debug */, 619 | 9F5681FF2CDAD996004A53B8 /* Release */, 620 | ); 621 | defaultConfigurationIsVisible = 0; 622 | defaultConfigurationName = Release; 623 | }; 624 | /* End XCConfigurationList section */ 625 | }; 626 | rootObject = 9F54F9712CD841200054DA28 /* Project object */; 627 | } 628 | -------------------------------------------------------------------------------- /MINIXCompat.xcodeproj/xcshareddata/xcschemes/MINIXCompat.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 9 | 10 | 16 | 22 | 23 | 24 | 25 | 26 | 32 | 33 | 44 | 46 | 52 | 53 | 54 | 55 | 58 | 59 | 62 | 63 | 64 | 65 | 69 | 70 | 74 | 75 | 79 | 80 | 84 | 85 | 89 | 90 | 94 | 95 | 96 | 97 | 103 | 105 | 111 | 112 | 113 | 114 | 116 | 117 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /MINIXCompat/MINIXCompat.1: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /MINIXCompat/MINIXCompat.c: -------------------------------------------------------------------------------- 1 | /* 2 | MINIXCompat.c 3 | 4 | A compatibility environment that can run M68000 MINIX 1.5 binaries, 5 | for example to enable building M68000 MINIX on a modern UNIX. 6 | 7 | Copyright © 2024 Christopher M. Hanson. See file LICENSE for details. 8 | */ 9 | 10 | #include "MINIXCompat.h" 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include "MINIXCompat_Types.h" 23 | #include "MINIXCompat_Emulation.h" 24 | #include "MINIXCompat_Executable.h" 25 | #include "MINIXCompat_Filesystem.h" 26 | #include "MINIXCompat_Logging.h" 27 | #include "MINIXCompat_Messages.h" 28 | #include "MINIXCompat_Processes.h" 29 | #include "MINIXCompat_SysCalls.h" 30 | 31 | 32 | static MINIXCompat_Execution_State MINIXCompat_State = MINIXCompat_Execution_State_Started; 33 | 34 | 35 | int main(int argc, char **argv, char **envp) 36 | { 37 | // Validate arguments. 38 | 39 | if (argc < 2) { 40 | fprintf(stderr, "%s: Insufficient arguments.\n", argv[0]); 41 | exit(EX_USAGE); 42 | } 43 | 44 | // Initialize subsystems. 45 | 46 | MINIXCompat_Log_Initialize(); 47 | MINIXCompat_Filesystem_Initialize(); 48 | MINIXCompat_CPU_Initialize(); 49 | MINIXCompat_Processes_Initialize(); 50 | MINIXCompat_SysCall_Initialize(); 51 | 52 | // Run the main emulation loop. 53 | 54 | while (MINIXCompat_State != MINIXCompat_Execution_State_Finished) { 55 | switch (MINIXCompat_State) { 56 | case MINIXCompat_Execution_State_Started: { 57 | // Set up the tool to run. 58 | 59 | int16_t exec_err = MINIXCompat_Processes_ExecuteWithHostParams(argv[1], argc, argv, envp); 60 | if (exec_err != 0) { 61 | fprintf(stderr, "Failed to execute %s: %d\n", argv[1], exec_err); 62 | exit(EX_OSERR); 63 | } 64 | } break; 65 | 66 | case MINIXCompat_Execution_State_Ready: { 67 | // Reset the emulated CPU so it's prepared to run, then swtich to the running state. 68 | 69 | MINIXCompat_CPU_Reset(); 70 | MINIXCompat_Execution_ChangeState(MINIXCompat_Execution_State_Running); 71 | } break; 72 | 73 | case MINIXCompat_Execution_State_Running: { 74 | // Run the emulated CPU for a bunch of cycles. 75 | 76 | (void) MINIXCompat_CPU_Run(10000); 77 | 78 | // If the execution state hasn't changed as a result of running the emulated CPU, handle any pending signals. 79 | 80 | if (MINIXCompat_State == MINIXCompat_Execution_State_Running) { 81 | MINIXCompat_Processes_HandlePendingSignals(); 82 | } 83 | } break; 84 | 85 | case MINIXCompat_Execution_State_Finished: { 86 | // Just exit the main work loop once we're in this state. 87 | } break; 88 | } 89 | } 90 | 91 | // Exit with whatever our exit code should be. 92 | 93 | return MINIXCompat_Processes_ExitStatus; 94 | } 95 | 96 | 97 | void MINIXCompat_Execution_ChangeState(MINIXCompat_Execution_State state) 98 | { 99 | // Ensure a valid state transition, only these are allowed: 100 | // - start -> ready 101 | // - ready -> running 102 | // - running -> ready (for exec) 103 | // - running -> finished 104 | // - finished -> finished (since exit(2) can be called multiple times before actually exiting) 105 | 106 | assert( ((MINIXCompat_State == MINIXCompat_Execution_State_Started) && (state == MINIXCompat_Execution_State_Ready)) 107 | || ((MINIXCompat_State == MINIXCompat_Execution_State_Ready) && (state == MINIXCompat_Execution_State_Running)) 108 | || ((MINIXCompat_State == MINIXCompat_Execution_State_Running) && (state == MINIXCompat_Execution_State_Ready)) 109 | || ((MINIXCompat_State == MINIXCompat_Execution_State_Running) && (state == MINIXCompat_Execution_State_Finished)) 110 | || ((MINIXCompat_State == MINIXCompat_Execution_State_Finished) && (state == MINIXCompat_Execution_State_Finished))); 111 | 112 | MINIXCompat_State = state; 113 | } 114 | -------------------------------------------------------------------------------- /MINIXCompat/MINIXCompat.h: -------------------------------------------------------------------------------- 1 | // 2 | // MINIXCompat.h 3 | // MINIXCompat 4 | // 5 | // Created by Chris Hanson on 11/6/24. 6 | // Copyright © 2024 Christopher M. Hanson. See file LICENSE for details. 7 | // 8 | 9 | #ifndef MINIXCompat_h 10 | #define MINIXCompat_h 11 | 12 | #include "MINIXCompat_Types.h" 13 | 14 | 15 | MINIXCOMPAT_HEADER_BEGIN 16 | 17 | /*! 18 | The states that MINIXCompat execution can be in. 19 | */ 20 | typedef enum MINIXCompat_Execution_State { 21 | /*! The emulator has just been started. */ 22 | MINIXCompat_Execution_State_Started = 0, 23 | 24 | /*! The emulator is ready to run a new executable. */ 25 | MINIXCompat_Execution_State_Ready = 1, 26 | 27 | /*! The emulator should be running. */ 28 | MINIXCompat_Execution_State_Running = 2, 29 | 30 | /*! The emulator shoud shut down and exit with an appropriate status. */ 31 | MINIXCompat_Execution_State_Finished = 3, 32 | } MINIXCompat_Execution_State; 33 | 34 | 35 | /*! Change the execution state. */ 36 | MINIXCOMPAT_EXTERN void MINIXCompat_Execution_ChangeState(MINIXCompat_Execution_State state); 37 | 38 | 39 | MINIXCOMPAT_HEADER_END 40 | 41 | 42 | #endif /* MINIXCompat_h */ 43 | -------------------------------------------------------------------------------- /MINIXCompat/MINIXCompat_Emulation.c: -------------------------------------------------------------------------------- 1 | // 2 | // MINIXCompat_Emulation.c 3 | // MINIXCompat 4 | // 5 | // Created by Chris Hanson on 11/4/24. 6 | // Copyright © 2024 Christopher M. Hanson. See file LICENSE for details. 7 | // 8 | 9 | #include "MINIXCompat_Emulation.h" 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include /* for ntohs et al */ 19 | 20 | #include "MINIXCompat_Types.h" 21 | #include "MINIXCompat_Executable.h" 22 | #include "MINIXCompat_SysCalls.h" 23 | 24 | #include "m68k.h" 25 | 26 | 27 | MINIXCOMPAT_SOURCE_BEGIN 28 | 29 | 30 | /*! 31 | The RAM for the emulated CPU. Since all I/O is handled via system calls, there are no I/O devices and there isn't even a bootstrap ROM. Instead, the "bootstrap" is handled by manually setting a PC value and starting execution. This gives us the entire 16MB address space to work with, modulo data structures stored at specific addresses. 32 | 33 | Note that everything within this array is in Motorola (network) byte order. 34 | */ 35 | static uint8_t *MINIXCompat_RAM; 36 | 37 | 38 | static int MINIXCompat_CPU_Trap_Callback(int trap); 39 | 40 | 41 | int MINIXCompat_CPU_Initialize(void) 42 | { 43 | // Configure the RAM. 44 | 45 | MINIXCompat_RAM = calloc(16 * 1024 * 1024, sizeof(uint8_t)); 46 | assert(MINIXCompat_RAM != NULL); 47 | 48 | // Configure the CPU. 49 | 50 | m68k_init(); 51 | m68k_set_cpu_type(M68K_CPU_TYPE_68000); 52 | m68k_set_trap_instr_callback(MINIXCompat_CPU_Trap_Callback); 53 | 54 | // Ready to reset and then execute instructions! 55 | 56 | return 0; 57 | } 58 | 59 | 60 | void MINIXCompat_CPU_Reset(void) 61 | { 62 | // Establish initial vectors (really only SSP and PC). 63 | 64 | MINIXCompat_RAM_Write_32(0x000000, MINIXCompat_Stack_Base); 65 | MINIXCompat_RAM_Write_32(0x000004, MINIXCompat_Executable_Base); 66 | 67 | // Ensure the CPU starts in user mode with interrupts unmasked. 68 | 69 | m68k_set_reg(M68K_REG_SR, 0x00000000); 70 | 71 | // Pulse reset so the CPU can be run. 72 | 73 | m68k_pulse_reset(); 74 | } 75 | 76 | 77 | int MINIXCompat_CPU_Run(int cycles) 78 | { 79 | return m68k_execute(cycles); 80 | } 81 | 82 | 83 | m68k_address_t MINIXCompat_CPU_GetPC(void) 84 | { 85 | return m68k_get_reg(NULL, M68K_REG_PC); 86 | } 87 | 88 | void MINIXCompat_CPU_SetPC(m68k_address_t value) 89 | { 90 | m68k_set_reg(M68K_REG_PC, value); 91 | } 92 | 93 | uint16_t MINIXCompat_CPU_GetSR(void) 94 | { 95 | return m68k_get_reg(NULL, M68K_REG_SR); 96 | } 97 | 98 | m68k_address_t MINIXCompat_CPU_Push_16(uint16_t value) 99 | { 100 | m68k_address_t sp = m68k_get_reg(NULL, M68K_REG_SP); 101 | sp -= 2; 102 | m68k_set_reg(M68K_REG_SP, sp); 103 | MINIXCompat_RAM_Write_16(sp, value); 104 | return sp; 105 | } 106 | 107 | m68k_address_t MINIXCompat_CPU_Push_32(uint32_t value) 108 | { 109 | m68k_address_t sp = m68k_get_reg(NULL, M68K_REG_SP); 110 | sp -= 4; 111 | m68k_set_reg(M68K_REG_SP, sp); 112 | MINIXCompat_RAM_Write_32(sp, value); 113 | return sp; 114 | } 115 | 116 | 117 | static int MINIXCompat_CPU_Trap_Callback(int trap) 118 | { 119 | assert((trap >= 0x0) && (trap <= 0xf)); 120 | bool handled = false; 121 | 122 | switch (trap) { 123 | case 0x0: { 124 | // Get D0.w, D1.w, A0 and use them for func, src_dest, and msg respectively. 125 | uint32_t D0_l = m68k_get_reg(NULL, M68K_REG_D0); 126 | uint32_t D1_l = m68k_get_reg(NULL, M68K_REG_D1); 127 | uint32_t A0 = m68k_get_reg(NULL, M68K_REG_A0); 128 | 129 | minix_syscall_func_t func = (uint16_t) D0_l; 130 | uint16_t src_dest = (uint16_t) D1_l; 131 | m68k_address_t msg = A0; 132 | 133 | uint32_t new_D0_l = 0; 134 | minix_syscall_result_t syscall_result = MINIXCompat_System_Call(func, src_dest, msg, &new_D0_l); 135 | switch (syscall_result) { 136 | case minix_syscall_result_success: 137 | m68k_set_reg(M68K_REG_D0, htonl(new_D0_l)); 138 | break; 139 | 140 | case minix_syscall_result_success_empty: 141 | m68k_set_reg(M68K_REG_D0, 0x00000000); 142 | break; 143 | 144 | case minix_syscall_result_failure: 145 | m68k_set_reg(M68K_REG_D0, 0xFFFFFFFF); 146 | break; 147 | } 148 | 149 | handled = true; 150 | } break; 151 | 152 | default: { 153 | // Let the CPU handle the trap. 154 | handled = false; 155 | } break; 156 | } 157 | 158 | return handled ? 1 : 0; 159 | } 160 | 161 | 162 | uint8_t MINIXCompat_RAM_Read_8(m68k_address_t m68k_address) 163 | { 164 | assert(m68k_address < 0x01000000); 165 | const uint8_t *RAM = MINIXCompat_RAM + m68k_address; 166 | const uint8_t *RAM8 = (const uint8_t *)RAM; 167 | return *RAM8; 168 | } 169 | 170 | uint16_t MINIXCompat_RAM_Read_16(m68k_address_t m68k_address) 171 | { 172 | assert(m68k_address < 0x01000000); 173 | const uint8_t *RAM = MINIXCompat_RAM + m68k_address; 174 | const uint16_t *RAM16 = (const uint16_t *)RAM; 175 | return ntohs(*RAM16); 176 | } 177 | 178 | uint32_t MINIXCompat_RAM_Read_32(m68k_address_t m68k_address) 179 | { 180 | assert(m68k_address < 0x01000000); 181 | const uint8_t *RAM = MINIXCompat_RAM + m68k_address; 182 | const uint32_t *RAM32 = (const uint32_t *)RAM; 183 | return ntohl(*RAM32); 184 | } 185 | 186 | 187 | void MINIXCompat_RAM_Write_8(m68k_address_t m68k_address, uint8_t value) 188 | { 189 | assert(m68k_address < 0x01000000); 190 | uint8_t *RAM = MINIXCompat_RAM + m68k_address; 191 | uint8_t *RAM8 = (uint8_t *)RAM; 192 | *RAM8 = value; 193 | } 194 | 195 | void MINIXCompat_RAM_Write_16(m68k_address_t m68k_address, uint16_t value) 196 | { 197 | assert(m68k_address < 0x01000000); 198 | uint8_t *RAM = MINIXCompat_RAM + m68k_address; 199 | uint16_t *RAM16 = (uint16_t *)RAM; 200 | *RAM16 = htons(value); 201 | } 202 | 203 | void MINIXCompat_RAM_Write_32(m68k_address_t m68k_address, uint32_t value) 204 | { 205 | assert(m68k_address < 0x01000000); 206 | uint8_t *RAM = MINIXCompat_RAM + m68k_address; 207 | uint32_t *RAM32 = (uint32_t *)RAM; 208 | *RAM32 = htonl(value); 209 | } 210 | 211 | 212 | void MINIXCompat_RAM_Copy_Block_From_Host(m68k_address_t m68k_address, void *host_block_address, uint32_t host_block_size) 213 | { 214 | assert(m68k_address < 0x01000000); 215 | assert((host_block_size + m68k_address) <= 0x01000000); 216 | uint8_t *RAM = MINIXCompat_RAM + m68k_address; 217 | 218 | memcpy(RAM, host_block_address, host_block_size); 219 | } 220 | 221 | 222 | void *MINIXCompat_RAM_Copy_Block_To_Host(m68k_address_t m68k_address, uint32_t m68k_block_size) 223 | { 224 | assert(m68k_address < 0x01000000); 225 | assert((m68k_block_size + m68k_address) <= 0x01000000); 226 | 227 | const uint8_t *RAM = MINIXCompat_RAM + m68k_address; 228 | 229 | uint8_t *host_block = malloc(m68k_block_size); 230 | assert(host_block != NULL); 231 | 232 | memcpy(host_block, RAM, m68k_block_size); 233 | 234 | return host_block; 235 | } 236 | 237 | 238 | unsigned int m68k_read_memory_8(unsigned int address) 239 | { 240 | return MINIXCompat_RAM_Read_8(address); 241 | } 242 | 243 | unsigned int m68k_read_memory_16(unsigned int address) 244 | { 245 | return MINIXCompat_RAM_Read_16(address); 246 | } 247 | 248 | unsigned int m68k_read_memory_32(unsigned int address) 249 | { 250 | return MINIXCompat_RAM_Read_32(address); 251 | } 252 | 253 | unsigned int m68k_read_disassembler_8 (unsigned int address) 254 | { 255 | return MINIXCompat_RAM_Read_8(address); 256 | } 257 | 258 | unsigned int m68k_read_disassembler_16 (unsigned int address) 259 | { 260 | return MINIXCompat_RAM_Read_16(address); 261 | } 262 | 263 | unsigned int m68k_read_disassembler_32 (unsigned int address) 264 | { 265 | return MINIXCompat_RAM_Read_32(address); 266 | } 267 | 268 | void m68k_write_memory_8(unsigned int address, unsigned int value) 269 | { 270 | assert(value < 0x00000100); 271 | MINIXCompat_RAM_Write_8(address, value); 272 | } 273 | 274 | void m68k_write_memory_16(unsigned int address, unsigned int value) 275 | { 276 | assert(value < 0x00010000); 277 | MINIXCompat_RAM_Write_16(address, value); 278 | } 279 | 280 | void m68k_write_memory_32(unsigned int address, unsigned int value) 281 | { 282 | MINIXCompat_RAM_Write_32(address, value); 283 | } 284 | 285 | 286 | MINIXCOMPAT_SOURCE_END 287 | -------------------------------------------------------------------------------- /MINIXCompat/MINIXCompat_Emulation.h: -------------------------------------------------------------------------------- 1 | // 2 | // MINIXCompat_Emulation.h 3 | // MINIXCompat 4 | // 5 | // Created by Chris Hanson on 11/4/24. 6 | // Copyright © 2024 Christopher M. Hanson. See file LICENSE for details. 7 | // 8 | 9 | #ifndef MINIXCompat_Emulation_h 10 | #define MINIXCompat_Emulation_h 11 | 12 | #include "MINIXCompat_Types.h" 13 | 14 | 15 | MINIXCOMPAT_HEADER_BEGIN 16 | 17 | 18 | /*! Initialize the CPU emulation. */ 19 | MINIXCOMPAT_EXTERN int MINIXCompat_CPU_Initialize(void); 20 | 21 | /*! Reste the CPU emulation, necessary after everything is initialized and configred but before running. */ 22 | MINIXCOMPAT_EXTERN void MINIXCompat_CPU_Reset(void); 23 | 24 | /*! Run the CPU emulation. */ 25 | MINIXCOMPAT_EXTERN int MINIXCompat_CPU_Run(int cycles); 26 | 27 | 28 | /*! Get the current program counter. */ 29 | MINIXCOMPAT_EXTERN m68k_address_t MINIXCompat_CPU_GetPC(void); 30 | 31 | /*! Set the current program counter. */ 32 | MINIXCOMPAT_EXTERN void MINIXCompat_CPU_SetPC(m68k_address_t value); 33 | 34 | /*! Get the status register. */ 35 | MINIXCOMPAT_EXTERN uint16_t MINIXCompat_CPU_GetSR(void); 36 | 37 | /*! Push a 16-bit word on the stack, and return the address to which it was pushed.. */ 38 | MINIXCOMPAT_EXTERN m68k_address_t MINIXCompat_CPU_Push_16(uint16_t value); 39 | 40 | /*! Push a 32-bit word on the stack, and return the address to which it was pushed. */ 41 | MINIXCOMPAT_EXTERN m68k_address_t MINIXCompat_CPU_Push_32(uint32_t value); 42 | 43 | 44 | /*! Read the 8-bit byte at \a m68k_address from RAM. */ 45 | MINIXCOMPAT_EXTERN uint8_t MINIXCompat_RAM_Read_8(m68k_address_t m68k_address); 46 | 47 | /*! Read the 16-bit word at \a m68k_address from RAM, converting to host byte order. */ 48 | MINIXCOMPAT_EXTERN uint16_t MINIXCompat_RAM_Read_16(m68k_address_t m68k_address); 49 | 50 | /*! Read the 32-bit longword at \a m68k_address from RAM, converting to host byte order. */ 51 | MINIXCOMPAT_EXTERN uint32_t MINIXCompat_RAM_Read_32(m68k_address_t m68k_address); 52 | 53 | /*! Read the 8-bit byte \a value to \a m68k_address in RAM. */ 54 | MINIXCOMPAT_EXTERN void MINIXCompat_RAM_Write_8(m68k_address_t m68k_address, uint8_t value); 55 | 56 | /*! Read the 16-bit word \a value to \a m68k_address in RAM, converting from host byte order. */ 57 | MINIXCOMPAT_EXTERN void MINIXCompat_RAM_Write_16(m68k_address_t m68k_address, uint16_t value); 58 | 59 | /*! Read the 32-bit longword \a value to \a m68k_address in RAM, converting from host byte order. */ 60 | MINIXCOMPAT_EXTERN void MINIXCompat_RAM_Write_32(m68k_address_t m68k_address, uint32_t value); 61 | 62 | 63 | /*! 64 | Copy a block of memory to the emulated CPU from the host address space. 65 | 66 | The `host_block_size` plus the `m68k_address` must not extend past the end of the 16MB address space. 67 | */ 68 | MINIXCOMPAT_EXTERN void MINIXCompat_RAM_Copy_Block_From_Host(m68k_address_t m68k_address, void *host_block_address, uint32_t host_block_size); 69 | 70 | /*! 71 | Copy a block of memory from the emulated CPU to the host address space. The copied block is a new allocation created with `calloc` that must be freed using `free`. 72 | 73 | The `m68k_block_size` plus the `m68k_address` must not extend past the end of the 16MB address space. 74 | */ 75 | MINIXCOMPAT_EXTERN void *MINIXCompat_RAM_Copy_Block_To_Host(m68k_address_t m68k_address, uint32_t m68k_block_size); 76 | 77 | 78 | MINIXCOMPAT_HEADER_END 79 | 80 | 81 | #endif /* MINIXCompat_Emulation_h */ 82 | -------------------------------------------------------------------------------- /MINIXCompat/MINIXCompat_EmulationOps.c: -------------------------------------------------------------------------------- 1 | // 2 | // MINIXCompat_EmulationOps.c 3 | // MINIXCompat 4 | // 5 | // Created by Chris Hanson on 11/10/24. 6 | // Copyright © 2024 Christopher M. Hanson. See file LICENSE for details. 7 | // 8 | 9 | /* 10 | For some reason, Xcode 16.1 isn't adding these back into the build after generating them via a Run Script build phase, despite being listed as Output Files. 11 | 12 | So instead of relying on that, pull them into the Musashi target's sources entirely manually. 13 | */ 14 | 15 | #include "m68k.h" 16 | #include "m68kops.h" 17 | #include "m68kops.c" 18 | -------------------------------------------------------------------------------- /MINIXCompat/MINIXCompat_Errors.c: -------------------------------------------------------------------------------- 1 | // 2 | // MINIXCompat_Errors.c 3 | // MINIXCompat 4 | // 5 | // Created by Chris Hanson on 12/4/24. 6 | // Copyright © 2024 Christopher M. Hanson. See file LICENSE for details. 7 | // 8 | 9 | #include "MINIXCompat_Errors.h" 10 | 11 | #include 12 | 13 | 14 | MINIXCOMPAT_SOURCE_BEGIN 15 | 16 | 17 | MINIXCOMPAT_EXTERN minix_error_t MINIXCompat_Errors_MINIXErrorForHostError(int host_error) 18 | { 19 | switch (host_error) { 20 | case EPERM: return minix_EPERM; 21 | case ENOENT: return minix_ENOENT; 22 | case ESRCH: return minix_ESRCH; 23 | case EINTR: return minix_EINTR; 24 | case EIO: return minix_EIO; 25 | case ENXIO: return minix_ENXIO; 26 | case E2BIG: return minix_E2BIG; 27 | case ENOEXEC: return minix_ENOEXEC; 28 | case EBADF: return minix_EBADF; 29 | case ECHILD: return minix_ECHILD; 30 | case EAGAIN: return minix_EAGAIN; 31 | case ENOMEM: return minix_ENOMEM; 32 | case EACCES: return minix_EACCES; 33 | case EFAULT: return minix_EFAULT; 34 | case ENOTBLK: return minix_ENOTBLK; 35 | case EBUSY: return minix_EBUSY; 36 | case EEXIST: return minix_EEXIST; 37 | case EXDEV: return minix_EXDEV; 38 | case ENODEV: return minix_ENODEV; 39 | case ENOTDIR: return minix_ENOTDIR; 40 | case EISDIR: return minix_EISDIR; 41 | case EINVAL: return minix_EINVAL; 42 | case ENFILE: return minix_ENFILE; 43 | case EMFILE: return minix_EMFILE; 44 | case ENOTTY: return minix_ENOTTY; 45 | case ETXTBSY: return minix_ETXTBSY; 46 | case EFBIG: return minix_EFBIG; 47 | case ENOSPC: return minix_ENOSPC; 48 | case ESPIPE: return minix_ESPIPE; 49 | case EROFS: return minix_EROFS; 50 | case EMLINK: return minix_EMLINK; 51 | case EPIPE: return minix_EPIPE; 52 | case EDOM: return minix_EDOM; 53 | case ERANGE: return minix_ERANGE; 54 | case EDEADLK: return minix_EDEADLK; 55 | case ENAMETOOLONG: return minix_ENAMETOOLONG; 56 | case ENOLCK: return minix_ENOLCK; 57 | case ENOSYS: return minix_ENOSYS; 58 | case ENOTEMPTY: return minix_ENOTEMPTY; 59 | 60 | default: return minix_ERROR; 61 | } 62 | } 63 | 64 | MINIXCOMPAT_EXTERN int MINIXCompat_Errors_HostErrorForMINIXError(minix_error_t minix_error) 65 | { 66 | switch (minix_error) { 67 | case minix_EPERM: return EPERM; 68 | case minix_ENOENT: return ENOENT; 69 | case minix_ESRCH: return ESRCH; 70 | case minix_EINTR: return EINTR; 71 | case minix_EIO: return EIO; 72 | case minix_ENXIO: return ENXIO; 73 | case minix_E2BIG: return E2BIG; 74 | case minix_ENOEXEC: return ENOEXEC; 75 | case minix_EBADF: return EBADF; 76 | case minix_ECHILD: return ECHILD; 77 | case minix_EAGAIN: return EAGAIN; 78 | case minix_ENOMEM: return ENOMEM; 79 | case minix_EACCES: return EACCES; 80 | case minix_EFAULT: return EFAULT; 81 | case minix_ENOTBLK: return ENOTBLK; 82 | case minix_EBUSY: return EBUSY; 83 | case minix_EEXIST: return EEXIST; 84 | case minix_EXDEV: return EXDEV; 85 | case minix_ENODEV: return ENODEV; 86 | case minix_ENOTDIR: return ENOTDIR; 87 | case minix_EISDIR: return EISDIR; 88 | case minix_EINVAL: return EINVAL; 89 | case minix_ENFILE: return ENFILE; 90 | case minix_EMFILE: return EMFILE; 91 | case minix_ENOTTY: return ENOTTY; 92 | case minix_ETXTBSY: return ETXTBSY; 93 | case minix_EFBIG: return EFBIG; 94 | case minix_ENOSPC: return ENOSPC; 95 | case minix_ESPIPE: return ESPIPE; 96 | case minix_EROFS: return EROFS; 97 | case minix_EMLINK: return EMLINK; 98 | case minix_EPIPE: return EPIPE; 99 | case minix_EDOM: return EDOM; 100 | case minix_ERANGE: return ERANGE; 101 | case minix_EDEADLK: return EDEADLK; 102 | case minix_ENAMETOOLONG: return ENAMETOOLONG; 103 | case minix_ENOLCK: return ENOLCK; 104 | case minix_ENOSYS: return ENOSYS; 105 | case minix_ENOTEMPTY: return ENOTEMPTY; 106 | case minix_ERROR: return ENOTRECOVERABLE; 107 | } 108 | } 109 | 110 | MINIXCOMPAT_SOURCE_END 111 | -------------------------------------------------------------------------------- /MINIXCompat/MINIXCompat_Errors.h: -------------------------------------------------------------------------------- 1 | // 2 | // MINIXCompat_Errors.h 3 | // MINIXCompat 4 | // 5 | // Created by Chris Hanson on 12/4/24. 6 | // Copyright © 2024 Christopher M. Hanson. See file LICENSE for details. 7 | // 8 | 9 | #ifndef MINIXCompat_Errors_h 10 | #define MINIXCompat_Errors_h 11 | 12 | #include "MINIXCompat_Types.h" 13 | 14 | 15 | MINIXCOMPAT_HEADER_BEGIN 16 | 17 | 18 | /*! A MINIX `errno` value. */ 19 | typedef enum minix_error: int16_t { 20 | minix_EPERM = 1, 21 | minix_ENOENT = 2, 22 | minix_ESRCH = 3, 23 | minix_EINTR = 4, 24 | minix_EIO = 5, 25 | minix_ENXIO = 6, 26 | minix_E2BIG = 7, 27 | minix_ENOEXEC = 8, 28 | minix_EBADF = 9, 29 | minix_ECHILD = 10, 30 | minix_EAGAIN = 11, 31 | minix_ENOMEM = 12, 32 | minix_EACCES = 13, 33 | minix_EFAULT = 14, 34 | minix_ENOTBLK = 15, 35 | minix_EBUSY = 16, 36 | minix_EEXIST = 17, 37 | minix_EXDEV = 18, 38 | minix_ENODEV = 19, 39 | minix_ENOTDIR = 20, 40 | minix_EISDIR = 21, 41 | minix_EINVAL = 22, 42 | minix_ENFILE = 23, 43 | minix_EMFILE = 24, 44 | minix_ENOTTY = 25, 45 | minix_ETXTBSY = 26, 46 | minix_EFBIG = 27, 47 | minix_ENOSPC = 28, 48 | minix_ESPIPE = 29, 49 | minix_EROFS = 30, 50 | minix_EMLINK = 31, 51 | minix_EPIPE = 32, 52 | minix_EDOM = 33, 53 | minix_ERANGE = 34, 54 | minix_EDEADLK = 35, 55 | minix_ENAMETOOLONG = 36, 56 | minix_ENOLCK = 37, 57 | minix_ENOSYS = 38, 58 | minix_ENOTEMPTY = 39, 59 | 60 | minix_ERROR = 99, 61 | } minix_error_t; 62 | 63 | 64 | MINIXCOMPAT_EXTERN minix_error_t MINIXCompat_Errors_MINIXErrorForHostError(int host_error); 65 | 66 | MINIXCOMPAT_EXTERN int MINIXCompat_Errors_HostErrorForMINIXError(minix_error_t minix_error); 67 | 68 | 69 | MINIXCOMPAT_HEADER_END 70 | 71 | 72 | #endif /* MINIXCompat_Errors_h */ 73 | -------------------------------------------------------------------------------- /MINIXCompat/MINIXCompat_Executable.c: -------------------------------------------------------------------------------- 1 | // 2 | // MINIXCompat_Executable.c 3 | // MINIXCompat 4 | // 5 | // Created by Chris Hanson on 11/6/24. 6 | // Copyright © 2024 Christopher M. Hanson. See file LICENSE for details. 7 | // 8 | 9 | #include "MINIXCompat_Executable.h" 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include /* for ntohs et al */ 19 | 20 | #include "MINIXCompat_Types.h" 21 | #include "MINIXCompat_Errors.h" 22 | 23 | 24 | MINIXCOMPAT_SOURCE_BEGIN 25 | 26 | 27 | const m68k_address_t MINIXCompat_Executable_Base = 0x00001000; // text + data + bss + brk(2) 28 | const m68k_address_t MINIXCompat_Executable_Limit = 0x00fe0000; // heap grows upward 29 | const m68k_address_t MINIXCompat_Stack_Base = 0x00ff0000; // up to 64KB stack 30 | const m68k_address_t MINIXCompat_Stack_Limit = 0x00fe0000; // stack grows downward, not upward 31 | 32 | 33 | const uint32_t MINIX_CLICK_SIZE = 256; 34 | const uint32_t MINIX_CLICK_SHIFT = 8; 35 | #define MINIX_CLICK_ROUND(s) (((s) + MINIX_CLICK_SIZE - 1) >> MINIX_CLICK_SHIFT) 36 | 37 | 38 | struct minix_exec { 39 | uint32_t a_magic; 40 | uint32_t a_flags; 41 | uint32_t a_text; 42 | uint32_t a_data; 43 | uint32_t a_bss; 44 | uint32_t a_no_entry; 45 | uint32_t a_total; 46 | uint32_t a_syms; 47 | } __attribute__((packed)); 48 | 49 | 50 | const uint32_t minix_exec_magic_combined = 0x04100301; 51 | const uint32_t minix2_exec_magic_combined = 0x0b100301; 52 | const uint32_t minix_exec_magic_separate = 0x04200301; 53 | const uint32_t minix_exec_flags = 0x00000020; 54 | const uint32_t minix_exec_no_entry = 0x00000000; 55 | 56 | 57 | /*! The full version of the MINIXExecutable structure. */ 58 | struct MINIXCompat_Executable { 59 | 60 | /*! Executable header, swapped to host byte order. */ 61 | struct minix_exec exec_h; 62 | }; 63 | 64 | 65 | static int MINIXExecutableLoadHeader(FILE *pef, struct MINIXCompat_Executable *peh); 66 | static void MINIXExecutableRelocateLongAtOffset(uint8_t *buf, uint32_t relocation_base, uint32_t relocation_offset); 67 | static int MINIXExecutableRelocate(FILE *pef, struct MINIXCompat_Executable *peh, uint8_t *buf); 68 | 69 | // The process' initial break value 70 | static m68k_address_t minix_initial_break = 0; 71 | 72 | m68k_address_t MINIXCompat_Executable_Get_Initial_Break(void) { 73 | return minix_initial_break; 74 | } 75 | 76 | int MINIXCompat_Executable_Load(FILE *pef, struct MINIXCompat_Executable * _Nullable * _Nonnull out_peh, uint8_t * _Nullable * _Nonnull out_buf, uint32_t *out_buf_len) 77 | { 78 | assert(pef != NULL); 79 | assert((out_peh != NULL) && (*out_peh == NULL)); 80 | assert((out_buf != NULL) && (*out_buf == NULL)); 81 | assert(out_buf_len != NULL); 82 | 83 | // Load and validate the executable header. 84 | 85 | struct MINIXCompat_Executable *peh = calloc(sizeof(struct MINIXCompat_Executable), 1); 86 | if (peh == NULL) return -ENOMEM; 87 | *out_peh = peh; 88 | 89 | int header_err = MINIXExecutableLoadHeader(pef, peh); 90 | if (header_err != 0) return header_err; 91 | 92 | // Compute the size of buffer to allocate for the program. 93 | 94 | struct minix_exec *exec_h = &peh->exec_h; 95 | 96 | uint32_t text_clicks = MINIX_CLICK_ROUND(exec_h->a_text); 97 | uint32_t total_clicks = MINIX_CLICK_ROUND(exec_h->a_total); 98 | 99 | uint8_t *buf = calloc(total_clicks * MINIX_CLICK_SIZE, 1); 100 | if (buf == NULL) return -ENOMEM; 101 | *out_buf = buf; 102 | *out_buf_len = total_clicks * MINIX_CLICK_SIZE; 103 | 104 | // Seek past the header, then load the data and text into the blob at the appropriate offsets. 105 | 106 | int seek_err = fseek(pef, sizeof(struct minix_exec), SEEK_SET); 107 | if (seek_err != 0) return -MINIXCompat_Errors_MINIXErrorForHostError(errno); 108 | 109 | uint32_t text_base = 0; 110 | uint32_t data_base = text_base + (text_clicks * MINIX_CLICK_SIZE); 111 | 112 | if (text_clicks > 0) { 113 | size_t text_read = fread(buf + text_base, exec_h->a_text, 1, pef); 114 | if (text_read < 1) return -MINIXCompat_Errors_MINIXErrorForHostError(ENOEXEC); 115 | } 116 | 117 | size_t data_read = fread(buf + data_base, exec_h->a_data, 1, pef); 118 | if (data_read < 1) return -MINIXCompat_Errors_MINIXErrorForHostError(ENODATA); 119 | 120 | // Relocation information is after any symbol table, so skip that. 121 | 122 | if (exec_h->a_syms) { 123 | int skip_err = fseek(pef, exec_h->a_syms, SEEK_CUR); 124 | if (skip_err != 0) return -MINIXCompat_Errors_MINIXErrorForHostError(errno); 125 | } 126 | 127 | // Do any necessary relocation. 128 | 129 | int relocate_err = MINIXExecutableRelocate(pef, peh, buf); 130 | if (relocate_err != 0) return -relocate_err; 131 | 132 | // Set up the process' initial break 133 | minix_initial_break = exec_h->a_text + exec_h->a_data + exec_h->a_bss; 134 | 135 | return 0; 136 | } 137 | 138 | 139 | static int MINIXExecutableLoadHeader(FILE *pef, struct MINIXCompat_Executable *peh) 140 | { 141 | assert(pef != NULL); 142 | assert(peh != NULL); 143 | 144 | // Get the network byte order header at the head of the file. 145 | 146 | struct minix_exec exec_n; 147 | 148 | int seek_err = fseek(pef, 0, SEEK_SET); 149 | if (seek_err != 0) return -MINIXCompat_Errors_MINIXErrorForHostError(errno); 150 | 151 | size_t read_count = fread(&exec_n, sizeof(struct minix_exec), 1, pef); 152 | if (read_count < 1) return -MINIXCompat_Errors_MINIXErrorForHostError(EIO); 153 | 154 | // Copy the network byte order header to the header in host byte order. 155 | 156 | struct minix_exec *exec_h = &peh->exec_h; 157 | 158 | exec_h->a_magic = ntohl(exec_n.a_magic); 159 | exec_h->a_flags = ntohl(exec_n.a_flags); 160 | exec_h->a_text = ntohl(exec_n.a_text); 161 | exec_h->a_data = ntohl(exec_n.a_data); 162 | exec_h->a_bss = ntohl(exec_n.a_bss); 163 | exec_h->a_no_entry = ntohl(exec_n.a_no_entry); 164 | exec_h->a_total = ntohl(exec_n.a_total); 165 | exec_h->a_syms = ntohl(exec_n.a_syms); 166 | 167 | // Validate the header. 168 | 169 | if ( (exec_h->a_magic != minix_exec_magic_combined) 170 | && (exec_h->a_magic != minix2_exec_magic_combined) 171 | && (exec_h->a_magic != minix_exec_magic_separate)) { 172 | return -ENOEXEC; 173 | } 174 | 175 | if (exec_h->a_flags != minix_exec_flags) return -ENOEXEC; 176 | if (exec_h->a_no_entry != minix_exec_no_entry) return -ENOEXEC; 177 | if (exec_h->a_total == 0) return -ENOEXEC; 178 | 179 | if ( (exec_h->a_magic == minix_exec_magic_combined) 180 | || (exec_h->a_magic == minix2_exec_magic_combined)) { 181 | // Combined I&D is considered all data, so adjust. 182 | exec_h->a_data += exec_h->a_text; 183 | exec_h->a_text = 0; 184 | } else { 185 | // Do adjustment as demonstrated in MINIX mm/exec.c (if needed). 186 | } 187 | 188 | return 0; 189 | } 190 | 191 | 192 | /*! 193 | Relocate! 194 | 195 | Change the longword at \a relocation_offset so it's relative to \a relocation_base rather than `0`. 196 | 197 | - NOTE: The values in _buf_ are always in network byte order so we have to swap back and forth. 198 | */ 199 | static void MINIXExecutableRelocateLongAtOffset(uint8_t *buf, uint32_t relocation_base, uint32_t relocation_offset) 200 | { 201 | uint8_t *pb = buf + relocation_offset; 202 | uint32_t *pl = (uint32_t *)pb; 203 | uint32_t l = ntohl(*pl); 204 | uint32_t rl = l + relocation_base; 205 | *pl = htonl(rl); 206 | } 207 | 208 | 209 | static int MINIXExecutableRelocate(FILE *pef, struct MINIXCompat_Executable *peh, uint8_t *buf) 210 | { 211 | assert(pef != NULL); 212 | assert(peh != NULL); 213 | assert(buf != NULL); 214 | 215 | // At this point pef should be set to the point in the file where relocation information lives. 216 | 217 | int32_t relocation_initial_offset_n; 218 | 219 | size_t read_count = fread(&relocation_initial_offset_n, sizeof(int32_t), 1, pef); 220 | if (read_count < 1) { 221 | // No relocation information, just return success. 222 | return 0; 223 | } 224 | 225 | uint32_t relocation_offset = ntohl(relocation_initial_offset_n); 226 | bool done = (relocation_offset == 0); 227 | 228 | if (!done) { 229 | // Do the first relocation. 230 | 231 | MINIXExecutableRelocateLongAtOffset(buf, MINIXCompat_Executable_Base, relocation_offset); 232 | 233 | // Do the rest of the relocations one at a time. 234 | 235 | do { 236 | uint8_t b; 237 | size_t relo_read_count = fread(&b, sizeof(uint8_t), 1, pef); 238 | if (relo_read_count < 1) return -EIO; 239 | 240 | if (b == 0x00) { 241 | // Relocation done. 242 | done = true; 243 | } else if (b == 0x01) { 244 | // Don't relocate, just bump the relocation offset by 254. 245 | relocation_offset += 254; 246 | } else if ((b & 0x01) == 0x00) { 247 | // Bump the offset by the value encoded into b. 248 | 249 | relocation_offset += b; 250 | 251 | MINIXExecutableRelocateLongAtOffset(buf, MINIXCompat_Executable_Base, relocation_offset); 252 | } else if ((b & 0x01) == 0x01) { 253 | return -ENOEXEC; 254 | } 255 | } while (!done); 256 | } 257 | 258 | return 0; 259 | } 260 | 261 | 262 | MINIXCOMPAT_SOURCE_END 263 | -------------------------------------------------------------------------------- /MINIXCompat/MINIXCompat_Executable.h: -------------------------------------------------------------------------------- 1 | // 2 | // MINIXCompat_Executable.h 3 | // MINIXCompat 4 | // 5 | // Created by Chris Hanson on 11/6/24. 6 | // Copyright © 2024 Christopher M. Hanson. See file LICENSE for details. 7 | // 8 | 9 | #ifndef MINIXCompat_Executable_h 10 | #define MINIXCompat_Executable_h 11 | 12 | #include 13 | #include 14 | 15 | #include "MINIXCompat_Types.h" 16 | 17 | 18 | MINIXCOMPAT_HEADER_BEGIN 19 | 20 | 21 | /*! Forward declaration of opaque MINIXExcutable structure. */ 22 | struct MINIXCompat_Executable; 23 | 24 | 25 | /*! Base address in emulated CPU at which every executable is loaded. */ 26 | MINIXCOMPAT_EXTERN const m68k_address_t MINIXCompat_Executable_Base; 27 | 28 | /*! First address after ``MINIXCompat_Executable_Base`` in emulated CPU after end of initialized and uninitialized data. */ 29 | MINIXCOMPAT_EXTERN const m68k_address_t MINIXCompat_Executable_Limit; 30 | 31 | /*! Top of the MINIX stack at start of execution, above which are argc, argv, envp, and their contents, up to 64KB worth. */ 32 | MINIXCOMPAT_EXTERN const m68k_address_t MINIXCompat_Stack_Base; 33 | 34 | /*! Limit of the MINIX stack, *below* which it must not grow since that would collide with initialized and uniitalized data. */ 35 | MINIXCOMPAT_EXTERN const m68k_address_t MINIXCompat_Stack_Limit; 36 | 37 | /*! 38 | Get the process' initial break value 39 | */ 40 | MINIXCOMPAT_EXTERN m68k_address_t MINIXCompat_Executable_Get_Initial_Break(void); 41 | 42 | 43 | /*! 44 | Loads a MINIX executable. 45 | 46 | Loads a MINIX executable's text (code) and data from \a pef into host RAM, with proper alignment to the MINIX 256-byte "click" size, and performs any relocation the file indicates is necessary. 47 | 48 | - Parameters: 49 | - pef: pointer to `FILE` from which to load the executable 50 | - out_peh: where to place the pointer to the new `struct MINIXCompat_Executable` representing the executable, which should be `NULL` on entry 51 | - out_buf: where to place the pointer to the buffer containing the executable, which should be `NULL` on entry 52 | - out_buf_len: where to place the length of the buffer containing the executable 53 | 54 | - Returns: 55 | - `0` on success, `-errno` on error; if `out_pef` or `out_buf` points to a non-`NULL` pointer, those must be freed by the caller with `free(3)`, even if an error has occurred. 56 | 57 | - Warning: Allocation of both `out_peh` and `out_buf` is performed with `calloc()` and is the caller's responsibility to `free()`. 58 | These should explicitly point to `NULL` pointers before a call, and may still need to be freed upon error. 59 | */ 60 | MINIXCOMPAT_EXTERN int MINIXCompat_Executable_Load(FILE *pef, struct MINIXCompat_Executable * _Nullable * _Nonnull out_peh, uint8_t * _Nullable * _Nonnull out_buf, uint32_t *out_buf_len); 61 | 62 | 63 | MINIXCOMPAT_HEADER_END 64 | 65 | 66 | #endif /* MINIXCompat_Executable_h */ 67 | -------------------------------------------------------------------------------- /MINIXCompat/MINIXCompat_Filesystem.c: -------------------------------------------------------------------------------- 1 | // 2 | // MINIXCompat_Filesystem.c 3 | // MINIXCompat 4 | // 5 | // Created by Chris Hanson on 11/16/24. 6 | // Copyright © 2024 Christopher M. Hanson. See file LICENSE for details. 7 | // 8 | 9 | #include "MINIXCompat_Filesystem.h" 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include /* for ntohs et al */ 23 | #include 24 | 25 | #include "MINIXCompat_Types.h" 26 | #include "MINIXCompat_Errors.h" 27 | #include "MINIXCompat_Logging.h" 28 | 29 | 30 | #if DEBUG 31 | 32 | /*! Uncomment to log all filesystem syscall implementations and arguments (though not content). */ 33 | //#define DEBUG_FILESYSTEM_SYSCALLS 1 34 | 35 | #endif 36 | 37 | 38 | MINIXCOMPAT_SOURCE_BEGIN 39 | 40 | 41 | #ifndef HTONS 42 | #define HTONS(x) ((x) = htons(x)) 43 | #define HTONL(x) ((x) = htonl(x)) 44 | #endif 45 | 46 | 47 | /*! Default directory of MINIX installation. */ 48 | static const char * const MINIXCOMPAT_DIR_default = "/opt/minix"; 49 | 50 | /*! Actual directory of MINIX installation. */ 51 | static const char *MINIXCOMPAT_DIR = NULL; 52 | 53 | /*! Cached length of ``MINIXCOMPAT_DIR``. */ 54 | static size_t MINIXCOMPAT_DIR_len = 0; 55 | 56 | /*! MINIX current working directory. */ 57 | static char *MINIXCOMPAT_PWD = NULL; 58 | 59 | /*! Cached length of ``MINIXCOMPAT_PWD``. */ 60 | static size_t MINIXCOMPAT_PWD_len = 0; 61 | 62 | /*! MINIX current working directory as a host path. */ 63 | static char *MINIXCOMPAT_PWD_Host = NULL; 64 | 65 | /*! Cached length of ``MINIXCOMPAT_PWD_Host``. */ 66 | static size_t MINIXCOMPAT_PWD_Host_len = 0; 67 | 68 | 69 | /*! 70 | A MINIX directory entry within a directory file. 71 | 72 | Since MINIX (like UNIX V7) requires use of `open(2)` and `read(2)` to iterate a directory instead of directory-specific system calls, upon `open(2)` of a directory MINIXCompat needs to synthesize its contents. Note that `d_ino` needs swapping as well before being able to be passed back to MINIX. 73 | */ 74 | struct minix_dirent { 75 | /*! The inode for this entry; will be 0 for a deleted file or other gap. */ 76 | uint16_t d_ino; 77 | 78 | /*! The name of this entry. */ 79 | char d_name[14]; 80 | } MINIXCOMPAT_PACK_STRUCT; 81 | typedef struct minix_dirent minix_dirent_t; 82 | 83 | 84 | /*! The number of open files MINIX can have at one time. */ 85 | #define MINIXCompat_fd_count 20 86 | 87 | /*! 88 | A mapping between MINIX file descriptors and host file descriptors. 89 | 90 | - Note: MINIX 1.5 is unlikely to support >32767 files in a directory due to use of 16-bit `int` in so many places… 91 | */ 92 | typedef struct minix_fdmap { 93 | /*! The host file descriptor. */ 94 | int host_fd; 95 | 96 | /*! The MINIX file descriptor (also currently used as the index. */ 97 | minix_fd_t minix_fd; 98 | 99 | /*! Whether the fd represents a file or a directory (or hasn't been checked). */ 100 | enum { f_unchecked, f_file, f_directory } f_type; 101 | 102 | /*! If this is a directory, the synthetic directory contents. */ 103 | minix_dirent_t *dir_entries; 104 | 105 | /*! If this is a directory, the number of entries it contains, rounded up to the next multiple of 32; empty entries have a 0 inode. */ 106 | int16_t dir_count; 107 | 108 | /*! If this is a directory, the current directory read offset. */ 109 | minix_off_t dir_offset; 110 | } minix_fdmap_t; 111 | 112 | /*! 113 | The table that maps between MINIX file descriptors and host file descriptors. 114 | 115 | This table is indexed by `minix_fd_t` with empty slots containing a `minix_fd` member of `-1` and in-use slots containing zero or a positive number. 116 | 117 | - Warning: Don't try treat a `host_fd` of `-1` as closed, because opening a directory doesn't keep the host file descriptor around due to the fact that `fopendir(2)` sets the close-on-exec bit, and we don't necessarily want that since it would be a behavior change for MINIX. 118 | */ 119 | static minix_fdmap_t MINIXCompat_fd_table[MINIXCompat_fd_count]; 120 | 121 | 122 | // MARK: - Forward Declarations 123 | 124 | static void MINIXCompat_CWD_Initialize(void); 125 | 126 | static void MINIXCompat_fd_ClearDescriptorEntry(minix_fd_t minix_fd); 127 | 128 | static void MINIXCompat_File_MINIXStatBufForHostStatBuf(minix_stat_t * _Nonnull minix_stat_buf, struct stat * _Nonnull host_stat_buf); 129 | static minix_ino_t MINIXCompat_File_MINIXInodeForHostInode(ino_t host_inode); 130 | static int MINIXCompat_File_HostWhenceForMINIXWhence(minix_whence_t minix_whence); 131 | 132 | static int16_t MINIXCompat_Dir_Precache(const char * _Nullable host_path, minix_fd_t minix_fd); 133 | static int16_t MINIXCompat_Dir_CheckIfDirAndCache(const char * _Nonnull host_path, minix_fd_t minix_fd); 134 | static int16_t MINIXCompat_Dir_Read(minix_fd_t minix_fd, void *host_buf, int16_t host_buf_size); 135 | static int16_t MINIXCompat_Dir_Seek(minix_fd_t minix_fd, minix_off_t minix_offset, minix_whence_t minix_whence); 136 | 137 | 138 | // MARK: - Initialization 139 | 140 | void MINIXCompat_Filesystem_Initialize(void) 141 | { 142 | assert(MINIXCOMPAT_DIR == NULL); 143 | 144 | // Establish the MINIX root directory. 145 | 146 | MINIXCOMPAT_DIR = getenv("MINIXCOMPAT_DIR"); 147 | if (MINIXCOMPAT_DIR == NULL) { 148 | MINIXCOMPAT_DIR = MINIXCOMPAT_DIR_default; 149 | setenv("MINIXCOMPAT_DIR", MINIXCOMPAT_DIR, 1); 150 | } 151 | 152 | // Also cache length of MINIXCOMPAT_DIR to avoid recomputing it. 153 | 154 | MINIXCOMPAT_DIR_len = strlen(MINIXCOMPAT_DIR); 155 | 156 | // Set up the CWD for MINIX and this process. 157 | 158 | MINIXCompat_CWD_Initialize(); 159 | 160 | // Set up the decriptor mapping table. 161 | 162 | for (minix_fd_t i = 0; i < MINIXCompat_fd_count; i++) { 163 | MINIXCompat_fd_ClearDescriptorEntry(i); 164 | } 165 | 166 | // Set up stdio to match the host. 167 | 168 | MINIXCompat_fd_table[0].host_fd = STDIN_FILENO; 169 | MINIXCompat_fd_table[0].minix_fd = 0; 170 | MINIXCompat_fd_table[0].f_type = f_file; 171 | 172 | MINIXCompat_fd_table[1].host_fd = STDOUT_FILENO; 173 | MINIXCompat_fd_table[1].minix_fd = 1; 174 | MINIXCompat_fd_table[1].f_type = f_file; 175 | 176 | MINIXCompat_fd_table[2].host_fd = STDERR_FILENO; 177 | MINIXCompat_fd_table[2].minix_fd = 2; 178 | MINIXCompat_fd_table[2].f_type = f_file; 179 | } 180 | 181 | 182 | // MARK: - Conversion to Host Paths 183 | 184 | char *MINIXCompat_Filesystem_CopyHostPathForPath(const char *path) 185 | { 186 | assert(MINIXCOMPAT_DIR != NULL); 187 | 188 | const size_t path_len = strlen(path); 189 | bool path_is_absolute = ((path_len > 0) && (path[0] == '/')); 190 | 191 | const char *base = path_is_absolute ? MINIXCOMPAT_DIR : MINIXCOMPAT_PWD_Host; 192 | const size_t base_len = path_is_absolute ? MINIXCOMPAT_DIR_len : MINIXCOMPAT_PWD_Host_len; 193 | 194 | const size_t out_path_len = base_len + 1 /* trailing slash */ + path_len; 195 | char *out_path = calloc(out_path_len + 1 /* trailing NUL */, sizeof(char)); 196 | 197 | strncat(out_path, base, out_path_len); 198 | 199 | if (!path_is_absolute) { 200 | strncat(out_path, "/", out_path_len); 201 | } 202 | 203 | strncat(out_path, path, out_path_len); 204 | 205 | return out_path; 206 | } 207 | 208 | 209 | // MARK: - Working Directory 210 | 211 | const char *MINIXCompat_Filesystem_CopyWorkingDirectory(void) 212 | { 213 | assert(MINIXCOMPAT_DIR != NULL); 214 | 215 | return strdup(MINIXCOMPAT_PWD); 216 | } 217 | 218 | const char *MINIXCompat_CopyHostWorkingDirectory(void) 219 | { 220 | assert(MINIXCOMPAT_DIR != NULL); 221 | 222 | return strdup(MINIXCOMPAT_PWD_Host); 223 | } 224 | 225 | void MINIXCompat_Filesystem_SetWorkingDirectory(const char *mwd) 226 | { 227 | assert(MINIXCOMPAT_DIR != NULL); 228 | 229 | char *old_pwd = MINIXCOMPAT_PWD; 230 | MINIXCOMPAT_PWD = strdup(mwd); 231 | free(old_pwd); 232 | MINIXCOMPAT_PWD_len = strlen(MINIXCOMPAT_PWD); 233 | 234 | char *old_host_pwd = MINIXCOMPAT_PWD_Host; 235 | MINIXCOMPAT_PWD_Host = MINIXCompat_Filesystem_CopyHostPathForPath(mwd); 236 | free(old_host_pwd); 237 | MINIXCOMPAT_PWD_Host_len = strlen(MINIXCOMPAT_PWD_Host); 238 | 239 | (void) chdir(MINIXCOMPAT_PWD_Host); 240 | } 241 | 242 | static bool MINIXCompat_PathContains(const char *path, const char *subpath) 243 | { 244 | const size_t path_len = (path == MINIXCOMPAT_DIR) ? MINIXCOMPAT_DIR_len : strlen(path); 245 | const size_t subpath_len = strlen(subpath); 246 | if (path_len <= subpath_len) { 247 | return (strncmp(path, subpath, path_len) == 0); 248 | } else { 249 | return false; 250 | } 251 | } 252 | 253 | void MINIXCompat_CWD_Initialize(void) 254 | { 255 | char *MINIXCOMPAT_PWD = getenv("MINIXCOMPAT_PWD"); 256 | if (MINIXCOMPAT_PWD) { 257 | // If there's a MINIXCOMPAT_PWD set, use that as it's assumed to be relative to MINIXCOMPAT_DIR. 258 | // FIXME: Validate MINIXCOMPAT_PWD is inside MINIXCOMPAT_DIR 259 | MINIXCompat_Filesystem_SetWorkingDirectory(MINIXCOMPAT_PWD); 260 | } else { 261 | // If there's no MINIXCOMPAT_PWD, use the host's working directory as long as it's in MINIXCOMPAT_DIR, otherwise fall back to root. 262 | char *cwd = getcwd(NULL, 0); 263 | if (MINIXCompat_PathContains(MINIXCOMPAT_DIR, cwd)) { 264 | // Strip the MINIXCOMPAT_DIR prefix from cwd. 265 | MINIXCompat_Filesystem_SetWorkingDirectory(&cwd[MINIXCOMPAT_DIR_len]); 266 | } else { 267 | // cwd is not inside MINIXCOMPAT_DIR, set it to root. 268 | MINIXCompat_Filesystem_SetWorkingDirectory("/"); 269 | } 270 | free(cwd); 271 | } 272 | } 273 | 274 | 275 | // MARK: - File Descriptors 276 | 277 | static bool MINIXCompat_fd_IsInRange(minix_fd_t minix_fd) 278 | { 279 | return ((minix_fd >= 0) && (minix_fd < MINIXCompat_fd_count)); 280 | } 281 | 282 | static bool MINIXCompat_fd_IsOpen(minix_fd_t minix_fd) 283 | { 284 | return (MINIXCompat_fd_table[minix_fd].minix_fd != -1); 285 | } 286 | 287 | static int MINIXCompat_fd_GetHostDescriptor(minix_fd_t minix_fd) 288 | { 289 | assert(MINIXCompat_fd_IsInRange(minix_fd)); 290 | 291 | return MINIXCompat_fd_table[minix_fd].host_fd; 292 | } 293 | 294 | static void MINIXCompat_fd_SetHostDescriptor(minix_fd_t minix_fd, int host_fd) 295 | { 296 | assert(MINIXCompat_fd_IsInRange(minix_fd)); 297 | 298 | MINIXCompat_fd_table[minix_fd].host_fd = host_fd; 299 | MINIXCompat_fd_table[minix_fd].minix_fd = minix_fd; 300 | MINIXCompat_fd_table[minix_fd].f_type = f_unchecked; 301 | } 302 | 303 | static void MINIXCompat_fd_ClearDescriptorEntry(minix_fd_t minix_fd) 304 | { 305 | assert(MINIXCompat_fd_IsInRange(minix_fd)); 306 | 307 | minix_fdmap_t *entry = &MINIXCompat_fd_table[minix_fd]; 308 | 309 | entry->host_fd = -1; 310 | entry->minix_fd = -1; 311 | entry->f_type = f_unchecked; 312 | free(entry->dir_entries); 313 | entry->dir_entries = NULL; 314 | entry->dir_count = -1; 315 | entry->dir_offset = -1; 316 | } 317 | 318 | static bool MINIXCompat_fd_IsDirectory(minix_fd_t minix_fd) 319 | { 320 | assert(MINIXCompat_fd_IsInRange(minix_fd)); 321 | assert(MINIXCompat_fd_IsOpen(minix_fd)); 322 | 323 | return (MINIXCompat_fd_table[minix_fd].f_type == f_directory); 324 | } 325 | 326 | static minix_fd_t MINIXCompat_fd_FindNextAvailable(void) 327 | { 328 | for (int minix_fd = 0; minix_fd < MINIXCompat_fd_count; minix_fd++) { 329 | if (MINIXCompat_fd_GetHostDescriptor(minix_fd) == -1) { 330 | return minix_fd; 331 | } 332 | } 333 | 334 | return -ENFILE; 335 | } 336 | 337 | 338 | // MARK: - Files 339 | 340 | /*! Convert MINIX open flags to host open flags. */ 341 | static int MINIXCompat_File_HostOpenFlagsForMINIXOpenFlags(minix_open_flags_t minix_flags) 342 | { 343 | int host_flags = 0; 344 | 345 | if (minix_flags & minix_O_CREAT) host_flags |= O_CREAT; 346 | if (minix_flags & minix_O_EXCL) host_flags |= O_EXCL; 347 | if (minix_flags & minix_O_NOCTTY) host_flags |= O_NOCTTY; 348 | if (minix_flags & minix_O_TRUNC) host_flags |= O_TRUNC; 349 | if (minix_flags & minix_O_APPEND) host_flags |= O_APPEND; 350 | if (minix_flags & minix_O_NONBLOCK) host_flags |= O_NONBLOCK; 351 | #if O_RDONLY != 0 352 | if (minix_flags & minix_O_RDONLY) host_flags |= O_RDONLY; 353 | #endif 354 | if (minix_flags & minix_O_WRONLY) host_flags |= O_WRONLY; 355 | if (minix_flags & minix_O_RDWR) host_flags |= O_RDWR; 356 | 357 | return host_flags; 358 | } 359 | 360 | static mode_t MINIXCompat_File_HostOpenModeForMINIXOpenMode(minix_mode_t minix_mode) 361 | { 362 | mode_t host_mode = 0; 363 | 364 | if (minix_mode & minix_S_IFREG) host_mode |= S_IFREG; 365 | if (minix_mode & minix_S_IFBLK) host_mode |= S_IFBLK; 366 | if (minix_mode & minix_S_IFDIR) host_mode |= S_IFDIR; 367 | if (minix_mode & minix_S_IFCHR) host_mode |= S_IFCHR; 368 | if (minix_mode & minix_S_IFIFO) host_mode |= S_IFIFO; 369 | if (minix_mode & minix_S_ISUID) host_mode |= S_ISUID; 370 | if (minix_mode & minix_S_ISGID) host_mode |= S_ISGID; 371 | 372 | if (minix_mode & minix_S_ISVTX) host_mode |= S_ISVTX; 373 | 374 | if (minix_mode & minix_S_IRUSR) host_mode |= S_IRUSR; 375 | if (minix_mode & minix_S_IWUSR) host_mode |= S_IWUSR; 376 | if (minix_mode & minix_S_IXUSR) host_mode |= S_IXUSR; 377 | 378 | if (minix_mode & minix_S_IRGRP) host_mode |= S_IRGRP; 379 | if (minix_mode & minix_S_IWGRP) host_mode |= S_IWGRP; 380 | if (minix_mode & minix_S_IXGRP) host_mode |= S_IXGRP; 381 | 382 | if (minix_mode & minix_S_IROTH) host_mode |= S_IROTH; 383 | if (minix_mode & minix_S_IWOTH) host_mode |= S_IWOTH; 384 | if (minix_mode & minix_S_IXOTH) host_mode |= S_IXOTH; 385 | 386 | return host_mode; 387 | } 388 | 389 | static minix_ino_t MINIXCompat_File_MINIXInodeForHostInode(ino_t host_inode) 390 | { 391 | // If this inode doesn't exist, pass that through unmodified. 392 | if (host_inode == 0) return 0; 393 | 394 | // Just let truncation happen and hope that it works. 395 | minix_ino_t minix_inode = host_inode; 396 | 397 | // If truncation results in a 0 minix_ino_t, make something up in a way that's both deterministic and unlikely to collide. 398 | if (minix_inode == 0) { 399 | // n.b. the below should only bother generating one of the branches 400 | if (sizeof(ino_t) == sizeof(uint32_t)) { 401 | // Add the two 16-bit words. 402 | uint32_t temp = ( ((host_inode & 0xffff0000) >> 16) 403 | + ((host_inode & 0x0000ffff) >> 0)); 404 | minix_inode = (temp & 0x0000ffff); 405 | } else if (sizeof(ino_t) == sizeof(uint64_t)) { 406 | // Add the four 16-bit words. 407 | uint64_t temp = ( ((host_inode & 0xffff000000000000) >> 48) 408 | + ((host_inode & 0x0000ffff00000000) >> 32) 409 | + ((host_inode & 0x00000000ffff0000) >> 16) 410 | + ((host_inode & 0x000000000000ffff) >> 0)); 411 | minix_inode = (temp & 0x000000000000ffff); 412 | } else { 413 | // We don't support non-16/32/64-bit inodes yet. 414 | assert(minix_inode != 0); 415 | } 416 | } 417 | 418 | return minix_inode; 419 | } 420 | 421 | static int MINIXCompat_File_HostWhenceForMINIXWhence(minix_whence_t minix_whence) 422 | { 423 | switch (minix_whence) { 424 | case minix_SEEK_SET: return SEEK_SET; 425 | case minix_SEEK_CUR: return SEEK_CUR; 426 | case minix_SEEK_END: return SEEK_END; 427 | } 428 | } 429 | 430 | static minix_fd_t MINIXCompat_File_OpenOrCreate(const char *minix_path, int16_t minix_flags, minix_mode_t minix_mode); 431 | 432 | minix_fd_t MINIXCompat_File_Create(const char *minix_path, minix_mode_t minix_mode) 433 | { 434 | minix_fd_t minix_fd = MINIXCompat_File_OpenOrCreate(minix_path, minix_O_CREAT | minix_O_TRUNC | minix_O_WRONLY, minix_mode); 435 | 436 | #if DEBUG_FILESYSTEM_SYSCALLS 437 | MINIXCompat_Log("creat(\"%s\", %o) -> %d", minix_path, minix_mode, minix_fd); 438 | #endif 439 | 440 | return minix_fd; 441 | } 442 | 443 | minix_fd_t MINIXCompat_File_Open(const char *minix_path, int16_t minix_flags, minix_mode_t minix_mode) 444 | { 445 | minix_fd_t minix_fd = MINIXCompat_File_OpenOrCreate(minix_path, minix_flags, minix_mode); 446 | 447 | #if DEBUG_FILESYSTEM_SYSCALLS 448 | MINIXCompat_Log("open(\"%s\", %06o, %06o) -> %d", minix_path, minix_flags, minix_mode, minix_fd); 449 | #endif 450 | 451 | return minix_fd; 452 | } 453 | 454 | static minix_fd_t MINIXCompat_File_OpenOrCreate(const char *minix_path, int16_t minix_flags, minix_mode_t minix_mode) 455 | { 456 | int16_t result; 457 | 458 | assert(minix_path != NULL); 459 | int host_flags = MINIXCompat_File_HostOpenFlagsForMINIXOpenFlags(minix_flags); 460 | int host_mode = MINIXCompat_File_HostOpenModeForMINIXOpenMode(minix_mode); 461 | 462 | minix_fd_t minix_fd = MINIXCompat_fd_FindNextAvailable(); 463 | if (minix_fd >= 0) { 464 | char *host_path = MINIXCompat_Filesystem_CopyHostPathForPath(minix_path); 465 | 466 | // Open the file. 467 | 468 | int host_fd = open(host_path, host_flags, host_mode); 469 | if (host_fd >= 0) { 470 | // Save the association. 471 | 472 | MINIXCompat_fd_SetHostDescriptor(minix_fd, host_fd); 473 | 474 | // Check and record whether the newly-opened file is a directory, and do any necessary bookkeeping if so. 475 | // That will only fail if the open itself should fail. 476 | 477 | int16_t diropen_result = MINIXCompat_Dir_CheckIfDirAndCache(host_path, minix_fd); 478 | if (diropen_result < 0) { 479 | minix_fd = diropen_result; 480 | (void) close(host_fd); 481 | MINIXCompat_fd_ClearDescriptorEntry(minix_fd); 482 | } 483 | result = minix_fd; 484 | } else { 485 | result = -MINIXCompat_Errors_MINIXErrorForHostError(errno); 486 | } 487 | 488 | free(host_path); 489 | } else { 490 | result = -MINIXCompat_Errors_MINIXErrorForHostError(ENFILE); 491 | } 492 | 493 | return result; 494 | } 495 | 496 | int16_t MINIXCompat_File_Close(minix_fd_t minix_fd) 497 | { 498 | int16_t result; 499 | 500 | assert(MINIXCompat_fd_IsInRange(minix_fd)); 501 | 502 | int host_fd = MINIXCompat_fd_GetHostDescriptor(minix_fd); 503 | 504 | int close_result = close(host_fd); 505 | if (close_result == -1) { 506 | result = -MINIXCompat_Errors_MINIXErrorForHostError(errno); 507 | } else { 508 | result = close_result; 509 | } 510 | 511 | MINIXCompat_fd_ClearDescriptorEntry(minix_fd); 512 | 513 | #if DEBUG_FILESYSTEM_SYSCALLS 514 | MINIXCompat_Log("close(%d) -> %d", minix_fd, result); 515 | #endif 516 | 517 | return result; 518 | } 519 | 520 | int16_t MINIXCompat_File_Mkdir(const char *minix_path, minix_mode_t minix_mode) 521 | { 522 | int16_t result; 523 | 524 | assert(minix_path != NULL); 525 | int host_mode = MINIXCompat_File_HostOpenModeForMINIXOpenMode(minix_mode); 526 | 527 | int mkdir_result= mkdir(minix_path, host_mode); 528 | if (mkdir_result == -1) { 529 | result = -MINIXCompat_Errors_MINIXErrorForHostError(errno); 530 | } else { 531 | result = mkdir_result; 532 | } 533 | 534 | #if DEBUG_FILESYSTEM_SYSCALLS 535 | MINIXCompat_Log("mkdir(\"%s\", %o) -> %d", minix_path, minix_mode, result); 536 | #endif 537 | 538 | return result; 539 | } 540 | 541 | int16_t MINIXCompat_File_Read(minix_fd_t minix_fd, void *host_buf, int16_t host_buf_size) 542 | { 543 | int16_t result; 544 | 545 | assert(MINIXCompat_fd_IsInRange(minix_fd)); 546 | assert(MINIXCompat_fd_IsOpen(minix_fd)); 547 | assert(host_buf != NULL); 548 | assert(host_buf_size > 0); 549 | 550 | if (MINIXCompat_fd_IsDirectory(minix_fd)) { 551 | // Handle directories specially, since readdir et al are userspace on MINIX. 552 | 553 | result = MINIXCompat_Dir_Read(minix_fd, host_buf, host_buf_size); 554 | } else { 555 | int host_fd = MINIXCompat_fd_GetHostDescriptor(minix_fd); 556 | if (host_fd >= 0) { 557 | ssize_t bytesread = read(host_fd, host_buf, host_buf_size); 558 | if (bytesread < 0) { 559 | result = -MINIXCompat_Errors_MINIXErrorForHostError(errno); 560 | } else { 561 | result = bytesread; 562 | } 563 | } else { 564 | result = -MINIXCompat_Errors_MINIXErrorForHostError(ENFILE); 565 | } 566 | } 567 | 568 | #if DEBUG_FILESYSTEM_SYSCALLS 569 | MINIXCompat_Log("read(%d, %p, %d) -> %d", minix_fd, host_buf, host_buf_size, result); 570 | #endif 571 | 572 | return result; 573 | } 574 | 575 | int16_t MINIXCompat_File_Write(minix_fd_t minix_fd, void *host_buf, int16_t host_buf_size) 576 | { 577 | int16_t result; 578 | 579 | assert(MINIXCompat_fd_IsInRange(minix_fd)); 580 | assert(MINIXCompat_fd_IsOpen(minix_fd)); 581 | assert(!MINIXCompat_fd_IsDirectory(minix_fd)); 582 | assert(host_buf != NULL); 583 | assert(host_buf_size >= 0); 584 | 585 | int host_fd = MINIXCompat_fd_GetHostDescriptor(minix_fd); 586 | if (host_fd >= 0) { 587 | ssize_t byteswritten = write(host_fd, host_buf, host_buf_size); 588 | if (byteswritten < 0) { 589 | result = -MINIXCompat_Errors_MINIXErrorForHostError(errno); 590 | } else { 591 | result = byteswritten; 592 | } 593 | } else { 594 | result = -MINIXCompat_Errors_MINIXErrorForHostError(ENFILE); 595 | } 596 | 597 | #if DEBUG_FILESYSTEM_SYSCALLS 598 | MINIXCompat_Log("write(%d, %p, %d) -> %d", minix_fd, host_buf, host_buf_size, result); 599 | #endif 600 | 601 | return result; 602 | } 603 | 604 | int16_t MINIXCompat_File_Seek(minix_fd_t minix_fd, minix_off_t minix_offset, int16_t minix_whence) 605 | { 606 | int16_t result; 607 | 608 | assert(MINIXCompat_fd_IsInRange(minix_fd)); 609 | assert(MINIXCompat_fd_IsOpen(minix_fd)); 610 | 611 | if (MINIXCompat_fd_IsDirectory(minix_fd)) { 612 | // Handle directories specially, since readdir et al are userspace on MINIX. 613 | 614 | return MINIXCompat_Dir_Seek(minix_fd, minix_offset, minix_whence); 615 | } else { 616 | int host_fd = MINIXCompat_fd_GetHostDescriptor(minix_fd); 617 | off_t host_offset = minix_offset; 618 | int host_whence = MINIXCompat_File_HostWhenceForMINIXWhence(minix_whence); 619 | 620 | off_t seek_result = lseek(host_fd, host_offset, host_whence); 621 | if (seek_result < 0) { 622 | result = -MINIXCompat_Errors_MINIXErrorForHostError(errno); 623 | } else { 624 | result = seek_result; 625 | } 626 | } 627 | 628 | #if DEBUG_FILESYSTEM_SYSCALLS 629 | MINIXCompat_Log("lseek(%d, %d, %d) -> %d", minix_fd, minix_offset, minix_whence, result); 630 | #endif 631 | 632 | return result; 633 | } 634 | 635 | void MINIXCompat_File_StatSwap(minix_stat_t * _Nonnull minix_stat_buf) 636 | { 637 | HTONS(minix_stat_buf->st_dev); 638 | HTONS(minix_stat_buf->st_ino); 639 | HTONS(minix_stat_buf->st_mode); 640 | HTONS(minix_stat_buf->st_nlink); 641 | HTONS(minix_stat_buf->st_uid); 642 | HTONS(minix_stat_buf->st_gid); 643 | HTONS(minix_stat_buf->st_rdev); 644 | HTONL(minix_stat_buf->st_size); 645 | HTONL(minix_stat_buf->minix_st_atime); 646 | HTONL(minix_stat_buf->minix_st_mtime); 647 | HTONL(minix_stat_buf->minix_st_ctime); 648 | } 649 | 650 | static minix_mode_t MINIXCompat_File_MINIXStatModeForHostStatMode(mode_t host_mode) 651 | { 652 | minix_mode_t minix_mode = 0; 653 | 654 | if ((host_mode & S_IFREG) == S_IFREG) minix_mode |= minix_S_IFREG; 655 | if ((host_mode & S_IFREG) == S_IFBLK) minix_mode |= minix_S_IFBLK; 656 | if ((host_mode & S_IFDIR) == S_IFDIR) minix_mode |= minix_S_IFDIR; 657 | if ((host_mode & S_IFCHR) == S_IFCHR) minix_mode |= minix_S_IFCHR; 658 | if ((host_mode & S_IFIFO) == S_IFIFO) minix_mode |= minix_S_IFIFO; 659 | 660 | if (host_mode & S_ISUID) minix_mode |= minix_S_ISUID; 661 | if (host_mode & S_ISGID) minix_mode |= minix_S_ISGID; 662 | 663 | if (host_mode & S_ISVTX) minix_mode |= minix_S_ISVTX; 664 | 665 | if (host_mode & S_IRUSR) minix_mode |= minix_S_IRUSR; 666 | if (host_mode & S_IWUSR) minix_mode |= minix_S_IWUSR; 667 | if (host_mode & S_IXUSR) minix_mode |= minix_S_IXUSR; 668 | 669 | if (host_mode & S_IRGRP) minix_mode |= minix_S_IRGRP; 670 | if (host_mode & S_IWGRP) minix_mode |= minix_S_IWGRP; 671 | if (host_mode & S_IXGRP) minix_mode |= minix_S_IXGRP; 672 | 673 | if (host_mode & S_IROTH) minix_mode |= minix_S_IROTH; 674 | if (host_mode & S_IWOTH) minix_mode |= minix_S_IWOTH; 675 | if (host_mode & S_IXOTH) minix_mode |= minix_S_IXOTH; 676 | 677 | return minix_mode; 678 | } 679 | 680 | static minix_off_t MINIXCompat_File_MINIXStatSizeForHostStatSize(off_t host_size) 681 | { 682 | minix_off_t minix_size; 683 | if (host_size >= 0x7FFFFFFF) { 684 | minix_size = 0x7FFFFFFF; // clamp to 2GB - 1 685 | } else { 686 | minix_size = (minix_off_t) host_size; 687 | } 688 | return minix_size; 689 | } 690 | 691 | static void MINIXCompat_File_MINIXStatBufForHostStatBuf(minix_stat_t * _Nonnull minix_stat_buf, struct stat * _Nonnull host_stat_buf) 692 | { 693 | minix_stat_buf->st_dev = host_stat_buf->st_dev; //xxx translate? 694 | minix_stat_buf->st_ino = MINIXCompat_File_MINIXInodeForHostInode(host_stat_buf->st_ino); 695 | minix_stat_buf->st_mode = MINIXCompat_File_MINIXStatModeForHostStatMode(host_stat_buf->st_mode); 696 | minix_stat_buf->st_nlink = host_stat_buf->st_nlink; 697 | minix_stat_buf->st_uid = host_stat_buf->st_uid; //xxx translate? 698 | minix_stat_buf->st_gid = host_stat_buf->st_gid; //xxx translate? 699 | minix_stat_buf->st_rdev = host_stat_buf->st_rdev; //xxx translate? 700 | minix_stat_buf->st_size = MINIXCompat_File_MINIXStatSizeForHostStatSize(host_stat_buf->st_size); 701 | minix_stat_buf->minix_st_atime = (minix_time_t) host_stat_buf->st_atime; 702 | minix_stat_buf->minix_st_mtime = (minix_time_t) host_stat_buf->st_mtime; 703 | minix_stat_buf->minix_st_ctime = (minix_time_t) host_stat_buf->st_ctime; 704 | } 705 | 706 | int16_t MINIXCompat_File_Stat(const char * _Nonnull minix_path, minix_stat_t * _Nonnull minix_stat_buf) 707 | { 708 | int16_t result; 709 | 710 | assert(minix_path != NULL); 711 | assert(minix_stat_buf != NULL); 712 | 713 | char *host_path = MINIXCompat_Filesystem_CopyHostPathForPath(minix_path); 714 | 715 | struct stat host_stat_buf; 716 | int stat_err = stat(host_path, &host_stat_buf); 717 | if (stat_err == 0) { 718 | MINIXCompat_File_MINIXStatBufForHostStatBuf(minix_stat_buf, &host_stat_buf); 719 | 720 | // Swap for passing back to MINIX. 721 | MINIXCompat_File_StatSwap(minix_stat_buf); 722 | result = 0; 723 | } else { 724 | result = -MINIXCompat_Errors_MINIXErrorForHostError(errno); 725 | } 726 | 727 | free(host_path); 728 | 729 | #if DEBUG_FILESYSTEM_SYSCALLS 730 | MINIXCompat_Log("stat(\"%s\", %p) -> %d", minix_path, minix_stat_buf, result); 731 | #endif 732 | 733 | return result; 734 | } 735 | 736 | int16_t MINIXCompat_File_StatOpen(minix_fd_t minix_fd, minix_stat_t * _Nonnull minix_stat_buf) 737 | { 738 | int16_t result; 739 | 740 | assert(MINIXCompat_fd_IsInRange(minix_fd)); 741 | assert(MINIXCompat_fd_IsOpen(minix_fd)); 742 | assert(minix_stat_buf != NULL); 743 | 744 | int host_fd = MINIXCompat_fd_GetHostDescriptor(minix_fd); 745 | 746 | struct stat host_stat_buf; 747 | int stat_err = fstat(host_fd, &host_stat_buf); 748 | if (stat_err == 0) { 749 | MINIXCompat_File_MINIXStatBufForHostStatBuf(minix_stat_buf, &host_stat_buf); 750 | 751 | // Swap for passing back to MINIX. 752 | MINIXCompat_File_StatSwap(minix_stat_buf); 753 | result = 0; 754 | } else { 755 | result = -MINIXCompat_Errors_MINIXErrorForHostError(errno); 756 | } 757 | 758 | #if DEBUG_FILESYSTEM_SYSCALLS 759 | MINIXCompat_Log("fstat(%d, %p) -> %d", minix_fd, minix_stat_buf, result); 760 | #endif 761 | 762 | return result; 763 | } 764 | 765 | int16_t MINIXCompat_File_Unlink(const char *minix_path) 766 | { 767 | int16_t result; 768 | 769 | assert(minix_path != NULL); 770 | 771 | char *host_path = MINIXCompat_Filesystem_CopyHostPathForPath(minix_path); 772 | 773 | int unlink_err = unlink(host_path); 774 | if (unlink_err == 0) { 775 | result = 0; 776 | } else { 777 | result = -MINIXCompat_Errors_MINIXErrorForHostError(errno); 778 | } 779 | 780 | free(host_path); 781 | 782 | #if DEBUG_FILESYSTEM_SYSCALLS 783 | MINIXCompat_Log("unlink(\"%s\") -> %d", minix_path, result); 784 | #endif 785 | 786 | return result; 787 | } 788 | 789 | int16_t MINIXCompat_File_Link(const char *minix_path, const char *minix_path2) 790 | { 791 | int16_t result; 792 | 793 | assert(minix_path != NULL); 794 | assert(minix_path2 != NULL); 795 | 796 | char *host_path = MINIXCompat_Filesystem_CopyHostPathForPath(minix_path); 797 | char *host_path2 = MINIXCompat_Filesystem_CopyHostPathForPath(minix_path2); 798 | 799 | int link_err = link(host_path, host_path2); 800 | if (link_err == 0) { 801 | result = 0; 802 | } else { 803 | result = -MINIXCompat_Errors_MINIXErrorForHostError(errno); 804 | } 805 | 806 | free(host_path); 807 | free(host_path2); 808 | 809 | #if DEBUG_FILESYSTEM_SYSCALLS 810 | MINIXCompat_Log("link(\"%s\", \"%s\") -> %d", minix_path, minix_path2, result); 811 | #endif 812 | 813 | return result; 814 | } 815 | 816 | int16_t MINIXCompat_File_Rename(const char *minix_path, const char *minix_path2) 817 | { 818 | int16_t result; 819 | 820 | assert(minix_path != NULL); 821 | assert(minix_path2 != NULL); 822 | 823 | char *host_path = MINIXCompat_Filesystem_CopyHostPathForPath(minix_path); 824 | char *host_path2 = MINIXCompat_Filesystem_CopyHostPathForPath(minix_path2); 825 | 826 | int rename_err = rename(host_path, host_path2); 827 | if (rename_err == 0) { 828 | result = 0; 829 | } else { 830 | result = -MINIXCompat_Errors_MINIXErrorForHostError(errno); 831 | } 832 | 833 | free(host_path); 834 | free(host_path2); 835 | 836 | #if DEBUG_FILESYSTEM_SYSCALLS 837 | MINIXCompat_Log("rename(\"%s\", \"%s\") -> %d", minix_path, minix_path2, result); 838 | #endif 839 | 840 | return result; 841 | } 842 | 843 | minix_fd_t MINIXCompat_File_Access(const char *minix_path, minix_mode_t minix_mode) 844 | { 845 | int16_t result; 846 | 847 | assert(minix_path != NULL); 848 | 849 | char *host_path = MINIXCompat_Filesystem_CopyHostPathForPath(minix_path); 850 | mode_t host_mode = MINIXCompat_File_HostOpenModeForMINIXOpenMode(minix_mode); 851 | 852 | int access_err = access(host_path, host_mode); 853 | if (access_err == -1) { 854 | result = -MINIXCompat_Errors_MINIXErrorForHostError(errno); 855 | } else { 856 | result = 0; 857 | } 858 | 859 | free(host_path); 860 | 861 | #if DEBUG_FILESYSTEM_SYSCALLS 862 | MINIXCompat_Log("access(\"%s\", %06o) -> %d", minix_path, minix_mode, result); 863 | #endif 864 | 865 | return result; 866 | } 867 | 868 | minix_fd_t MINIXCompat_File_Chdir(const char *minix_path) 869 | { 870 | int16_t result; 871 | 872 | assert(minix_path != NULL); 873 | 874 | char *host_path = MINIXCompat_Filesystem_CopyHostPathForPath(minix_path); 875 | 876 | int chdir_err = chdir(host_path); 877 | if (chdir_err == -1) { 878 | result = -MINIXCompat_Errors_MINIXErrorForHostError(errno); 879 | } else { 880 | result = 0; 881 | } 882 | 883 | free(host_path); 884 | 885 | #if DEBUG_FILESYSTEM_SYSCALLS 886 | MINIXCompat_Log("chdir(\"%s\") -> %d", minix_path, result); 887 | #endif 888 | 889 | return result; 890 | } 891 | 892 | /*! Change a file's permissions */ 893 | int16_t MINIXCompat_File_Chmod(const char *minix_path, minix_mode_t minix_mode) 894 | { 895 | int16_t result; 896 | 897 | assert(minix_path != NULL); 898 | int host_mode = MINIXCompat_File_HostOpenModeForMINIXOpenMode(minix_mode); 899 | char *host_path = MINIXCompat_Filesystem_CopyHostPathForPath(minix_path); 900 | 901 | int chmod_err = chmod(host_path, host_mode); 902 | if (chmod_err == 0) { 903 | result = 0; 904 | } else { 905 | result = -MINIXCompat_Errors_MINIXErrorForHostError(errno); 906 | } 907 | 908 | free(host_path); 909 | 910 | #if DEBUG_FILESYSTEM_SYSCALLS 911 | MINIXCompat_Log("chmod(\"%s\", %o) -> %d", minix_path, minix_mode, result); 912 | #endif 913 | 914 | return result; 915 | } 916 | 917 | 918 | // MARK: - Directories 919 | 920 | /*! Pre-cache a directory for reading. */ 921 | static int16_t MINIXCompat_Dir_Precache(const char * _Nullable host_path, minix_fd_t minix_fd) 922 | { 923 | // Open the directory for iteration. 924 | 925 | DIR *dir = opendir(host_path); 926 | if (dir == NULL) { 927 | return -MINIXCompat_Errors_MINIXErrorForHostError(errno); 928 | } 929 | 930 | // Create a minix_dirent_t for every corresponding struct dirent, resizing our table as needed. 931 | 932 | bool done_reading = false; 933 | size_t entry_count = 0; 934 | size_t dircache_count = 32; // start at 32 since most directories have fewer entries, and 32 are one MINIX block 935 | 936 | minix_dirent_t *dircache = calloc(dircache_count, sizeof(minix_dirent_t)); 937 | 938 | do { 939 | errno = 0; // errno will be unchanged for end-of-directory 940 | struct dirent *entry = readdir(dir); 941 | if (entry == NULL) { 942 | int host_errno = errno; 943 | if (host_errno != 0) { // errno is unchanged for end-of-directory 944 | return -MINIXCompat_Errors_MINIXErrorForHostError(host_errno); 945 | } else { 946 | // Read has finished successfully. Close the directory. 947 | closedir(dir); 948 | dir = NULL; 949 | } 950 | done_reading = true; 951 | } else { 952 | // Expand the cache if needed. 953 | 954 | if (entry_count >= dircache_count) { 955 | // Always increase size by one block's worth so it doesn't grow too quickly. 956 | size_t new_dircache_count = (dircache_count + 32); 957 | minix_dirent_t *new_dircache = calloc(new_dircache_count, sizeof(minix_dirent_t)); 958 | memcpy(new_dircache, dircache, dircache_count * sizeof(minix_dirent_t)); 959 | free(dircache); 960 | dircache = new_dircache; 961 | 962 | // Update number of available entries. 963 | dircache_count = new_dircache_count; 964 | } 965 | 966 | // MINIX just wants inode and 14-character name. 967 | 968 | dircache[entry_count].d_ino = htons(MINIXCompat_File_MINIXInodeForHostInode(entry->d_ino)); 969 | strncpy(dircache[entry_count].d_name, entry->d_name, 14); 970 | 971 | entry_count += 1; 972 | } 973 | } while (!done_reading); 974 | 975 | // Save the MINIX directory entries in the descriptor map. 976 | 977 | MINIXCompat_fd_table[minix_fd].dir_entries = dircache; 978 | MINIXCompat_fd_table[minix_fd].dir_count = dircache_count; 979 | MINIXCompat_fd_table[minix_fd].dir_offset = 0; 980 | 981 | return 0; 982 | } 983 | 984 | /*! A version of `stat(2)` that handles `EINTR` and returns `-errno` instead of just `-1` on error. */ 985 | static int stat_without_EINTR(const char * _Nonnull path, struct stat * _Nonnull sbuf) 986 | { 987 | int result; 988 | bool stat_done = false; 989 | 990 | do { 991 | result = stat(path, sbuf); 992 | if (result == -1) { 993 | if (errno != EINTR) { 994 | result = -EINTR; 995 | stat_done = true; 996 | } else { 997 | // Just loop until success or a real failure. Thanks, UNIX. 998 | } 999 | } else { 1000 | stat_done = true; 1001 | } 1002 | } while (!stat_done); 1003 | 1004 | return result; 1005 | } 1006 | 1007 | static int16_t MINIXCompat_Dir_CheckIfDirAndCache(const char * _Nonnull host_path, minix_fd_t minix_fd) 1008 | { 1009 | int16_t result; 1010 | 1011 | bool is_directory; 1012 | struct stat sbuf; 1013 | int stat_result = stat_without_EINTR(host_path, &sbuf); 1014 | if (stat_result == 0) { 1015 | // Indicate whether the fd corresponds to a directory. 1016 | is_directory = S_ISDIR(sbuf.st_mode); 1017 | MINIXCompat_fd_table[minix_fd].f_type = is_directory ? f_directory : f_file; 1018 | result = 0; 1019 | } else { 1020 | is_directory = false; 1021 | result = MINIXCompat_Errors_MINIXErrorForHostError(-stat_result); 1022 | } 1023 | 1024 | // If that was successful, and the file is a directory, also pre-cache its entries at open(2) time. 1025 | // NOTE: Since we're in the middle of opening, don't use IsOpen, IsDirectory, etc. 1026 | 1027 | if ((result == 0) && is_directory) { 1028 | // If the fd is a directory, pre-cache its entries, failing the open if that fails. 1029 | int16_t precache_result = MINIXCompat_Dir_Precache(host_path, minix_fd); 1030 | result = precache_result; 1031 | } 1032 | 1033 | return result; 1034 | } 1035 | 1036 | /*! Read and return many entries from the directory into \a host_buf as are appropriate for \a host_buf_size. */ 1037 | static int16_t MINIXCompat_Dir_Read(minix_fd_t minix_fd, void *host_buf, int16_t host_buf_size) 1038 | { 1039 | int16_t result; 1040 | minix_fdmap_t *entry = &MINIXCompat_fd_table[minix_fd]; 1041 | 1042 | const minix_off_t max_off_plus_one = entry->dir_count * sizeof(minix_dirent_t); 1043 | const minix_off_t cur_off = entry->dir_offset; 1044 | 1045 | if ((cur_off + host_buf_size) <= max_off_plus_one) { 1046 | uint8_t *raw_dir_entries = (uint8_t *)entry->dir_entries; 1047 | memcpy(host_buf, raw_dir_entries + cur_off, host_buf_size); 1048 | entry->dir_offset += host_buf_size; 1049 | result = host_buf_size; 1050 | } else { 1051 | result = MINIXCompat_Errors_MINIXErrorForHostError(EIO); 1052 | } 1053 | 1054 | return result; 1055 | } 1056 | 1057 | /*! Seek within a directory. */ 1058 | static int16_t MINIXCompat_Dir_Seek(minix_fd_t minix_fd, minix_off_t minix_offset, minix_whence_t minix_whence) 1059 | { 1060 | int16_t result; 1061 | 1062 | assert(MINIXCompat_fd_IsInRange(minix_fd)); 1063 | assert(MINIXCompat_fd_IsOpen(minix_fd)); 1064 | assert(MINIXCompat_fd_IsDirectory(minix_fd)); 1065 | 1066 | minix_fdmap_t *entry = &MINIXCompat_fd_table[minix_fd]; 1067 | 1068 | const minix_off_t min_off = 0; 1069 | const minix_off_t max_off = entry->dir_count * sizeof(minix_dirent_t) - 1; 1070 | minix_off_t new_off; 1071 | 1072 | switch (minix_whence) { 1073 | case minix_SEEK_SET: { 1074 | new_off = min_off + minix_offset; 1075 | } break; 1076 | 1077 | case minix_SEEK_CUR: { 1078 | new_off = entry->dir_offset + minix_offset; 1079 | } break; 1080 | 1081 | case minix_SEEK_END: { 1082 | new_off = max_off + minix_offset; 1083 | } break; 1084 | } 1085 | 1086 | if ((new_off < 0) || (new_off > max_off)) { 1087 | result = -minix_EINVAL; 1088 | } else { 1089 | entry->dir_offset = new_off; 1090 | result = 0; 1091 | } 1092 | 1093 | return result; 1094 | } 1095 | 1096 | 1097 | MINIXCOMPAT_SOURCE_END 1098 | -------------------------------------------------------------------------------- /MINIXCompat/MINIXCompat_Filesystem.h: -------------------------------------------------------------------------------- 1 | // 2 | // MINIXCompat_Filesystem.h 3 | // MINIXCompat 4 | // 5 | // Created by Chris Hanson on 11/16/24. 6 | // Copyright © 2024 Christopher M. Hanson. See file LICENSE for details. 7 | // 8 | 9 | #ifndef MINIXCompat_Filesystem_h 10 | #define MINIXCompat_Filesystem_h 11 | 12 | #include "MINIXCompat_Types.h" 13 | 14 | 15 | MINIXCOMPAT_HEADER_BEGIN 16 | 17 | 18 | /*! Initialize the "filesystem" subsystem. */ 19 | MINIXCOMPAT_EXTERN void MINIXCompat_Filesystem_Initialize(void); 20 | 21 | /*! 22 | Construct a full host path for the given MINIX path. 23 | 24 | - Note: Always returns an absolute path, using the MINIX current working directory into account if \a path is not absolute. 25 | */ 26 | MINIXCOMPAT_EXTERN char *MINIXCompat_Filesystem_CopyHostPathForPath(const char *path); 27 | 28 | /*! Copy the current MINIX working directory. */ 29 | MINIXCOMPAT_EXTERN const char *MINIXCompat_Filesystem_CopyWorkingDirectory(void); 30 | 31 | /*! Copy the current MINIX working directory as a host path. */ 32 | 33 | /*! Set the current MINIX working directory. */ 34 | MINIXCOMPAT_EXTERN void MINIXCompat_Filesystem_SetWorkingDirectory(const char *mwd); 35 | 36 | 37 | /*! A MINIX file descriptor, which must always be positive; a negative value represents `-errno`. */ 38 | typedef int16_t minix_fd_t; 39 | 40 | 41 | /*! MINIX-side file open flags */ 42 | typedef enum minix_open_flags : uint16_t { 43 | minix_O_CREAT = 00100, 44 | minix_O_EXCL = 00200, 45 | minix_O_NOCTTY = 00400, 46 | minix_O_TRUNC = 01000, 47 | minix_O_APPEND = 02000, 48 | minix_O_NONBLOCK = 04000, 49 | minix_O_RDONLY = 00000, 50 | minix_O_WRONLY = 00001, 51 | minix_O_RDWR = 00002, 52 | } minix_open_flags_t; 53 | 54 | 55 | /*! MINIX-side stat mode flags */ 56 | typedef enum minix_mode : uint16_t { 57 | minix_S_IFMT = 0170000, 58 | minix_S_IFREG = 0100000, 59 | minix_S_IFBLK = 0060000, 60 | minix_S_IFDIR = 0040000, 61 | minix_S_IFCHR = 0020000, 62 | minix_S_IFIFO = 0010000, 63 | minix_S_ISUID = 0004000, 64 | minix_S_ISGID = 0002000, 65 | 66 | minix_S_ISVTX = 01000, 67 | 68 | minix_S_IRWXU = 00700, 69 | minix_S_IRUSR = 00400, 70 | minix_S_IWUSR = 00200, 71 | minix_S_IXUSR = 00100, 72 | 73 | minix_S_IRWXG = 00070, 74 | minix_S_IRGRP = 00040, 75 | minix_S_IWGRP = 00020, 76 | minix_S_IXGRP = 00010, 77 | 78 | minix_S_IRWXO = 00007, 79 | minix_S_IROTH = 00004, 80 | minix_S_IWOTH = 00002, 81 | minix_S_IXOTH = 00001, 82 | } minix_mode_t; 83 | 84 | 85 | typedef uint16_t minix_dev_t; 86 | typedef uint16_t minix_ino_t; 87 | typedef int16_t minix_uid_t; 88 | typedef int16_t minix_gid_t; 89 | typedef int32_t minix_off_t; 90 | typedef int32_t minix_time_t; 91 | 92 | 93 | /*! A MINIX stat structure, in either host or MINIX byte order.. */ 94 | typedef struct minix_stat { 95 | minix_dev_t st_dev; 96 | minix_ino_t st_ino; 97 | minix_mode_t st_mode; 98 | int16_t st_nlink; 99 | minix_uid_t st_uid; 100 | minix_gid_t st_gid; 101 | minix_dev_t st_rdev; 102 | minix_off_t st_size; 103 | minix_time_t minix_st_atime; // Prefixed to avoid macros on Darwin. 104 | minix_time_t minix_st_mtime; // Prefixed to avoid macros on Darwin. 105 | minix_time_t minix_st_ctime; // Prefixed to avoid macros on Darwin. 106 | } MINIXCOMPAT_PACK_STRUCT minix_stat_t; 107 | 108 | 109 | /*! The position from which to seek. */ 110 | typedef enum minix_whence : int16_t { 111 | minix_SEEK_SET = 0, 112 | minix_SEEK_CUR = 1, 113 | minix_SEEK_END = 2, 114 | } minix_whence_t; 115 | 116 | 117 | /*! 118 | Create the file at the given MINIX relative or absolute path, with the given MINIX mode. 119 | 120 | - Returns: A MINIX file descriptor, or `-errno` upon error. 121 | */ 122 | MINIXCOMPAT_EXTERN minix_fd_t MINIXCompat_File_Create(const char *minix_path, minix_mode_t minix_mode); 123 | 124 | /*! 125 | Open the file at the given MINIX relative or absolute path, with the given MINIX flags (and mode, if appropriate). 126 | 127 | - Returns: A MINIX file descriptor, or `-errno` upon error. 128 | */ 129 | MINIXCOMPAT_EXTERN minix_fd_t MINIXCompat_File_Open(const char *minix_path, int16_t minix_flags, minix_mode_t minix_mode); 130 | 131 | /*! Close the file with the given MINIX file descriptor. */ 132 | MINIXCOMPAT_EXTERN int16_t MINIXCompat_File_Close(minix_fd_t fd); 133 | 134 | /*! Make the directory with the given MINIX mode */ 135 | MINIXCOMPAT_EXTERN int16_t MINIXCompat_File_Mkdir(const char *minix_path, minix_mode_t minix_mode); 136 | 137 | /*! Reads the specified amount of data from the given file descriptor into the given host-side buffer. Returns the number of bytes read or `-errno` on error. */ 138 | MINIXCOMPAT_EXTERN int16_t MINIXCompat_File_Read(minix_fd_t fd, void * _Nonnull buf, int16_t buf_size); 139 | 140 | MINIXCOMPAT_EXTERN int16_t MINIXCompat_File_Write(minix_fd_t fd, void * _Nonnull buf, int16_t buf_size); 141 | 142 | MINIXCOMPAT_EXTERN int16_t MINIXCompat_File_Seek(minix_fd_t fd, minix_off_t offset, minix_whence_t minix_whence); 143 | 144 | MINIXCOMPAT_EXTERN void MINIXCompat_File_StatSwap(minix_stat_t * _Nonnull minix_stat_buf); 145 | 146 | MINIXCOMPAT_EXTERN int16_t MINIXCompat_File_Stat(const char * _Nonnull minix_path, minix_stat_t * _Nonnull minix_stat_buf); 147 | 148 | MINIXCOMPAT_EXTERN int16_t MINIXCompat_File_StatOpen(minix_fd_t minix_fd, minix_stat_t * _Nonnull minix_stat_buf); 149 | 150 | MINIXCOMPAT_EXTERN int16_t MINIXCompat_File_Unlink(const char *minix_path); 151 | MINIXCOMPAT_EXTERN int16_t MINIXCompat_File_Link(const char *minix_path, const char *minix_path2); 152 | MINIXCOMPAT_EXTERN int16_t MINIXCompat_File_Rename(const char *minix_path, const char *minix_path2); 153 | 154 | MINIXCOMPAT_EXTERN minix_fd_t MINIXCompat_File_Access(const char *minix_path, minix_mode_t minix_mode); 155 | MINIXCOMPAT_EXTERN minix_fd_t MINIXCompat_File_Chdir(const char *minix_path); 156 | MINIXCOMPAT_EXTERN int16_t MINIXCompat_File_Chmod(const char *minix_path, minix_mode_t minix_mode); 157 | 158 | 159 | MINIXCOMPAT_HEADER_END 160 | 161 | 162 | #endif /* MINIXCompat_Filesystem_h */ 163 | -------------------------------------------------------------------------------- /MINIXCompat/MINIXCompat_Logging.c: -------------------------------------------------------------------------------- 1 | // 2 | // MINIXCompat_Logging.c 3 | // MINIXCompat 4 | // 5 | // Created by Chris Hanson on 12/19/24. 6 | // Copyright © 2024 Christopher M. Hanson. All rights reserved. 7 | // 8 | 9 | #include "MINIXCompat_Logging.h" 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | 20 | MINIXCOMPAT_SOURCE_BEGIN 21 | 22 | 23 | #if DEBUG 24 | 25 | 26 | /*! The directory in which MINIXCompat logs are written. */ 27 | static char *MINIXCOMPAT_LOG_DIR = NULL; 28 | 29 | /*! Cached length of `MINIXCOMPAT_LOG_DIR`. */ 30 | static size_t MINIXCOMPAT_LOG_DIR_len = 0; 31 | 32 | /*! The ID of the process that's logging. This is used to open a new log across a `fork(2)`. */ 33 | static pid_t MINIXCompat_Log_pid = 0; 34 | 35 | 36 | /*! The path to the log. */ 37 | static char MINIXCompat_Log_Path[1024] = {0}; 38 | 39 | 40 | /*! The file being logged to. */ 41 | static FILE *MINIXCompat_Log_File = NULL; 42 | 43 | 44 | /*! Create a new log file when needed. */ 45 | static void MINIXCompat_Log_New(void); 46 | 47 | 48 | void MINIXCompat_Log_Initialize(void) 49 | { 50 | MINIXCompat_Log_pid = getpid(); 51 | MINIXCompat_Log_New(); 52 | } 53 | 54 | 55 | void MINIXCompat_Log_New(void) 56 | { 57 | // If there's already a log (say one that's been inherited across a fork(2), close it. 58 | 59 | if (MINIXCompat_Log_File != NULL) { 60 | fclose(MINIXCompat_Log_File); 61 | } 62 | 63 | // Get the directory to log into, using /tmp if none is specified. 64 | 65 | if (MINIXCOMPAT_LOG_DIR == NULL) { 66 | MINIXCOMPAT_LOG_DIR = getenv("MINIXCOMPAT_LOG_DIR"); 67 | if (MINIXCOMPAT_LOG_DIR == NULL) { 68 | MINIXCOMPAT_LOG_DIR = "/tmp"; 69 | } 70 | MINIXCOMPAT_LOG_DIR_len = strlen(MINIXCOMPAT_LOG_DIR); 71 | } 72 | 73 | // Construct a path to the log. 74 | 75 | char name[64] = {0}; 76 | snprintf(name, 64, "MINIXCompat.%d", MINIXCompat_Log_pid); 77 | 78 | MINIXCompat_Log_Path[0] = '\0'; 79 | strncat(MINIXCompat_Log_Path, MINIXCOMPAT_LOG_DIR, 1024); 80 | if (MINIXCOMPAT_LOG_DIR[MINIXCOMPAT_LOG_DIR_len - 1] != '/') { 81 | strncat(MINIXCompat_Log_Path, "/", 1024); 82 | } 83 | strncat(MINIXCompat_Log_Path, name, 1024); 84 | 85 | // Open the log and crash if we can't. 86 | 87 | MINIXCompat_Log_File = fopen(MINIXCompat_Log_Path, "w"); 88 | assert(MINIXCompat_Log_File != NULL); 89 | } 90 | 91 | 92 | void MINIXCompat_Log(const char *fmt, ...) 93 | { 94 | va_list ap; 95 | va_start(ap, fmt); 96 | MINIXCompat_Logv(fmt, ap); 97 | va_end(ap); 98 | } 99 | 100 | 101 | void MINIXCompat_Logv(const char *fmt, va_list args) 102 | { 103 | pid_t curpid = getpid(); 104 | if (MINIXCompat_Log_pid != curpid) { 105 | MINIXCompat_Log_pid = curpid; 106 | MINIXCompat_Log_New(); 107 | } 108 | 109 | fprintf(MINIXCompat_Log_File, "%d: ", curpid); 110 | vfprintf(MINIXCompat_Log_File, fmt, args); 111 | size_t fmt_len = strlen(fmt); 112 | if (fmt[fmt_len - 1] != '\n') { 113 | fprintf(MINIXCompat_Log_File, "\n"); 114 | } 115 | } 116 | 117 | 118 | #endif /* DEBUG */ 119 | 120 | 121 | MINIXCOMPAT_SOURCE_END 122 | -------------------------------------------------------------------------------- /MINIXCompat/MINIXCompat_Logging.h: -------------------------------------------------------------------------------- 1 | // 2 | // MINIXCompat_Logging.h 3 | // MINIXCompat 4 | // 5 | // Created by Chris Hanson on 12/19/24. 6 | // Copyright © 2024 Christopher M. Hanson. All rights reserved. 7 | // 8 | 9 | #ifndef MINIXCompat_Logging_h 10 | #define MINIXCompat_Logging_h 11 | 12 | #include "MINIXCompat_Types.h" 13 | 14 | #include 15 | 16 | 17 | MINIXCOMPAT_HEADER_BEGIN 18 | 19 | 20 | #if DEBUG 21 | 22 | 23 | /*! Initialize the logging subsystem. */ 24 | MINIXCOMPAT_EXTERN void MINIXCompat_Log_Initialize(void); 25 | 26 | 27 | /*! Log some information to the per-process log file. */ 28 | MINIXCOMPAT_EXTERN void MINIXCompat_Log(const char *fmt, ...) __printflike(1, 2);; 29 | 30 | /*! Log some information to the per-process log file (callable). */ 31 | MINIXCOMPAT_EXTERN void MINIXCompat_Logv(const char *fmt, va_list args); 32 | 33 | 34 | #else 35 | 36 | 37 | #define MINIXCompat_Log_Initialize() 38 | #define MINIXCompat_Log(fmt, ...) 39 | #define MINIXCompat_Logv(fmt, args) 40 | 41 | 42 | #endif 43 | 44 | 45 | MINIXCOMPAT_HEADER_END 46 | 47 | 48 | #endif /* MINIXCompat_Logging_h */ 49 | -------------------------------------------------------------------------------- /MINIXCompat/MINIXCompat_Messages.c: -------------------------------------------------------------------------------- 1 | // 2 | // MINIXCompat_Messages.c 3 | // MINIXCompat 4 | // 5 | // Created by Chris Hanson on 11/16/24. 6 | // Copyright © 2024 Christopher M. Hanson. See file LICENSE for details. 7 | // 8 | 9 | #include "MINIXCompat_Messages.h" 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #include /* for ntohs et al */ 16 | 17 | #include "MINIXCompat_Types.h" 18 | 19 | #ifndef HTONS 20 | #define HTONS(x) ((x) = htons(x)) 21 | #define HTONL(x) ((x) = htonl(x)) 22 | #endif 23 | 24 | MINIXCOMPAT_SOURCE_BEGIN 25 | 26 | 27 | #if defined(__LITTLE_ENDIAN__) 28 | 29 | static void MINIXCompat_Message_Swap_header(minix_message_t * _Nonnull msg) 30 | { 31 | assert(msg != NULL); 32 | 33 | HTONS(msg->m_source); 34 | HTONS(msg->m_type); 35 | } 36 | 37 | void MINIXCompat_Message_Swap_mess1(minix_message_t * _Nonnull msg) 38 | { 39 | MINIXCompat_Message_Swap_header(msg); 40 | HTONS(msg->m1_i1); 41 | HTONS(msg->m1_i2); 42 | HTONS(msg->m1_i3); 43 | HTONL(msg->m1_p1); 44 | HTONL(msg->m1_p2); 45 | HTONL(msg->m1_p3); 46 | } 47 | 48 | void MINIXCompat_Message_Swap_mess2(minix_message_t * _Nonnull msg) 49 | { 50 | MINIXCompat_Message_Swap_header(msg); 51 | 52 | HTONS(msg->m2_i1); 53 | HTONS(msg->m2_i2); 54 | HTONS(msg->m2_i3); 55 | HTONL(msg->m2_l1); 56 | HTONL(msg->m2_l2); 57 | HTONL(msg->m2_p1); 58 | } 59 | 60 | void MINIXCompat_Message_Swap_mess3(minix_message_t * _Nonnull msg) 61 | { 62 | MINIXCompat_Message_Swap_header(msg); 63 | 64 | HTONS(msg->m3_i1); 65 | HTONS(msg->m3_i2); 66 | HTONL(msg->m3_p1); 67 | // m3_ca1 doesn't need any swapping 68 | } 69 | 70 | void MINIXCompat_Message_Swap_mess4(minix_message_t * _Nonnull msg) 71 | { 72 | MINIXCompat_Message_Swap_header(msg); 73 | 74 | HTONL(msg->m4_l1); 75 | HTONL(msg->m4_l2); 76 | HTONL(msg->m4_l3); 77 | HTONL(msg->m4_l4); 78 | } 79 | 80 | void MINIXCompat_Message_Swap_mess5(minix_message_t * _Nonnull msg) 81 | { 82 | MINIXCompat_Message_Swap_header(msg); 83 | 84 | // m5_c1 needs no swapping 85 | // m5_c2 needs no swapping 86 | HTONS(msg->m5_i1); 87 | HTONS(msg->m5_i2); 88 | HTONL(msg->m5_l1); 89 | HTONL(msg->m5_l2); 90 | HTONL(msg->m5_l3); 91 | } 92 | 93 | void MINIXCompat_Message_Swap_mess6(minix_message_t * _Nonnull msg) 94 | { 95 | MINIXCompat_Message_Swap_header(msg); 96 | 97 | HTONS(msg->m6_i1); 98 | HTONS(msg->m6_i2); 99 | HTONS(msg->m6_i3); 100 | HTONL(msg->m6_l1); 101 | HTONL(msg->m6_f1); 102 | } 103 | 104 | #endif 105 | 106 | 107 | void MINIXCompat_Message_Clear(minix_message_t * _Nonnull msg) 108 | { 109 | assert(msg != NULL); 110 | 111 | memset(msg, 0, sizeof(minix_message_t)); 112 | } 113 | 114 | 115 | MINIXCOMPAT_SOURCE_END 116 | -------------------------------------------------------------------------------- /MINIXCompat/MINIXCompat_Messages.h: -------------------------------------------------------------------------------- 1 | // 2 | // MINIXCompat_Messages.h 3 | // MINIXCompat 4 | // 5 | // Created by Chris Hanson on 11/16/24. 6 | // Copyright © 2024 Christopher M. Hanson. See file LICENSE for details. 7 | // 8 | 9 | #ifndef MINIXCompat_Messages_h 10 | #define MINIXCompat_Messages_h 11 | 12 | #include "MINIXCompat_Types.h" 13 | 14 | 15 | MINIXCOMPAT_HEADER_BEGIN 16 | 17 | 18 | /* Different system calls use different message structures that are all combined into a union for some manner of type safety. */ 19 | 20 | struct minix_mess_1 {int16_t m1i1, m1i2, m1i3; m68k_address_t m1p1, m1p2, m1p3;} MINIXCOMPAT_PACK_STRUCT; 21 | struct minix_mess_2 {int16_t m2i1, m2i2, m2i3; int32_t m2l1, m2l2; m68k_address_t m2p1;} MINIXCOMPAT_PACK_STRUCT; 22 | struct minix_mess_3 {int16_t m3i1, m3i2; m68k_address_t m3p1; char m3ca1[14];} MINIXCOMPAT_PACK_STRUCT; 23 | struct minix_mess_4 {int32_t m4l1, m4l2, m4l3, m4l4;} MINIXCOMPAT_PACK_STRUCT; 24 | struct minix_mess_5 {char m5c1, m5c2; int16_t m5i1, m5i2; int32_t m5l1, m5l2, m5l3;} MINIXCOMPAT_PACK_STRUCT; 25 | struct minix_mess_6 {int16_t m6i1, m6i2, m6i3; int32_t m6l1; m68k_address_t m6f1;} MINIXCOMPAT_PACK_STRUCT; 26 | typedef struct minix_mess_1 minix_mess_1; 27 | typedef struct minix_mess_2 minix_mess_2; 28 | typedef struct minix_mess_3 minix_mess_3; 29 | typedef struct minix_mess_4 minix_mess_4; 30 | typedef struct minix_mess_5 minix_mess_5; 31 | typedef struct minix_mess_6 minix_mess_6; 32 | 33 | /*! 34 | MINIX messages are used for system calls. 35 | Different system calls have different message structures. 36 | 37 | All content in a message is in network byte order on the emulator side. 38 | */ 39 | struct minix_message { 40 | int16_t m_source; /* who sent the message */ 41 | int16_t m_type; /* what kind of message is it */ 42 | union { 43 | minix_mess_1 m_m1; 44 | minix_mess_2 m_m2; 45 | minix_mess_3 m_m3; 46 | minix_mess_4 m_m4; 47 | minix_mess_5 m_m5; 48 | minix_mess_6 m_m6; 49 | } m_u; 50 | } MINIXCOMPAT_PACK_STRUCT; 51 | typedef struct minix_message minix_message_t; 52 | 53 | 54 | #if defined(__LITTLE_ENDIAN__) 55 | 56 | /*! Swap a message's content to host byte order given its type and a pointer to it. */ 57 | #define MINIXCompat_Message_Swap(t,m) MINIXCompat_Message_Swap_##t(m) 58 | 59 | /* For little-endian systems each message type has a swap function. */ 60 | MINIXCOMPAT_EXTERN void MINIXCompat_Message_Swap_mess1(minix_message_t * _Nonnull msg); 61 | MINIXCOMPAT_EXTERN void MINIXCompat_Message_Swap_mess2(minix_message_t * _Nonnull msg); 62 | MINIXCOMPAT_EXTERN void MINIXCompat_Message_Swap_mess3(minix_message_t * _Nonnull msg); 63 | MINIXCOMPAT_EXTERN void MINIXCompat_Message_Swap_mess4(minix_message_t * _Nonnull msg); 64 | MINIXCOMPAT_EXTERN void MINIXCompat_Message_Swap_mess5(minix_message_t * _Nonnull msg); 65 | MINIXCOMPAT_EXTERN void MINIXCompat_Message_Swap_mess6(minix_message_t * _Nonnull msg); 66 | 67 | #elif defined(__BIG_ENDIAN__) 68 | 69 | /*! Swap a message's content to host byte order given its type and a pointer to it. */ 70 | #define MINIXCompat_Message_Swap(t,m) /* Do nothing on big-endian. */ 71 | 72 | #else 73 | #error One of __LITTLE_ENDIAN__ or __BIG_ENDIAN__ must be defined. 74 | #endif 75 | 76 | 77 | /*! Clear a message prior to filling it out, to prevent any garbage from being present. */ 78 | MINIXCOMPAT_EXTERN void MINIXCompat_Message_Clear(minix_message_t * _Nonnull msg); 79 | 80 | 81 | /* The following defines provide names for useful members. */ 82 | #define m1_i1 m_u.m_m1.m1i1 83 | #define m1_i2 m_u.m_m1.m1i2 84 | #define m1_i3 m_u.m_m1.m1i3 85 | #define m1_p1 m_u.m_m1.m1p1 86 | #define m1_p2 m_u.m_m1.m1p2 87 | #define m1_p3 m_u.m_m1.m1p3 88 | #define m2_i1 m_u.m_m2.m2i1 89 | #define m2_i2 m_u.m_m2.m2i2 90 | #define m2_i3 m_u.m_m2.m2i3 91 | #define m2_l1 m_u.m_m2.m2l1 92 | #define m2_l2 m_u.m_m2.m2l2 93 | #define m2_p1 m_u.m_m2.m2p1 94 | #define m3_i1 m_u.m_m3.m3i1 95 | #define m3_i2 m_u.m_m3.m3i2 96 | #define m3_p1 m_u.m_m3.m3p1 97 | #define m3_ca1 m_u.m_m3.m3ca1 98 | #define m4_l1 m_u.m_m4.m4l1 99 | #define m4_l2 m_u.m_m4.m4l2 100 | #define m4_l3 m_u.m_m4.m4l3 101 | #define m4_l4 m_u.m_m4.m4l4 102 | #define m5_c1 m_u.m_m5.m5c1 103 | #define m5_c2 m_u.m_m5.m5c2 104 | #define m5_i1 m_u.m_m5.m5i1 105 | #define m5_i2 m_u.m_m5.m5i2 106 | #define m5_l1 m_u.m_m5.m5l1 107 | #define m5_l2 m_u.m_m5.m5l2 108 | #define m5_l3 m_u.m_m5.m5l3 109 | #define m6_i1 m_u.m_m6.m6i1 110 | #define m6_i2 m_u.m_m6.m6i2 111 | #define m6_i3 m_u.m_m6.m6i3 112 | #define m6_l1 m_u.m_m6.m6l1 113 | #define m6_f1 m_u.m_m6.m6f1 114 | 115 | /* Names of messages fields used in reply messages from tasks. */ 116 | #define REP_PROC_NR m2_i1 /* # of proc on whose behalf I/O was done */ 117 | #define REP_STATUS m2_i2 /* bytes transferred or error number */ 118 | 119 | /*! 120 | The well-known tasks are: 121 | 122 | - -9: TTY driver 123 | - -7: Printer driver (same codes as tty driver) 124 | - -6: Hard disk driver 125 | - -5: Floppy disk driver 126 | - -4: Memory driver (/dev/ram, /dev/mem, /dev/kmem) 127 | - -3: Clock driver 128 | - -2: System task 129 | - -1: Hardware handling task 130 | - 0: The memory manager 131 | - 1: The file system 132 | - 2: `init`, the first process on the system 133 | 134 | User processes have a task ID of 3 or higher. 135 | */ 136 | typedef enum minix_task: int16_t { 137 | minix_task_tty = -9, 138 | minix_task_printer = -7, 139 | minix_task_storage_fixed = -6, 140 | minix_task_storage_removable = -5, 141 | minix_task_memory = -4, 142 | minix_task_clock = -3, 143 | minix_task_system = -2, 144 | minix_task_hardware = -1, 145 | minix_task_mm = 0, 146 | minix_task_fs = 1, 147 | minix_task_init = 2, 148 | } minix_task_t; 149 | 150 | /*! tty & printer task messages */ 151 | typedef enum minix_tty_func: int16_t { 152 | minix_tty_read = 3, 153 | minix_tty_write = 4, 154 | minix_tty_ioctl = 5, 155 | minix_tty_setpgrp = 6, 156 | minix_tty_open = 7, 157 | minix_tty_close = 8, 158 | minix_tty_suspend = -998, 159 | } minix_tty_func_t; 160 | 161 | /* Names of message fields for messages to TTY task. */ 162 | #define TTY_LINE m2_i1 /* message parameter: terminal line */ 163 | #define TTY_REQUEST m2_i3 /* message parameter: ioctl request code */ 164 | #define TTY_SPEK m2_l1 /* message parameter: ioctl speed, erasing */ 165 | #define TTY_FLAGS m2_l2 /* message parameter: ioctl tty mode */ 166 | #define TTY_PGRP m2_i3 /* message parameter: process group */ 167 | 168 | /*! storage task messages */ 169 | typedef enum minix_storage_func: int16_t { 170 | minix_storage_read = 3, 171 | minix_storage_write = 4, 172 | minix_storage_ioctl = 5, 173 | minix_storage_scattered_io = 6, 174 | minix_storage_optional_io = 16, 175 | } minix_storage_func_t; 176 | 177 | /* Names of message fields used for messages to block and character tasks. */ 178 | #define DEVICE m2_i1 /* major-minor device */ 179 | #define PROC_NR m2_i2 /* which (proc) wants I/O? */ 180 | #define COUNT m2_i3 /* how many bytes to transfer */ 181 | #define POSITION m2_l1 /* file offset */ 182 | #define ADDRESS m2_p1 /* core buffer address */ 183 | 184 | /*! clock task messages */ 185 | typedef enum minix_clock_func: int16_t { 186 | minix_clock_alarm_set = 1, /* send to set an alarm */ 187 | minix_clock_time_get = 3, 188 | minix_clock_time_set = 4, 189 | minix_clock_real_time = 1, /* here is real time */ 190 | } minix_clock_func_t; 191 | 192 | /* Names of message fields for messages to CLOCK task. */ 193 | #define DELTA_TICKS m6_l1 /* alarm interval in clock ticks */ 194 | #define FUNC_TO_CALL m6_f1 /* pointer to function to call */ 195 | #define NEW_TIME m6_l1 /* value to set clock to (SET_TIME) */ 196 | #define CLOCK_PROC_NR m6_i1 /* which proc (or task) wants the alarm? */ 197 | #define SECONDS_LEFT m6_l1 /* how many seconds were remaining */ 198 | 199 | /* system task messages */ 200 | typedef enum minix_system_func: int16_t { 201 | minix_system_exit = 1, 202 | minix_sysetm_getsp = 2, 203 | minix_system_sig = 3, 204 | minix_system_fork = 4, 205 | minix_system_newmap = 5, 206 | minix_system_copy = 6, 207 | minix_system_exec = 7, 208 | minix_system_times = 8, 209 | minix_system_abort = 9, 210 | minix_system_fresh = 10, 211 | minix_system_kill = 11, 212 | minix_system_gboot = 12, 213 | minix_system_umap = 13, 214 | minix_system_mem = 14, 215 | minix_system_trace = 15, 216 | } minix_system_func_t; 217 | 218 | /* Names of fields for copy message to SYSTASK. */ 219 | #define SRC_SPACE m5_c1 /* T or D space (stack is also D) */ 220 | #define SRC_PROC_NR m5_i1 /* process to copy from */ 221 | #define SRC_BUFFER m5_l1 /* virtual address where data come from */ 222 | #define DST_SPACE m5_c2 /* T or D space (stack is also D) */ 223 | #define DST_PROC_NR m5_i2 /* process to copy to */ 224 | #define DST_BUFFER m5_l2 /* virtual address where data go to */ 225 | #define COPY_BYTES m5_l3 /* number of bytes to copy */ 226 | 227 | /* Field names for accounting, SYSTASK and miscellaneous. */ 228 | #define USER_TIME m4_l1 /* user time consumed by process */ 229 | #define SYSTEM_TIME m4_l2 /* system time consumed by process */ 230 | #define CHILD_UTIME m4_l3 /* user time consumed by process' children */ 231 | #define CHILD_STIME m4_l4 /* sys time consumed by process' children */ 232 | 233 | #define PROC1 m1_i1 /* indicates a process */ 234 | #define PROC2 m1_i2 /* indicates a process */ 235 | #define PID m1_i3 /* process id passed from MM to kernel */ 236 | #define STACK_PTR m1_p1 /* used for stack ptr in sys_exec, sys_getsp */ 237 | #define PR m6_i1 /* process number for sys_sig */ 238 | #define SIGNUM m6_i2 /* signal number for sys_sig */ 239 | #define FUNC m6_f1 /* function pointer for sys_sig */ 240 | #define MEM_PTR m1_p1 /* tells where memory map is for sys_newmap */ 241 | #define CANCEL 0 /* general request to force a task to cancel */ 242 | #define SIG_MAP m1_i2 /* used by kernel for passing signal bit map */ 243 | 244 | 245 | MINIXCOMPAT_HEADER_END 246 | 247 | 248 | #endif /* MINIXCompat_Messages_h */ 249 | -------------------------------------------------------------------------------- /MINIXCompat/MINIXCompat_Musashi.h: -------------------------------------------------------------------------------- 1 | // 2 | // MINIXCompat_Musashi.h 3 | // MINIXCompat 4 | // 5 | // Created by Chris Hanson on 11/4/24. 6 | // Copyright © 2024 Christopher M. Hanson. See file LICENSE for details. 7 | // 8 | 9 | #ifndef MINIXCompat_Musashi_h 10 | #define MINIXCompat_Musashi_h 11 | 12 | /* Emulate only the 68000. */ 13 | 14 | #define M68K_EMULATE_010 M68K_OPT_OFF 15 | #define M68K_EMULATE_020 M68K_OPT_OFF 16 | #define M68K_EMULATE_030 M68K_OPT_OFF 17 | #define M68K_EMULATE_040 M68K_OPT_OFF 18 | 19 | /* Enable TRACE emulation. */ 20 | 21 | #define M68K_EMULATE_TRACE M68K_OPT_ON 22 | 23 | /* Enable TRAP emulation. */ 24 | 25 | #define M68K_TRAP_HAS_CALLBACK M68K_OPT_ON 26 | 27 | /* Disable PMMU emulation. */ 28 | 29 | #define M68K_EMULATE_PMMU M68K_OPT_OFF 30 | 31 | /* Pull in the Musashi configuration (since this is used as the Musashi configuration). */ 32 | 33 | #include "m68kconf.h" 34 | 35 | #endif /* MINIXCompat_Musashi_h */ 36 | -------------------------------------------------------------------------------- /MINIXCompat/MINIXCompat_Processes.c: -------------------------------------------------------------------------------- 1 | // 2 | // MINIXCompat_Processes.c 3 | // MINIXCompat 4 | // 5 | // Created by Chris Hanson on 11/30/24. 6 | // Copyright © 2024 Christopher M. Hanson. See file LICENSE for details. 7 | // 8 | 9 | #include "MINIXCompat_Processes.h" 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include /* for ntohs et al */ 22 | #include 23 | #include 24 | 25 | #include "MINIXCompat_Types.h" 26 | #include "MINIXCompat.h" 27 | #include "MINIXCompat_Emulation.h" 28 | #include "MINIXCompat_Errors.h" 29 | #include "MINIXCompat_Executable.h" 30 | #include "MINIXCompat_Filesystem.h" 31 | #include "MINIXCompat_Logging.h" 32 | 33 | 34 | #if DEBUG 35 | 36 | /*! 37 | Uncomment to debug forking. 38 | 39 | This will spin both the parent and child in loops immediately following the fork, allowing debugger attachment and resumption. 40 | */ 41 | //#define DEBUG_FORK 1 42 | 43 | /*! Uncomment to debug signal handling. */ 44 | //#define DEBUG_SIGNAL 1 45 | 46 | /*! Uncomment to trace process-related system calls. */ 47 | //#define DEBUG_PROCESS_SYSCALLS 1 48 | 49 | #endif 50 | 51 | 52 | MINIXCOMPAT_SOURCE_BEGIN 53 | 54 | /*! 55 | A mapping between MINIX and host process IDs. 56 | 57 | MINIX uses 16-bit PIDs while the host may use 32-bit or even 64-bit PIDs, so we need to maintain a mapping. 58 | */ 59 | typedef struct minix_process_mapping { 60 | pid_t host_pid; 61 | minix_pid_t minix_pid; 62 | } minix_process_mapping_t; 63 | 64 | /*! 65 | The table that maps between MINIX and host process IDs. 66 | 67 | There are unlikely to be enough entries in the table for search speed to matter, so we just maintain it unordered. 68 | 69 | Note that MINIX process IDs start at 3, since 0 is MM, 1 is FS, and 2 is init. 70 | */ 71 | static minix_process_mapping_t *MINIXCompat_ProcessTable = NULL; 72 | 73 | /*! Maximum number of entries in the MINIX process table. */ 74 | static size_t MINIXCompat_ProcessTable_Size = 0; 75 | 76 | 77 | /*! The MINIX process ID equivalent to the host's. */ 78 | static minix_pid_t minix_self_pid = 0; 79 | 80 | /*! The MINIX parent process ID equivalent to the host's. */ 81 | static minix_pid_t minix_self_ppid = 0; 82 | 83 | /*! The next process ID to allocate. */ 84 | static minix_pid_t minix_next_pid = 0; 85 | 86 | 87 | /*! The signal handler table. */ 88 | static minix_sighandler_t minix_signal_handlers[17] = { 0x00000000 /* minix_SIG_DFL */ }; 89 | 90 | minix_sighandler_t minix_SIG_DFL = 0x00000000; 91 | minix_sighandler_t minix_SIG_IGN = 0x00000001; 92 | minix_sighandler_t minix_SIG_ERR = 0xFFFFFFFF; 93 | 94 | 95 | /*! Initialize the processes subsystem. */ 96 | void MINIXCompat_Processes_Initialize(void) 97 | { 98 | // We probably won't need any more than this, since MINIX sets `NR_PROCS` to this value. 99 | MINIXCompat_ProcessTable_Size = 32; 100 | MINIXCompat_ProcessTable = calloc(MINIXCompat_ProcessTable_Size, sizeof(minix_process_mapping_t)); 101 | 102 | pid_t host_self_pid = getpid(); 103 | pid_t host_self_ppid = getppid(); 104 | 105 | /* 106 | The lowest MINIX pid for a user process is 2, since 0 and 1 are MM and FS. 107 | However, 2 is init. Pretending the MINIX process is launched in a terminal, 108 | there should be the following processes: 109 | 110 | 3: sh started by init to run /etc/rc 111 | 4: getty started by /etc/rc to handle terminal 112 | 5: login started by getty on terminal to handle user session 113 | 6: sh started by login on terminal for user use 114 | 115 | So the first process ID to use should be 7, with 6 as our parent, and the next PID should be 8. 116 | */ 117 | const minix_pid_t pseudoparent = 6; 118 | const minix_pid_t ourselves = 7; 119 | 120 | // An entry for ourselves, first for fastest access by linear search. 121 | MINIXCompat_ProcessTable[0].minix_pid = ourselves; 122 | MINIXCompat_ProcessTable[0].host_pid = host_self_pid; 123 | 124 | // An entry for our parent, since it may actually be used by MINIX. 125 | MINIXCompat_ProcessTable[1].minix_pid = pseudoparent; 126 | MINIXCompat_ProcessTable[1].host_pid = host_self_ppid; // pretending that it's sh 127 | 128 | minix_next_pid = 8; 129 | } 130 | 131 | /*! Get the MINIX process corresponding to the given host-side process.. */ 132 | static minix_pid_t MINIXCompat_Processes_MINIXProcessForHostProcess(pid_t host_pid) 133 | { 134 | for (size_t i = 0; i < MINIXCompat_ProcessTable_Size; i++) { 135 | if (MINIXCompat_ProcessTable[i].host_pid == host_pid) { 136 | return MINIXCompat_ProcessTable[i].minix_pid; 137 | } 138 | } 139 | 140 | return -1; 141 | } 142 | 143 | /*! Get the host process corresponding to the given MINIX-side process. */ 144 | static pid_t MINIXCompat_Processes_HostProcessForMINIXProcess(minix_pid_t minix_pid) 145 | { 146 | for (size_t i = 0; i < MINIXCompat_ProcessTable_Size; i++) { 147 | if (MINIXCompat_ProcessTable[i].minix_pid == minix_pid) { 148 | return MINIXCompat_ProcessTable[i].host_pid; 149 | } 150 | } 151 | 152 | return -1; 153 | } 154 | 155 | /*! Get the next free entry number in the process table. */ 156 | static size_t MINIXCompat_Processes_NextFreeTableEntry(void) 157 | { 158 | for (size_t i = 2; i < MINIXCompat_ProcessTable_Size; i++) { 159 | if (MINIXCompat_ProcessTable[i].host_pid == 0) { 160 | return i; 161 | } 162 | } 163 | 164 | // If we got here, there are no free entries. Reallocate the table with half again the size, making sure all the new entries are zeroed, and return the first of the new entries (which will correspond to the size of the old table). 165 | 166 | minix_process_mapping_t *old_table = MINIXCompat_ProcessTable; 167 | const size_t old_table_size = MINIXCompat_ProcessTable_Size; 168 | const size_t new_table_size = old_table_size + (old_table_size/2); 169 | const size_t first_new_entry = old_table_size; 170 | MINIXCompat_ProcessTable = calloc(new_table_size, sizeof(minix_process_mapping_t)); 171 | memcpy(MINIXCompat_ProcessTable, old_table, MINIXCompat_ProcessTable_Size * sizeof(minix_process_mapping_t)); 172 | MINIXCompat_ProcessTable_Size = new_table_size; 173 | return first_new_entry; 174 | } 175 | 176 | void MINIXCompat_Processes_GetProcessIDs(minix_pid_t * _Nonnull minix_pid, minix_pid_t * _Nonnull minix_ppid) 177 | { 178 | assert(minix_pid != NULL); 179 | assert(minix_ppid != NULL); 180 | 181 | if ((minix_self_pid == 0) && (minix_self_ppid == 0)) { 182 | minix_self_pid = MINIXCompat_ProcessTable[0].minix_pid; 183 | minix_self_ppid = MINIXCompat_ProcessTable[1].minix_pid; 184 | } 185 | 186 | *minix_pid = minix_self_pid; 187 | *minix_ppid = minix_self_ppid; 188 | 189 | #if DEBUG_PROCESS_SYSCALLS 190 | MINIXCompat_Log("getpid() -> %d", minix_self_pid); 191 | MINIXCompat_Log("getppid() -> %d", minix_self_ppid); 192 | #endif 193 | 194 | return; 195 | } 196 | 197 | minix_pid_t MINIXCompat_Processes_fork(void) 198 | { 199 | minix_pid_t result; 200 | 201 | // Get a free entry in the process table prior to forking, so that both processes can have a similar table. 202 | size_t new_process_entry = MINIXCompat_Processes_NextFreeTableEntry(); 203 | 204 | // Get the child PID to use and bump the next MINIX pid to use, so both parent and child have a coherent view of the world. 205 | minix_pid_t new_minix_process = minix_next_pid++; 206 | 207 | // Actually fork the host. 208 | pid_t new_host_process = fork(); 209 | 210 | if (new_host_process == -1) { 211 | // An error occurred and no child was created; capture the error. 212 | 213 | result = -MINIXCompat_Errors_MINIXErrorForHostError(errno); 214 | 215 | // Reset minix_next_pid. 216 | 217 | minix_next_pid -= 1; 218 | } else if (new_host_process != 0) { 219 | #if DEBUG_FORK 220 | volatile int continue_parent = 0; 221 | do { 222 | sleep(1); 223 | } while (!continue_parent); 224 | #endif 225 | 226 | // This is the parent. Fill in the new entry in the process table. At this point the tables diverge. 227 | 228 | MINIXCompat_ProcessTable[new_process_entry].host_pid = new_host_process; 229 | MINIXCompat_ProcessTable[new_process_entry].minix_pid = new_minix_process; 230 | 231 | // Return the MINIX child PID. 232 | 233 | result = new_minix_process; 234 | } else { 235 | #if DEBUG_FORK 236 | volatile int continue_child = 0; 237 | do { 238 | sleep(1); 239 | } while (!continue_child); 240 | #endif 241 | // This is the child. Reinitialize logging (if it's a thing). 242 | 243 | MINIXCompat_Log_Initialize(); 244 | 245 | 246 | MINIXCompat_ProcessTable[new_process_entry].host_pid = MINIXCompat_ProcessTable[1].host_pid; 247 | MINIXCompat_ProcessTable[new_process_entry].minix_pid = MINIXCompat_ProcessTable[1].minix_pid; 248 | 249 | // Now adjust the parent and self entries in the process table. 250 | 251 | MINIXCompat_ProcessTable[1].host_pid = MINIXCompat_ProcessTable[0].host_pid; 252 | MINIXCompat_ProcessTable[1].minix_pid = MINIXCompat_ProcessTable[0].minix_pid; 253 | 254 | MINIXCompat_ProcessTable[0].host_pid = getpid(); 255 | MINIXCompat_ProcessTable[0].minix_pid = new_minix_process; 256 | 257 | // Return 0 here, because if the new process needs its own ID it can always use getpid(2) to get that. 258 | 259 | result = 0; 260 | } 261 | 262 | #if DEBUG_PROCESS_SYSCALLS 263 | MINIXCompat_Log("fork() -> %d", result); 264 | #endif 265 | 266 | return result; 267 | } 268 | 269 | static int16_t MINIXCompat_Processes_MINIXStatForHostStat(int host_stat) 270 | { 271 | int16_t minix_stat = 0; 272 | 273 | // The MINIX status has three separate styles: 274 | // 275 | // LSB == 0 (exit): 276 | // High byte is exit status 277 | // LSB == 0177 (job control): 278 | // High byte is signal number 279 | // MSB == 0 (signal): 280 | // Low byte is signal 281 | // 282 | // Portably construct this using the matching info in the host status. 283 | 284 | if (WIFEXITED(host_stat)) { 285 | minix_stat = WEXITSTATUS(host_stat); 286 | } else if (WIFSTOPPED(host_stat)) { 287 | minix_stat = (WSTOPSIG(host_stat) << 8) | 0177; 288 | } else if (WIFSIGNALED(host_stat)) { 289 | minix_stat = WTERMSIG(host_stat) << 8; 290 | } else { 291 | // Unsupported case on MINIX, just treat as killed by SIGKILL: 292 | // MSB == 0, LSB == 0x09. 293 | minix_stat = 0x0009; 294 | } 295 | 296 | return minix_stat; 297 | } 298 | 299 | minix_pid_t MINIXCompat_Processes_wait(int16_t * _Nonnull minix_stat_loc) 300 | { 301 | assert(minix_stat_loc != NULL); 302 | 303 | minix_pid_t minix_pid; 304 | 305 | int host_stat = 0; 306 | pid_t host_pid = wait(&host_stat); 307 | if (host_pid == -1) { 308 | minix_pid = -MINIXCompat_Errors_MINIXErrorForHostError(errno); 309 | } else { 310 | minix_pid = MINIXCompat_Processes_MINIXProcessForHostProcess(host_pid); 311 | int16_t minix_stat = MINIXCompat_Processes_MINIXStatForHostStat(host_stat); 312 | 313 | *minix_stat_loc = minix_stat; 314 | } 315 | 316 | return minix_pid; 317 | } 318 | 319 | 320 | int MINIXCompat_Processes_ExitStatus = 0; 321 | 322 | 323 | void MINIXCompat_Processes_exit(int16_t status) 324 | { 325 | MINIXCompat_Processes_ExitStatus = status; 326 | MINIXCompat_Execution_ChangeState(MINIXCompat_Execution_State_Finished); 327 | 328 | #if DEBUG_PROCESS_SYSCALLS 329 | MINIXCompat_Log("exit(%d)", status); 330 | #endif 331 | } 332 | 333 | 334 | // MARK: - Signal Handling 335 | 336 | #if DEBUG_PROCESS_SYSCALLS 337 | const char *MINIXCompat_Processes_NameForMINIXSignal(minix_signal_t minix_signal) 338 | { 339 | switch (minix_signal) { 340 | case minix_SIGHUP: return "SIGHUP"; 341 | case minix_SIGINT: return "SIGINT"; 342 | case minix_SIGQUIT: return "SIGQUIT"; 343 | case minix_SIGILL: return "SIGILL"; 344 | case minix_SIGTRAP: return "SIGTRAP"; 345 | case minix_SIGABRT: return "SIGABRT"; 346 | case minix_SIGUNUSED: return "SIGUNUSED"; 347 | case minix_SIGFPE: return "SIGFPE"; 348 | case minix_SIGKILL: return "SIGKILL"; 349 | case minix_SIGUSR1: return "SIGUSR1"; 350 | case minix_SIGSEGV: return "SIGSEGV"; 351 | case minix_SIGUSR2: return "SIGUSR2"; 352 | case minix_SIGPIPE: return "SIGPIPE"; 353 | case minix_SIGALRM: return "SIGALRM"; 354 | case minix_SIGTERM: return "SIGTERM"; 355 | case minix_SIGSTKFLT: return "SIGSTKFLT"; 356 | } 357 | } 358 | 359 | const char *MINIXCompat_Processes_NameForMINIXSignalHandler(minix_sighandler_t minix_handler) 360 | { 361 | if (minix_handler == minix_SIG_DFL) return "SIG_DFL"; 362 | else if (minix_handler == minix_SIG_IGN) return "SIG_IGN"; 363 | else if (minix_handler == minix_SIG_ERR) return "SIG_ERR"; 364 | else { 365 | static char namebuf[64] = {0}; 366 | snprintf(namebuf, 64, "0x%08x", minix_handler); 367 | return namebuf; 368 | } 369 | } 370 | #endif 371 | 372 | static int MINIXCompat_Processes_HostSignalForMINIXSignal(minix_signal_t minix_signal) 373 | { 374 | switch (minix_signal) { 375 | case minix_SIGHUP: return SIGHUP; 376 | case minix_SIGINT: return SIGINT; 377 | case minix_SIGQUIT: return SIGQUIT; 378 | case minix_SIGILL: return SIGILL; 379 | case minix_SIGTRAP: return SIGTRAP; 380 | case minix_SIGABRT: return SIGABRT; 381 | case minix_SIGUNUSED: return SIGXFSZ; // Should never be used, but available just in case. 382 | case minix_SIGFPE: return SIGFPE; 383 | case minix_SIGKILL: return SIGKILL; 384 | case minix_SIGUSR1: return SIGUSR1; 385 | case minix_SIGSEGV: return SIGSEGV; 386 | case minix_SIGUSR2: return SIGUSR2; 387 | case minix_SIGPIPE: return SIGPIPE; 388 | case minix_SIGALRM: return SIGALRM; 389 | case minix_SIGTERM: return SIGTERM; 390 | case minix_SIGSTKFLT: return SIGXCPU; // Doesn't really exist for us so just use a signal we're unlikely to get. 391 | } 392 | } 393 | 394 | /*! Return the MINIX signal that correponds to the given host signal; if there isn't one, returns `0`. */ 395 | static minix_signal_t MINIXCompat_Processes_MINIXSignalForHostSignal(int host_signal) 396 | { 397 | switch (host_signal) { 398 | case SIGHUP: return minix_SIGHUP; 399 | case SIGINT: return minix_SIGINT; 400 | case SIGQUIT: return minix_SIGQUIT; 401 | case SIGILL: return minix_SIGILL; 402 | case SIGTRAP: return minix_SIGTRAP; 403 | case SIGABRT: return minix_SIGABRT; 404 | case SIGXFSZ: return minix_SIGUNUSED; // Should never be used, but available just in case. 405 | case SIGFPE: return minix_SIGFPE; 406 | case SIGKILL: return minix_SIGKILL; 407 | case SIGUSR1: return minix_SIGUSR1; 408 | case SIGSEGV: return minix_SIGSEGV; 409 | case SIGUSR2: return minix_SIGUSR2; 410 | case SIGPIPE: return minix_SIGPIPE; 411 | case SIGALRM: return minix_SIGALRM; 412 | case SIGTERM: return minix_SIGTERM; 413 | case SIGXCPU: return minix_SIGSTKFLT; // Doesn't really exist for us so just use a signal we're unlikely to get. 414 | default: return 0; // indicate that MINIX doesn't support this signal 415 | } 416 | } 417 | 418 | static bool MINIXCompat_Processes_HasPendingSignal = false; 419 | static bool MINIXCompat_Processes_PendingSignals[17] = { false }; 420 | 421 | /*! Indicate that a signal was received and needs to be processed. */ 422 | static void MINIXCompat_Processes_RegisterPendingSignal(int host_signal) 423 | { 424 | minix_signal_t minix_signal = MINIXCompat_Processes_MINIXSignalForHostSignal(host_signal); 425 | if (minix_signal != 0) { 426 | MINIXCompat_Processes_HasPendingSignal = true; 427 | MINIXCompat_Processes_PendingSignals[minix_signal] = true; 428 | } 429 | } 430 | 431 | static void MINIXCompat_Processes_SignalHandler_DFL(int host_signal) 432 | { 433 | MINIXCompat_Processes_RegisterPendingSignal(host_signal); 434 | } 435 | 436 | static void MINIXCompat_Processes_SignalHandler_Other(int host_signal) 437 | { 438 | MINIXCompat_Processes_RegisterPendingSignal(host_signal); 439 | } 440 | 441 | static void MINIXCompat_Processes_HandlePendingSignal(minix_signal_t minix_signal) 442 | { 443 | minix_sighandler_t handler = minix_signal_handlers[minix_signal]; 444 | 445 | if (handler == minix_SIG_IGN) { 446 | return; 447 | } else if (handler == minix_SIG_DFL) { 448 | // Handle default behavior for the signal. 449 | #if DEBUG_SIGNAL 450 | MINIXCompat_Log("default signal handler for %d called", minix_signal); 451 | #endif 452 | // TODO: Default handler behavior for minix_signal. 453 | return; 454 | } else if (handler == minix_SIG_ERR) { 455 | // Representation of an error, should never be called but just in case it is... 456 | #if DEBUG_SIGNAL 457 | MINIXCompat_Log("error signal handler for %d", minix_signal); 458 | #endif 459 | // Do nothing. 460 | return; 461 | } else { 462 | // A real 68K handler was specified, set it up to be called. 463 | 464 | /* 465 | Here's our theory of operation, based on a suggestion by Warren Toomey. 466 | 467 | When we have a 68K signal handler to execute, we have to meet the expectations of the `_begsig` library function which always wraps the actual signal handler: 468 | 469 | 1. Push the current PC. 470 | 2. Push the current SR. 471 | 3. Push the signal number. 472 | 4. Set the current PC to the signal handler address (which will be `_begsig`). 473 | 474 | Then when we next run the emulator, it will run the signal handler, which expects the first thing on the stack to be the signal number. When that's done, it adjusts the stack and does an `RTR` will restore the SR and PC to what it was before running the handler, thus resuming execution where it left off. 475 | 476 | This doesn't support code that does a `longjmp(3)` out of a signal handler but it's not clear whether MINIX 1.5 supported such code either. 477 | */ 478 | 479 | m68k_address_t pc = MINIXCompat_CPU_GetPC(); 480 | MINIXCompat_CPU_Push_32(pc); 481 | uint16_t sr = MINIXCompat_CPU_GetSR(); 482 | MINIXCompat_CPU_Push_16(sr); 483 | MINIXCompat_CPU_Push_16(minix_signal); 484 | MINIXCompat_CPU_SetPC(handler); 485 | } 486 | } 487 | 488 | void MINIXCompat_Processes_HandlePendingSignals(void) 489 | { 490 | if (MINIXCompat_Processes_HasPendingSignal) { 491 | MINIXCompat_Processes_HasPendingSignal = false; 492 | for (minix_signal_t minix_signal = minix_SIGHUP; 493 | minix_signal <= minix_SIGSTKFLT; 494 | minix_signal++) 495 | { 496 | if (MINIXCompat_Processes_PendingSignals[minix_signal]) { 497 | MINIXCompat_Processes_PendingSignals[minix_signal] = false; 498 | MINIXCompat_Processes_HandlePendingSignal(minix_signal); 499 | } 500 | } 501 | } 502 | } 503 | 504 | static void *MINIXCompat_Processes_HostSignalHandlerForMINIXSignalHandler(minix_sighandler_t minix_handler) 505 | { 506 | if (minix_handler == minix_SIG_DFL) { 507 | return MINIXCompat_Processes_SignalHandler_DFL; 508 | } else if (minix_handler == minix_SIG_IGN) { 509 | return SIG_IGN; 510 | } else if (minix_handler == minix_SIG_ERR) { 511 | return SIG_ERR; 512 | } else { 513 | return MINIXCompat_Processes_SignalHandler_Other; 514 | } 515 | } 516 | 517 | minix_sighandler_t MINIXCompat_Processes_signal(minix_signal_t minix_signal, minix_sighandler_t minix_handler) 518 | { 519 | assert((minix_signal >= minix_SIGHUP) && (minix_signal <= minix_SIGSTKFLT)); 520 | 521 | // Update the MINIX signal table. 522 | 523 | minix_sighandler_t old_minix_handler = minix_signal_handlers[minix_signal]; 524 | minix_signal_handlers[minix_signal] = minix_handler; 525 | 526 | // Register a host-side handler for the given signal. 527 | 528 | int host_signal = MINIXCompat_Processes_HostSignalForMINIXSignal(minix_signal); 529 | void *host_handler = MINIXCompat_Processes_HostSignalHandlerForMINIXSignalHandler(minix_handler); 530 | 531 | void *old_host_handler = signal(host_signal, host_handler); 532 | 533 | if (old_host_handler == SIG_DFL) { 534 | old_minix_handler = minix_SIG_DFL; 535 | } else if (old_host_handler == SIG_IGN) { 536 | old_minix_handler = minix_SIG_IGN; 537 | } else if (old_host_handler == SIG_ERR) { 538 | old_minix_handler = minix_SIG_ERR; 539 | } else { 540 | // Just preserve the old MINIX handler as retrieved from the table. 541 | } 542 | 543 | #if DEBUG_PROCESS_SYSCALLS 544 | { 545 | const char *signal_name = MINIXCompat_Processes_NameForMINIXSignal(minix_signal); 546 | const char *new_handler_name = MINIXCompat_Processes_NameForMINIXSignalHandler(minix_handler); 547 | const char *old_handler_name = MINIXCompat_Processes_NameForMINIXSignalHandler(old_minix_handler); 548 | 549 | MINIXCompat_Log("signal(%s (%d), %s) -> %s", signal_name, minix_signal, new_handler_name, old_handler_name); 550 | } 551 | #endif 552 | 553 | return old_minix_handler; 554 | } 555 | 556 | int16_t MINIXCompat_Processes_kill(minix_pid_t minix_pid, minix_signal_t minix_signal) 557 | { 558 | int16_t result; 559 | 560 | assert(minix_pid > 0); 561 | assert((minix_signal >= minix_SIGHUP) && (minix_signal <= minix_SIGSTKFLT)); 562 | 563 | int host_signal = MINIXCompat_Processes_HostSignalForMINIXSignal(minix_signal); 564 | if (host_signal <= 0) { 565 | result = -minix_EINVAL; 566 | goto done; 567 | } 568 | 569 | pid_t host_pid = (minix_pid > 0) ? MINIXCompat_Processes_HostProcessForMINIXProcess(minix_pid) : minix_pid; 570 | if (host_pid <= 0) { 571 | result = -minix_ESRCH; 572 | goto done; 573 | } 574 | 575 | int kill_result = kill(host_pid, host_signal); 576 | if (kill_result == -1) { 577 | result = -MINIXCompat_Errors_MINIXErrorForHostError(errno); 578 | } else { 579 | result = kill_result; 580 | } 581 | 582 | done: 583 | #if DEBUG_PROCESS_SYSCALLS 584 | { 585 | const char *minix_signal_name = MINIXCompat_Processes_NameForMINIXSignal(minix_signal); 586 | MINIXCompat_Log("kill(%d, %s (%d)) -> %d", minix_pid, minix_signal_name, minix_signal, -minix_EINVAL); 587 | } 588 | #endif 589 | 590 | return result; 591 | } 592 | 593 | 594 | // MARK: - Exec 595 | 596 | static void MINIXCompat_Arguments_Initialize(uint32_t host_argc, char **host_argv, uint32_t host_envc, char **host_envp) 597 | { 598 | /*! Round up a value to the next multiple of 4. 0 = 0 but 1..3 = 4, 5..7 = 8, etc. */ 599 | #define round_up_32(x) ((x) + (4 - ((x) % 4))) 600 | 601 | /* 602 | Note there is no envc but we of course know the count of envp entries too on our way in. 603 | 604 | The region at and above the stack pointer is as follows: 605 | argc 606 | argv[0] (tool) 607 | argv[1]..argv[argc-1] 608 | NULL 609 | envp[0]..envp[envc-1] 610 | NULL 611 | 612 | This leads to the following: 613 | 1. &argc is sp 614 | 2. &argv[n] is (sp+4)+(n*4) 615 | 3. &argv[argc] is (sp+4)+(argc*4) and contains NULL 616 | 4. &envp[n] is &argv[argc+n]+4 617 | 5. &envp[envc] contains NULL 618 | 619 | All the actual string content comes after the argc/argv/envp, with each entry 4-byte aligned. 620 | */ 621 | 622 | uint32_t argc_argv_envp_count = 1 + (host_argc + 1) + (host_envc + 1); 623 | uint32_t argc_ragv_envp_size = argc_argv_envp_count * sizeof(uint32_t); 624 | uint32_t *argc_argv_envp = calloc(argc_argv_envp_count, sizeof(uint32_t)); 625 | uint32_t content_size = 0; 626 | char *content = NULL; 627 | 628 | // Figure out content_size first and set up a buffer for it. 629 | 630 | { 631 | for (char **iter_argv = host_argv; *iter_argv != NULL; iter_argv++) { 632 | char *argv_n = *iter_argv; 633 | uint32_t iter_argv_len = (uint32_t)strlen(argv_n) + 1; 634 | content_size += round_up_32(iter_argv_len); 635 | } 636 | 637 | for (char **iter_envp = host_envp; *iter_envp != NULL; iter_envp++) { 638 | char *envp_n = *iter_envp; 639 | if (strncmp("MINIX_", envp_n, 6) == 0) { 640 | uint32_t iter_envp_len = (uint32_t)strlen(envp_n) + 1 - 6; 641 | content_size += round_up_32(iter_envp_len); 642 | } 643 | } 644 | 645 | content = calloc(content_size, sizeof(char)); 646 | } 647 | 648 | // Copy the content into place and put the emulator-side pointers to it in place as well. 649 | 650 | { 651 | int argc_argv_envp_idx = 0; 652 | uint32_t content_offset = 0; 653 | 654 | // Start with argc. 655 | 656 | argc_argv_envp[argc_argv_envp_idx++] = htonl(host_argc); 657 | 658 | // Copy and put in place pointers to argv and envp. 659 | 660 | for (char **iter_argv = host_argv; *iter_argv != NULL; iter_argv++) { 661 | char *argv_n = *iter_argv; 662 | size_t argv_n_len = (uint32_t)strlen(argv_n) + 1; 663 | strncpy(&content[content_offset], argv_n, content_size - content_offset); 664 | m68k_address_t content_addr = MINIXCompat_Stack_Base + argc_ragv_envp_size + content_offset; 665 | argc_argv_envp[argc_argv_envp_idx++] = htonl(content_addr); 666 | content_offset += round_up_32(argv_n_len); 667 | } 668 | 669 | argc_argv_envp[argc_argv_envp_idx++] = htonl(0); 670 | 671 | for (char **iter_envp = host_envp; *iter_envp != NULL; iter_envp++) { 672 | char *envp_n = *iter_envp; 673 | if (strncmp("MINIX_", envp_n, 6) == 0) { 674 | size_t envp_n_len = (uint32_t)strlen(envp_n) + 1 - 6; 675 | strncpy(&content[content_offset], &envp_n[6], content_size - content_offset); 676 | m68k_address_t content_addr = MINIXCompat_Stack_Base + argc_ragv_envp_size + content_offset; 677 | argc_argv_envp[argc_argv_envp_idx++] = htonl(content_addr); 678 | content_offset += round_up_32(envp_n_len); 679 | } 680 | } 681 | 682 | argc_argv_envp[argc_argv_envp_idx++] = htonl(0); 683 | } 684 | 685 | // Copy buffers from the host to the emulated environment, making them contiguous. 686 | 687 | MINIXCompat_RAM_Copy_Block_From_Host(MINIXCompat_Stack_Base, argc_argv_envp, argc_ragv_envp_size); 688 | MINIXCompat_RAM_Copy_Block_From_Host(MINIXCompat_Stack_Base + argc_ragv_envp_size, content, content_size); 689 | 690 | // Free the host-side buffers now. 691 | 692 | free(argc_argv_envp); 693 | free(content); 694 | #undef round_up_32 695 | } 696 | 697 | static int16_t MINIXCompat_Processes_LoadTool(const char *executable_path) 698 | { 699 | int16_t result; 700 | 701 | // TODO: Support interpreter scripts 702 | /* 703 | We could do this by parsing the first line for a #! prefix and using the interpreter there as the tool to run, with the given arguments. 704 | 705 | I.e. if /tmp/foo.sh is an executable script that starts with `#!/bin/sh`, that means we'd load /bin/sh, pass /tmp/foo.sh as argv[1], and pass the other arguments as argv[2] and so on. 706 | */ 707 | 708 | // Get the path to the tool to run and ensure it actually exists. 709 | 710 | FILE *toolfile = NULL; 711 | char *executable_host_path = MINIXCompat_Filesystem_CopyHostPathForPath(executable_path); 712 | struct stat executable_host_stat; 713 | int stat_err = stat(executable_host_path, &executable_host_stat); 714 | if (stat_err == -1) { 715 | result = -MINIXCompat_Errors_MINIXErrorForHostError(errno); 716 | goto done; 717 | } 718 | 719 | // Load the tool into host memory, relocate it, and load the relocated tool into emulator memory. 720 | 721 | toolfile = fopen(executable_host_path, "r"); 722 | if (toolfile == NULL) { 723 | result = -MINIXCompat_Errors_MINIXErrorForHostError(EIO); 724 | goto done; 725 | } 726 | 727 | struct MINIXCompat_Executable *executable = NULL; 728 | uint8_t *executable_text_and_data = NULL; 729 | uint32_t executable_text_and_data_len = 0; 730 | 731 | int load_err = MINIXCompat_Executable_Load(toolfile, &executable, &executable_text_and_data, &executable_text_and_data_len); 732 | if (load_err != 0) { 733 | result = load_err; 734 | goto done; 735 | } 736 | 737 | MINIXCompat_RAM_Copy_Block_From_Host(MINIXCompat_Executable_Base, executable_text_and_data, executable_text_and_data_len); 738 | result = 0; 739 | 740 | done: 741 | if (toolfile) { 742 | fclose(toolfile); 743 | } 744 | 745 | free(executable_host_path); 746 | 747 | return result; 748 | } 749 | 750 | int16_t MINIXCompat_Processes_ExecuteWithStackBlock(const char *executable_path, void *stack_on_host, int16_t stack_size) 751 | { 752 | // Load and relocate the executable. 753 | 754 | int16_t load_err = MINIXCompat_Processes_LoadTool(executable_path); 755 | if (load_err != 0) { 756 | return load_err; 757 | } 758 | 759 | // Relocate the stack. 760 | 761 | uint32_t *stack = stack_on_host; 762 | stack++; // skip argc 763 | 764 | while (*stack != 0) { 765 | uint32_t relocated_argv = ntohl(*stack) + MINIXCompat_Stack_Base; 766 | *stack = htonl(relocated_argv); 767 | stack++; 768 | } 769 | 770 | stack++; // skip argv[argc] 771 | 772 | while (*stack != 0) { 773 | uint32_t relocated_envp = ntohl(*stack) + MINIXCompat_Stack_Base; 774 | *stack = htonl(relocated_envp); 775 | stack++; 776 | } 777 | 778 | // Load the relocated stack into emulator RAM. 779 | 780 | MINIXCompat_RAM_Copy_Block_From_Host(MINIXCompat_Stack_Base, stack_on_host, stack_size); 781 | 782 | // Ready to go! The ready state reinitializes the emulator; any existing emulator-side state is blown away. 783 | 784 | MINIXCompat_Execution_ChangeState(MINIXCompat_Execution_State_Ready); 785 | 786 | #if DEBUG_PROCESS_SYSCALLS 787 | MINIXCompat_Log("exec(\"%s\")", executable_path); 788 | #endif 789 | 790 | return 0; 791 | } 792 | 793 | int16_t MINIXCompat_Processes_ExecuteWithHostParams(const char *executable_path, int16_t argc, char * _Nullable * _Nonnull argv, char * _Nullable * _Nonnull envp) 794 | { 795 | // Load and relocate the executable. 796 | 797 | int16_t load_err = MINIXCompat_Processes_LoadTool(executable_path); 798 | if (load_err != 0) { 799 | return load_err; 800 | } 801 | 802 | // Set up the MINIX host-side argc, argv, and envp, and put them in their well-known locations in the "prix fixe" stack. 803 | // Note that when copying environment variables, only those prefixed with `MINIX_` are copied, allowing fine-grained control. 804 | 805 | uint32_t host_argc = argc - 1; 806 | char **host_argv = &argv[1]; 807 | uint32_t host_envc = 0; 808 | char **host_envp = envp; 809 | 810 | for (char **iter_envp = envp; *iter_envp; iter_envp++) { 811 | if (strncmp("MINIX_", *iter_envp, 6) == 0) { 812 | host_envc += 1; 813 | } 814 | } 815 | 816 | // Place the arguments into MINIX memory. 817 | 818 | MINIXCompat_Arguments_Initialize(host_argc, host_argv, host_envc, host_envp); 819 | 820 | // Ready to go! The ready state reinitializes the emulator; any existing emulator-side state is blown away. 821 | 822 | MINIXCompat_Execution_ChangeState(MINIXCompat_Execution_State_Ready); 823 | 824 | #if DEBUG_PROCESS_SYSCALLS 825 | MINIXCompat_Log("exec_host(\"%s\")", executable_path); 826 | #endif 827 | 828 | return 0; 829 | } 830 | 831 | 832 | // MARK: - "Break" Handling 833 | 834 | int16_t MINIXCompat_Processes_brk(m68k_address_t minix_requested_addr, m68k_address_t *minix_resulting_addr) 835 | { 836 | int16_t result; 837 | 838 | assert(minix_resulting_addr != NULL); 839 | 840 | // There is only one process and it has full run of the address space up to 0x00FE0000, so just allow any value up to that. Also keep track of the current break so it can be properly returned when requested. 841 | 842 | static m68k_address_t minix_current_break = 0; 843 | 844 | if (minix_current_break == 0) { 845 | minix_current_break = MINIXCompat_Executable_Get_Initial_Break(); 846 | } 847 | 848 | if ((minix_requested_addr < MINIXCompat_Executable_Limit) 849 | && (minix_requested_addr >= MINIXCompat_Executable_Get_Initial_Break())) 850 | { 851 | result = 0; 852 | *minix_resulting_addr = minix_requested_addr; 853 | minix_current_break = minix_requested_addr; 854 | } else { 855 | result = -minix_ENOMEM; 856 | *minix_resulting_addr = 0xFFFFFFFF; // MINIX-side ((char *)-1) value 857 | } 858 | 859 | #if DEBUG_PROCESS_SYSCALLS 860 | MINIXCompat_Log("brk(0x%08x, %p = 0x%08x) -> %d", minix_requested_addr, minix_resulting_addr, minix_requested_addr, result); 861 | #endif 862 | 863 | return result; 864 | } 865 | 866 | 867 | MINIXCOMPAT_SOURCE_END 868 | -------------------------------------------------------------------------------- /MINIXCompat/MINIXCompat_Processes.h: -------------------------------------------------------------------------------- 1 | // 2 | // MINIXCompat_Processes.h 3 | // MINIXCompat 4 | // 5 | // Created by Chris Hanson on 11/30/24. 6 | // Copyright © 2024 Christopher M. Hanson. See file LICENSE for details. 7 | // 8 | 9 | #ifndef MINIXCompat_Processes_h 10 | #define MINIXCompat_Processes_h 11 | 12 | #include 13 | 14 | #include "MINIXCompat_Types.h" 15 | 16 | 17 | MINIXCOMPAT_HEADER_BEGIN 18 | 19 | 20 | /*! The type of a MINIX process ID. */ 21 | typedef int16_t minix_pid_t; 22 | 23 | 24 | /*! Initialize the Processes subsystem. */ 25 | MINIXCOMPAT_EXTERN void MINIXCompat_Processes_Initialize(void); 26 | 27 | 28 | /*! Get the process ID and parent process ID for the running MINIX process. */ 29 | MINIXCOMPAT_EXTERN void MINIXCompat_Processes_GetProcessIDs(minix_pid_t * _Nonnull minix_pid, minix_pid_t * _Nonnull minix_ppid); 30 | 31 | /*! 32 | Fork the current process. 33 | 34 | All of this process's state is copied to the new process. 35 | 36 | - Returns: On success, 0 to the new process and the pid of the new process to the parent process. 37 | On failure, `-errno` to the parent process (no new process is started). 38 | */ 39 | MINIXCOMPAT_EXTERN minix_pid_t MINIXCompat_Processes_fork(void); 40 | 41 | /*! 42 | Wait for a child process to exit and record its status. 43 | */ 44 | MINIXCOMPAT_EXTERN minix_pid_t MINIXCompat_Processes_wait(int16_t * _Nonnull minix_stat_loc); 45 | 46 | /*! 47 | Exit with a given status. 48 | */ 49 | MINIXCOMPAT_EXTERN void MINIXCompat_Processes_exit(int16_t minix_status); 50 | 51 | /*! 52 | The exit status to use. 53 | */ 54 | MINIXCOMPAT_EXTERN int MINIXCompat_Processes_ExitStatus; 55 | 56 | 57 | /*! The type of a MINIX signal. */ 58 | typedef enum minix_signal: int16_t { 59 | minix_SIGHUP = 1, 60 | minix_SIGINT = 2, 61 | minix_SIGQUIT = 3, 62 | minix_SIGILL = 4, 63 | minix_SIGTRAP = 5, 64 | minix_SIGABRT = 6, 65 | minix_SIGIOT = 6, 66 | minix_SIGUNUSED = 7, 67 | minix_SIGFPE = 8, 68 | minix_SIGKILL = 9, 69 | minix_SIGUSR1 = 10, 70 | minix_SIGSEGV = 11, 71 | minix_SIGUSR2 = 12, 72 | minix_SIGPIPE = 13, 73 | minix_SIGALRM = 14, 74 | minix_SIGTERM = 15, 75 | minix_SIGSTKFLT = 16, 76 | } minix_signal_t; 77 | 78 | 79 | /*! The type of a MINIX signal handler. (Really a 68K-side pointer to a function that takes an `int16_t`. */ 80 | typedef m68k_address_t minix_sighandler_t; 81 | 82 | /*! The token that indicates the default MINIX signal handler. */ 83 | MINIXCOMPAT_EXTERN minix_sighandler_t minix_SIG_DFL; 84 | 85 | /*! The token that indicates the ignoring MINIX signal handler. */ 86 | MINIXCOMPAT_EXTERN minix_sighandler_t minix_SIG_IGN; 87 | 88 | /*! The token that indicates the "error" MINIX signal handler, which is really a return code. */ 89 | MINIXCOMPAT_EXTERN minix_sighandler_t minix_SIG_ERR; 90 | 91 | 92 | /*! 93 | Set up a signal handler for the current process. 94 | */ 95 | MINIXCOMPAT_EXTERN minix_sighandler_t MINIXCompat_Processes_signal(minix_signal_t minix_signal, minix_sighandler_t handler); 96 | 97 | 98 | /*! 99 | Send a signal to a process. 100 | */ 101 | MINIXCOMPAT_EXTERN int16_t MINIXCompat_Processes_kill(minix_pid_t minix_pid, minix_signal_t minix_signal); 102 | 103 | 104 | /*! 105 | Handle any pending signals. 106 | */ 107 | MINIXCOMPAT_EXTERN void MINIXCompat_Processes_HandlePendingSignals(void); 108 | 109 | 110 | /*! 111 | Load and run a compiled MINIX executable or interpreter file the same way `exec(2)` would. 112 | 113 | - Parameters: 114 | - executable_path: Absolute or relative MINIX path to executable file. 115 | - executbale_path_len: The length of `minix_path`. 116 | - stack_on_host: The 0-based, Motorola-order stack to start with, which contains `argc`, `argv`, `envp`, and their contents. 117 | - stack_size: The size of `stack_on_host`. 118 | 119 | - Warning: This resets the emulation environment and should only be invoked after startup or a `fork(2)` call. 120 | */ 121 | MINIXCOMPAT_EXTERN int16_t MINIXCompat_Processes_ExecuteWithStackBlock(const char *executable_path, void *stack_on_host, int16_t stack_size); 122 | 123 | 124 | /*! 125 | Load and run a compiled MINIX executable or interpreter file the same way `exec(2)` would. 126 | 127 | - Parameters: 128 | - executable_path: Absolute or relative MINIX path to executable file. 129 | - executbale_path_len: The length of `minix_path`. 130 | - argc: The number of elements in `argv`. 131 | - argv: The executable's arguments. 132 | - envp: The executable's environment. 133 | 134 | - Warning: This resets the emulation environment and should only be invoked after startup or a `fork(2)` call. 135 | */ 136 | MINIXCOMPAT_EXTERN int16_t MINIXCompat_Processes_ExecuteWithHostParams(const char *executable_path, int16_t argc, char * _Nullable * _Nonnull argv, char * _Nullable * _Nonnull envp); 137 | 138 | 139 | /*! 140 | Adjust the "break," which is the size of uninitialized data for the process. 141 | 142 | - Note: The break can never be lower than the BSS but can extend to the top of the area reserved for the stack. 143 | */ 144 | MINIXCOMPAT_EXTERN int16_t MINIXCompat_Processes_brk(m68k_address_t minix_requested_addr, m68k_address_t * _Nonnull minix_resulting_addr); 145 | 146 | 147 | MINIXCOMPAT_HEADER_END 148 | 149 | 150 | #endif /* MINIXCompat_Processes_h */ 151 | -------------------------------------------------------------------------------- /MINIXCompat/MINIXCompat_SysCalls.h: -------------------------------------------------------------------------------- 1 | // 2 | // MINIXCompat_SysCalls.h 3 | // MINIXCompat 4 | // 5 | // Created by Chris Hanson on 11/30/24. 6 | // Copyright © 2024 Christopher M. Hanson. See file LICENSE for details. 7 | // 8 | 9 | #ifndef MINIXCompat_SysCalls_h 10 | #define MINIXCompat_SysCalls_h 11 | 12 | #include 13 | 14 | #include "MINIXCompat_Types.h" 15 | 16 | 17 | MINIXCOMPAT_HEADER_BEGIN 18 | 19 | 20 | /*! system calls to mm/fs */ 21 | typedef enum minix_syscall: int16_t { 22 | minix_syscall_unused0 = 0, 23 | minix_syscall_exit = 1, 24 | minix_syscall_fork = 2, 25 | minix_syscall_read = 3, 26 | minix_syscall_write = 4, 27 | minix_syscall_open = 5, 28 | minix_syscall_close = 6, 29 | minix_syscall_wait = 7, 30 | minix_syscall_creat = 8, 31 | minix_syscall_link = 9, 32 | minix_syscall_unlink = 10, 33 | minix_syscall_exec = 11, 34 | minix_syscall_chdir = 12, 35 | minix_syscall_time = 13, 36 | minix_syscall_mknod = 14, 37 | minix_syscall_chmod = 15, 38 | minix_syscall_chown = 16, 39 | minix_syscall_break = 17, 40 | minix_syscall_stat = 18, 41 | minix_syscall_lseek = 19, 42 | minix_syscall_getpid = 20, 43 | minix_syscall_mount = 21, 44 | minix_syscall_umount = 22, 45 | minix_syscall_setuid = 23, 46 | minix_syscall_getuid = 24, 47 | minix_syscall_stime = 25, 48 | minix_syscall_ptrace = 26, 49 | minix_syscall_alarm = 27, 50 | minix_syscall_fstat = 28, 51 | minix_syscall_pause = 29, 52 | minix_syscall_utime = 30, 53 | minix_syscall_stty = 31, 54 | minix_syscall_gtty = 32, 55 | minix_syscall_access = 33, 56 | minix_syscall_nice = 34, 57 | minix_syscall_ftime = 35, 58 | minix_syscall_sync = 36, 59 | minix_syscall_kill = 37, 60 | minix_syscall_rename = 38, 61 | minix_syscall_mkdir = 39, 62 | minix_syscall_rmdir = 40, 63 | minix_syscall_dup = 41, 64 | minix_syscall_pipe = 42, 65 | minix_syscall_times = 43, 66 | minix_syscall_prof = 44, 67 | minix_syscall_unused45 = 45, 68 | minix_syscall_setgid = 46, 69 | minix_syscall_getgid = 47, 70 | minix_syscall_sig = 48, 71 | minix_syscall_unused49 = 49, 72 | minix_syscall_unused50 = 50, 73 | minix_syscall_acct = 51, 74 | minix_syscall_phys = 52, 75 | minix_syscall_lock = 53, 76 | minix_syscall_ioctl = 54, 77 | minix_syscall_fcntl = 55, 78 | minix_syscall_mpx = 56, 79 | minix_syscall_unused57 = 57, 80 | minix_syscall_unused58 = 58, 81 | minix_syscall_exece = 59, 82 | minix_syscall_umask = 60, 83 | minix_syscall_chroot = 61, 84 | minix_syscall_unused62 = 62, 85 | minix_syscall_unused63 = 63, 86 | minix_syscall_KSIG = 64, /* signals originating in the kernel */ 87 | minix_syscall_UNPAUSE = 65, 88 | minix_syscall_BRK2 = 66, /* used to tell MM size of FS,INIT */ 89 | minix_syscall_REVIVE = 67, 90 | minix_syscall_TASK_REPLY = 68, 91 | minix_syscall_unused69 = 69, 92 | } minix_syscall_t; 93 | 94 | 95 | /*! 96 | The `func` parameter to the `send`, `receive`, and `sendrec` system calls. 97 | */ 98 | typedef enum minix_syscall_func: uint16_t { 99 | /*! Send the message, blocking until sent.. */ 100 | minix_syscall_func_send = 1, 101 | 102 | /*! Receive a message, blocking until one is available. */ 103 | minix_syscall_func_receive = 2, 104 | 105 | /*! Send the message then receive a response to it, blocking until the response is received. */ 106 | minix_syscall_func_both = 3, 107 | } minix_syscall_func_t; 108 | 109 | 110 | /*! 111 | Initialize the System Call subsystem. The only way this can fail is if other things fail to initialize first. 112 | */ 113 | MINIXCOMPAT_EXTERN void MINIXCompat_SysCall_Initialize(void); 114 | 115 | 116 | /*! 117 | The result of a system call indicates whether/how to pass a value back in `d0.l` in the emulator. 118 | */ 119 | typedef enum minix_syscall_result: int16_t { 120 | /*! A host-side error of some sort that shouldn't be passed to the emulator occurred. */ 121 | minix_syscall_result_failure = -1, 122 | 123 | /*! The call completed successfully and has no updated `d0.l` value.*/ 124 | minix_syscall_result_success_empty = 0, 125 | 126 | /*! The call completed successfully and has an updated `d0.l` value. */ 127 | minix_syscall_result_success = 1, 128 | } minix_syscall_result_t; 129 | 130 | /*! 131 | MINIX 1.5.10.7 has only one system call, which is "send or receive a message (or both)." 132 | 133 | MINIX implements this as an M68000 `TRAP #0` instruction with the following register usage: 134 | - On call: 135 | - `d0.w` contains the function (a ``minix_syscall_func_t``). 136 | - `d1.l` contains the src_dest (the task to which the message should be sent to and/or recieved from); and, 137 | - `a0` contains a pointer to the message itself. 138 | - On return: 139 | - `d0.l` *may* contain a result. 140 | 141 | MINIX can also save and restore task state, including performing task switches, around system call invocations. However our implementation doesn't since it only runs a single task. 142 | 143 | The return value from this function is a tri-state ``minix_syscall_result_t``. 144 | */ 145 | MINIXCOMPAT_EXTERN minix_syscall_result_t MINIXCompat_System_Call(minix_syscall_func_t func, uint16_t src_dest, m68k_address_t msg, uint32_t * _Nonnull out_result); 146 | 147 | 148 | MINIXCOMPAT_HEADER_END 149 | 150 | 151 | #endif /* MINIXCompat_SysCalls_h */ 152 | -------------------------------------------------------------------------------- /MINIXCompat/MINIXCompat_Types.h: -------------------------------------------------------------------------------- 1 | // 2 | // MINIXCompat_Types.h 3 | // MINIXCompat 4 | // 5 | // Created by Chris Hanson on 11/16/24. 6 | // Copyright © 2024 Christopher M. Hanson. See file LICENSE for details. 7 | // 8 | 9 | #ifndef MINIXCompat_Types_h 10 | #define MINIXCompat_Types_h 11 | 12 | #include 13 | 14 | 15 | /* Useful macros for wrapping headers and sources. */ 16 | 17 | #define MINIXCOMPAT_ASSUME_NONNULL_BEGIN _Pragma("clang assume_nonnull begin") 18 | #define MINIXCOMPAT_ASSUME_NONNULL_END _Pragma("clang assume_nonnull end") 19 | 20 | #define MINIXCOMPAT_HEADER_BEGIN MINIXCOMPAT_ASSUME_NONNULL_BEGIN 21 | #define MINIXCOMPAT_HEADER_END MINIXCOMPAT_ASSUME_NONNULL_END 22 | 23 | #define MINIXCOMPAT_SOURCE_BEGIN MINIXCOMPAT_ASSUME_NONNULL_BEGIN 24 | #define MINIXCOMPAT_SOURCE_END MINIXCOMPAT_ASSUME_NONNULL_END 25 | 26 | 27 | MINIXCOMPAT_HEADER_BEGIN 28 | 29 | 30 | /*! Mark an "external" C symbol even in C++. */ 31 | #ifdef __cplusplus 32 | #define MINIXCOMPAT_EXTERN extern "C" 33 | #else 34 | #define MINIXCOMPAT_EXTERN extern 35 | #endif 36 | 37 | 38 | /*! Pack a structure, which many 68000 compilers do by default. */ 39 | #define MINIXCOMPAT_PACK_STRUCT __attribute__((packed)) 40 | 41 | 42 | /*! 43 | An address within the m68k address space, which is 32-bit. 44 | 45 | The address must actually be constrained to 24 bits, since we're using pure 68000 emulation. 46 | 47 | - Note: This kind of type should really come from Musashi, as should emulator-specific types for a byte, word, and long word. 48 | */ 49 | typedef uint32_t m68k_address_t; 50 | 51 | 52 | MINIXCOMPAT_HEADER_END 53 | 54 | 55 | #endif /* MINIXCompat_Types_h */ 56 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .POSIX: 2 | 3 | CC = clang 4 | 5 | #CFLAGS = -Os -Wall 6 | CFLAGS = -g -DDEBUG=1 -O0 -Wall 7 | CPPFLAGS += -MMD -IMusashi -IMINIXCompat -DMUSASHI_CNF='"MINIXCompat_Musashi.h"' 8 | LIBS ::= -lm 9 | 10 | CC_FOR_BUILD ?= $(CC) 11 | CFLAGS_FOR_BUILD ?= $(CFLAGS) 12 | CPPFLAGS_FOR_BUILD ?= $(CPPFLAGS) 13 | LDFLAGS_FOR_BUILD ?= $(LDFLAGS) 14 | 15 | PREFIX ?= /usr/local 16 | EXEC_PREFIX ?= $(PREFIX) 17 | BINDIR ?= $(EXEC_PREFIX)/bin 18 | DATAROOTDIR ?= $(PREFIX)/share 19 | MANDIR ?= $(DATAROOTDIR)/man 20 | 21 | .SUFFIXES: 22 | .SUFFIXES: .c .o 23 | 24 | MINIXCOMPAT_SRC != find MINIXCompat -name '*.c' 25 | MINIXCOMPAT_OBJ ::= $(MINIXCOMPAT_SRC:.c=.o) 26 | MINIXCOMPAT_BIN ::= MINIXCompat/MINIXCompat 27 | 28 | MUSASHI_SRC ::= Musashi/m68kcpu.c Musashi/m68kdasm.c Musashi/softfloat/softfloat.c 29 | MUSASHI_OBJ ::= $(MUSASHI_SRC:.c=.o) 30 | 31 | all: $(MINIXCOMPAT_BIN) 32 | .PHONY: all 33 | 34 | $(MINIXCOMPAT_BIN): $(MINIXCOMPAT_OBJ) $(MUSASHI_OBJ) 35 | $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LIBS) 36 | 37 | MINIXCompat/MINIXCompat_EmulationOps.o: Musashi/m68kops.c 38 | Musashi/m68kcpu.o: Musashi/m68kops.h 39 | 40 | MUSASHI_GEN_SRC ::= Musashi/m68kops.h Musashi/m68kops.c 41 | $(MUSASHI_GEN_SRC): Musashi/m68k_in.c Musashi/m68kmake 42 | Musashi/m68kmake Musashi $< 43 | 44 | Musashi/m68kmake: Musashi/m68kmake.c 45 | $(CC_FOR_BUILD) $(CPPFLAGS_FOR_BUILD) $(CFLAGS_FOR_BUILD) $(LDFLAGS_FOR_BUILD) -o $@ $< 46 | 47 | .c.o: 48 | $(CC) -c $(CPPFLAGS) $(CFLAGS) -o $@ $< 49 | 50 | clean: 51 | rm -f $(MINIXCOMPAT_BIN) Musashi/m68kmake $(MUSASHI_GEN_SRC) $(MUSASHI_OBJ) $(MINIXCOMPAT_OBJ) 52 | rm -f $(MINIXCOMPAT_SRC:.c=.d) $(MUSASHI_SRC:.c=.d) Musashi/m68kmake.d 53 | distclean: clean 54 | .PHONY: clean distclean 55 | 56 | install: all 57 | mkdir -p $(DESTDIR)$(BINDIR) 58 | install $(MINIXCOMPAT_BIN) $(DESTDIR)$(BINDIR) 59 | mkdir -p $(DESTDIR)$(MANDIR)/man1 60 | cp MINIXCompat/MINIXCompat.1 $(DESTDIR)$(MANDIR)/man1 61 | .PHONY: install 62 | 63 | -include $(MINIXCOMPAT_SRC:.c=.d) 64 | -include $(MUSASHI_SRC:.c=.d) 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MINIXCompat 2 | 3 | MINIXCompat is a compatibility environment that can run M68000 MINIX 1.5 4 | binaries on modern UNIX-style systems such as macOS and Linux. 5 | 6 | It consists of a version of the Musashi M68000 emulator that has been modified 7 | to perform a callback upon encountering a `TRAP` instruction, plus a system 8 | call emulator invoked by that callback that more-or-less directly translates 9 | the UNIX V7 system calls issued by MINIX binaries into modern POSIX system 10 | calls, and then translates their results back. 11 | 12 | 13 | ## The MINIXCompat Execution Environment 14 | 15 | MINIXCompat expects a `MINIXCOMPAT_DIR` environment variable to be set in the 16 | environment to indicate where an Atari ST MINIX 1.5.10.7 installation is 17 | located, and expects all execution to take place within this directory. If 18 | none is suppleid, a default of `/opt/minix` is used. 19 | 20 | To set the environment’s initial working directory, you can `cd` to a 21 | directory undereneath `MINIXCOMPAT_DIR` before invoking the environment, or 22 | you can specify an absolute path via the `MINIXCOMPAT_PWD` environment 23 | variable that will be treated as the directory within `MINIXCOMPAT_DIR` to use 24 | as the initial working directory. 25 | 26 | MINIXCompat is invoked via the command line using any number of arguments and 27 | no options; its first argument is the MINIX-style path to the MINIX executable 28 | to run, and all subsequent arguments are passed to the MINIX executable via 29 | its argument vector. The entire host environment is **not** passed to the 30 | MINIX executable, instead only environment variables with a `MINIX_` prefix 31 | are passed, without the prefix. 32 | 33 | I'm making [a tarball of an Atari ST MINIX 1.5.10.7 34 | installation](http://rendezvous.eschatologist.net/cmh/minix-st-1.5.10.7.tar.bz2) 35 | available which is essentially just an archive of the file hierarchy in 36 | the [ACSI disk image here](http://www.subsole.org/minix_on_the_atari_st). 37 | 38 | 39 | ## Future Work 40 | 41 | The primary goal for MINIXCompat is to enable compilation of M68000 MINIX 42 | (such as Atari ST MINIX) from within a modern UNIX-style environment. 43 | Therefore the primary focus has been to run tools such as `/usr/bin/cc` and 44 | `/usr/bin/make`, and many system calls that are not necessary for this 45 | toolchain have not been implemented yet including `ioctl(2)` and `fcntl(2)`. 46 | 47 | Subtleties in the emulation of some system calls is also incomplete. For 48 | example `open(2)` and `read(2)` require substantial additional work to 49 | synthesize MINIX-style directory content so they will work with MINIX’s 50 | userspace `readdir` implementation, which expects a directory to behave like 51 | an ordinary file with well-defined contents. It will not be possible to use 52 | MINIX’s `ls` tool until this is addressed. 53 | 54 | Finally, the implementation of some subsystems may be inadequate for their 55 | full needs. An example may be the process abstraction, which sits mostly atop 56 | the host system’s process abstraction: A `fork(2)` in the MINIX environment 57 | results in a `fork(2)` of MINIXCompat that attempts to preserve as much 58 | information as possible about the process tree. Subsequent `fork(2)` system 59 | calls within child processes, however, may not produce a coherent view of the 60 | process table across all members of the resulting process tree. One way to 61 | address this might be share the memory containing the process table across the 62 | entire tree, though this may also add substantially to the complexity of the 63 | process implementation. 64 | 65 | A lot of this results from the need to accommodate the use of a 16-bit `int` 66 | within MINIX for M68000: Since it was a fork of 16-bit x86 MINIX and based on 67 | UNIX V7, MINIX for M68000 used an LP32 model rather than an ILP32 model such 68 | as used by BSD, SunOS, and Linux. Since so many system values such as process 69 | IDs and lengths use `int` in MINIX, maps between the MINIX-side and host-side 70 | values must be maintained and consulted. 71 | 72 | 73 | ## Building MINIXCompat 74 | 75 | To build MINIXCompat you’ll need Xcode 16.1 on your Mac, and then to do the 76 | following: 77 | 78 | 1. Clone this repository and `cd` into the resulting directory. 79 | 2. `git submodule init` to initialize all submodules. 80 | 3. `git submodule update --recursive` to get the right versions of submodules. 81 | 4. Build the `Musashi` scheme. 82 | 5. If the build fails due to an error running `m68kmake` to generate the Musashi M68000 emulator core, run the build again *without cleaning first*. 83 | 84 | Unfortunately the build failure with `m68kmake` appears to be an Xcode 16 bug 85 | (FB15735449) when a Run Script build phase depends on the result of building 86 | another target: The build system tries to execute the Run Script phase that 87 | uses `m68kmake` before `m68kmake` has its code signed, which happens because 88 | signing is done in-place. 89 | 90 | 91 | ## Porting MINIXCompat 92 | 93 | MINIXCompat is written in ANSI/ISO C99 and attempts to stay close POSIX 94 | wherever possible rather than use other OS facilities, and should be fairly 95 | portable as a result. However, no Makefile is currently provided. 96 | 97 | For ease of inter-operation between the emulated 68K environment and the 98 | modern POSIX host, MINIXCompat uses enums with fixed underlying types, so 99 | you’ll need a compiler with support for that feature. 100 | 101 | As an example, the stock `gcc` included with NetBSD 10 doesn’t support enums 102 | with fixed underlying types in C, so building MINIXCompat on it will require 103 | using a more recent `gcc` or `clang` as the compiler. 104 | --------------------------------------------------------------------------------