## To reproduce
1. Open a fresh Squeak trunk image 2. doIt:
```smalltalk FileStream stdin close. Smalltalk snapshot: true andQuit: true. ```
## Expected
Image saves and quits.
## Actual
Image fails to save, opens an error window with message `Failed to write image file (disk full?)`.
## Analysis
- `writeImageFileIO` calls `sqImageFileOpen`. - `sqImageFileOpen` in `platforms/unix/vm/sqImageFileAccess.h` never observably fails to its caller - it calls `exit` if `open(2)` fails - `sqImageFileOpen` can perfectly reasonably hand back file descriptor zero for the new image file! If `FileStream stdin` has been closed, it will in fact do so. - If it does this, `writeImageFileIO` gets confused, because if the result of `sqImageFileOpen` is equal to `null`, it considers this a failure.
This looks annoyingly Unix-specific, a result of punning raw fds as `sqImageFile` values. Other platforms use e.g. a `FILE*` instead of a raw fd, in which case even if fd 0 were to be opened under the covers, the corresponding `FILE*` would not be `null`.
## Appendix (not very useful): Full bug report from debugger
29 November 2021 4:32:57.802885 pm
VM: unix - Smalltalk Image: Squeak6.0alpha [latest update: #20751]
SecurityManager state: Restricted: false FileAccess: true SocketAccess: true Working Dir /tmp/squeaker-run-g38g7lcb Trusted Dir /tmp/squeaker-run-g38g7lcb/secure Untrusted Dir /tmp/squeaker-run-g38g7lcb/My Squeak
SmalltalkImage(Object)>>error: Receiver: Smalltalk Arguments and temporary variables: aString: 'Failed to write image file (disk full?)' Receiver's instance variables: globals: Smalltalk
SmalltalkImage>>snapshot:andQuit:withExitCode:embedded: Receiver: Smalltalk Arguments and temporary variables: save: true quit: true exitCode: nil embeddedFlag: false resuming: nil msg: '----QUIT----{29 November 2021 . 4:32:05 pm} squeak.image priorSource: 20637916...etc... Receiver's instance variables: globals: Smalltalk
SmalltalkImage>>snapshot:andQuit:embedded: Receiver: Smalltalk Arguments and temporary variables: save: true quit: true embeddedFlag: false Receiver's instance variables: globals: Smalltalk
SmalltalkImage>>snapshot:andQuit: Receiver: Smalltalk Arguments and temporary variables: save: true quit: true Receiver's instance variables: globals: Smalltalk
UndefinedObject>>DoIt Receiver: nil Arguments and temporary variables:
Receiver's instance variables: nil
Compiler>>evaluateCue:ifFail: Receiver: a Compiler Arguments and temporary variables: aCue: a CompilationCue failBlock: [closure] in Compiler>>evaluateCue:ifFail:logged: methodNode: DoIt ^ Smalltalk snapshot: true andQuit: true method: (UndefinedObject>>#DoIt "a CompiledMethod(1015626)") value: nil Receiver's instance variables: parser: a Parser cue: a CompilationCue
Compiler>>evaluateCue:ifFail:logged: Receiver: a Compiler Arguments and temporary variables: aCue: a CompilationCue failBlock: [closure] in [] in SmalltalkEditor(TextEditor)>>evaluateSelectionAndDo:...etc... logFlag: true value: nil Receiver's instance variables: parser: a Parser cue: a CompilationCue
Compiler>>evaluate:in:to:environment:notifying:ifFail:logged: Receiver: a Compiler Arguments and temporary variables: textOrStream: a ReadStream aContext: nil receiver: nil anEnvironment: Smalltalk aRequestor: a SmalltalkEditor failBlock: [closure] in [] in SmalltalkEditor(TextEditor)>>evaluateSelectionAndDo:...etc... logFlag: true Receiver's instance variables: parser: a Parser cue: a CompilationCue
[] in SmalltalkEditor(TextEditor)>>evaluateSelectionAndDo: Receiver: a SmalltalkEditor Arguments and temporary variables: aBlock: [closure] in SmalltalkEditor(TextEditor)>>evaluateSelection result: nil rcvr: nil ctxt: nil Receiver's instance variables: morph: a TextMorphForEditView(2236073) model: a Workspace paragraph: a NewParagraph markBlock: a CharacterBlock with index 1 and character $F and rectangle 3@0 cor...etc... pointBlock: a CharacterBlock with index 62 and rectangle 235@16 corner: 235@32 ...etc... beginTypeInIndex: nil emphasisHere: #() lastParenLocation: nil otherInterval: (49 to: 56) oldInterval: nil typeAhead: a WriteStream history: a TextEditorCommandHistory
FullBlockClosure(BlockClosure)>>on:do: Receiver: [closure] in SmalltalkEditor(TextEditor)>>evaluateSelectionAndDo: Arguments and temporary variables: exceptionOrExceptionSet: OutOfScopeNotification handlerAction: [closure] in SmalltalkEditor(TextEditor)>>evaluateSelectionAndDo:...etc... handlerActive: true handlerRearmed: false Receiver's instance variables: outerContext: SmalltalkEditor(TextEditor)>>evaluateSelectionAndDo: startpcOrMethod: ([] in TextEditor>>#evaluateSelectionAndDo: "a CompiledBlock(1...etc... numArgs: 0 receiver: a SmalltalkEditor
SmalltalkEditor(TextEditor)>>evaluateSelectionAndDo: Receiver: a SmalltalkEditor Arguments and temporary variables: aBlock: [closure] in SmalltalkEditor(TextEditor)>>evaluateSelection result: nil rcvr: nil ctxt: nil Receiver's instance variables: morph: a TextMorphForEditView(2236073) model: a Workspace paragraph: a NewParagraph markBlock: a CharacterBlock with index 1 and character $F and rectangle 3@0 cor...etc... pointBlock: a CharacterBlock with index 62 and rectangle 235@16 corner: 235@32 ...etc... beginTypeInIndex: nil emphasisHere: #() lastParenLocation: nil otherInterval: (49 to: 56) oldInterval: nil typeAhead: a WriteStream history: a TextEditorCommandHistory
SmalltalkEditor(TextEditor)>>evaluateSelection Receiver: a SmalltalkEditor Arguments and temporary variables:
Receiver's instance variables: morph: a TextMorphForEditView(2236073) model: a Workspace paragraph: a NewParagraph markBlock: a CharacterBlock with index 1 and character $F and rectangle 3@0 cor...etc... pointBlock: a CharacterBlock with index 62 and rectangle 235@16 corner: 235@32 ...etc... beginTypeInIndex: nil emphasisHere: #() lastParenLocation: nil otherInterval: (49 to: 56) oldInterval: nil typeAhead: a WriteStream history: a TextEditorCommandHistory
SmalltalkEditor(TextEditor)>>doIt Receiver: a SmalltalkEditor Arguments and temporary variables:
Receiver's instance variables: morph: a TextMorphForEditView(2236073) model: a Workspace paragraph: a NewParagraph markBlock: a CharacterBlock with index 1 and character $F and rectangle 3@0 cor...etc... pointBlock: a CharacterBlock with index 62 and rectangle 235@16 corner: 235@32 ...etc... beginTypeInIndex: nil emphasisHere: #() lastParenLocation: nil otherInterval: (49 to: 56) oldInterval: nil typeAhead: a WriteStream history: a TextEditorCommandHistory
SmalltalkEditor(TextEditor)>>doIt: Receiver: a SmalltalkEditor Arguments and temporary variables: aKeyboardEvent: [140@92 keystroke '<Opt-Cmd-d>' (100) 12807] Receiver's instance variables: morph: a TextMorphForEditView(2236073) model: a Workspace paragraph: a NewParagraph markBlock: a CharacterBlock with index 1 and character $F and rectangle 3@0 cor...etc... pointBlock: a CharacterBlock with index 62 and rectangle 235@16 corner: 235@32 ...etc... beginTypeInIndex: nil emphasisHere: #() lastParenLocation: nil otherInterval: (49 to: 56) oldInterval: nil typeAhead: a WriteStream history: a TextEditorCommandHistory
SmalltalkEditor(TextEditor)>>dispatchOnKeyboardEvent: Receiver: a SmalltalkEditor Arguments and temporary variables: aKeyboardEvent: [140@92 keystroke '<Opt-Cmd-d>' (100) 12807] honorCommandKeys: true typedChar: $d Receiver's instance variables: morph: a TextMorphForEditView(2236073) model: a Workspace paragraph: a NewParagraph markBlock: a CharacterBlock with index 1 and character $F and rectangle 3@0 cor...etc... pointBlock: a CharacterBlock with index 62 and rectangle 235@16 corner: 235@32 ...etc... beginTypeInIndex: nil emphasisHere: #() lastParenLocation: nil otherInterval: (49 to: 56) oldInterval: nil typeAhead: a WriteStream history: a TextEditorCommandHistory
SmalltalkEditor(TextEditor)>>keyStroke: Receiver: a SmalltalkEditor Arguments and temporary variables: anEvent: [140@92 keystroke '<Opt-Cmd-d>' (100) 12807] Receiver's instance variables: morph: a TextMorphForEditView(2236073) model: a Workspace paragraph: a NewParagraph markBlock: a CharacterBlock with index 1 and character $F and rectangle 3@0 cor...etc... pointBlock: a CharacterBlock with index 62 and rectangle 235@16 corner: 235@32 ...etc... beginTypeInIndex: nil emphasisHere: #() lastParenLocation: nil otherInterval: (49 to: 56) oldInterval: nil typeAhead: a WriteStream history: a TextEditorCommandHistory
[] in [] in TextMorphForEditView(TextMorph)>>keyStroke: Receiver: a TextMorphForEditView(2236073) Arguments and temporary variables: evt: [140@92 keystroke '<Opt-Cmd-d>' (100) 12807] action: nil Receiver's instance variables: bounds: 0@0 corner: 435@32 owner: a TransformMorph(3867971) submorphs: #() fullBounds: 0@0 corner: 435@32 color: Color black extension: a MorphExtension (3908410) [other: (unfocusedSelectionColor -> (Col...etc... borderWidth: 0 borderColor: Color black textStyle: a TextStyle Bitmap DejaVu Sans 9 text: a Text for 'FileStream stdin close Smalltalk snapshot: true andQuit: true...etc... wrapFlag: true paragraph: a NewParagraph editor: a SmalltalkEditor container: nil predecessor: nil successor: nil backgroundColor: nil margins: 3@0 corner: 0@0 readOnly: false autoFit: true editView: a PluggableTextMorphPlus(2181400) acceptOnCR: false
TextMorphForEditView(TextMorph)>>handleInteraction:fromEvent: Receiver: a TextMorphForEditView(2236073) Arguments and temporary variables: interactionBlock: [closure] in [] in TextMorphForEditView(TextMorph)>>keyStroke:...etc... evt: [140@92 keystroke '<Opt-Cmd-d>' (100) 12807] oldEditor: a SmalltalkEditor oldParagraph: a NewParagraph oldText: a Text for 'FileStream stdin close Smalltalk save: true andQuit: true'...etc... oldSelection: {205@16 corner: 210@32} Receiver's instance variables: bounds: 0@0 corner: 435@32 owner: a TransformMorph(3867971) submorphs: #() fullBounds: 0@0 corner: 435@32 color: Color black extension: a MorphExtension (3908410) [other: (unfocusedSelectionColor -> (Col...etc... borderWidth: 0 borderColor: Color black textStyle: a TextStyle Bitmap DejaVu Sans 9 text: a Text for 'FileStream stdin close Smalltalk snapshot: true andQuit: true...etc... wrapFlag: true paragraph: a NewParagraph editor: a SmalltalkEditor container: nil predecessor: nil successor: nil backgroundColor: nil margins: 3@0 corner: 0@0 readOnly: false autoFit: true editView: a PluggableTextMorphPlus(2181400) acceptOnCR: false
TextMorphForEditView>>handleInteraction:fromEvent: Receiver: a TextMorphForEditView(2236073) Arguments and temporary variables: interActionBlock: [closure] in [] in TextMorphForEditView(TextMorph)>>keyStroke:...etc... evt: [140@92 keystroke '<Opt-Cmd-d>' (100) 12807] Receiver's instance variables: bounds: 0@0 corner: 435@32 owner: a TransformMorph(3867971) submorphs: #() fullBounds: 0@0 corner: 435@32 color: Color black extension: a MorphExtension (3908410) [other: (unfocusedSelectionColor -> (Col...etc... borderWidth: 0 borderColor: Color black textStyle: a TextStyle Bitmap DejaVu Sans 9 text: a Text for 'FileStream stdin close Smalltalk snapshot: true andQuit: true...etc... wrapFlag: true paragraph: a NewParagraph editor: a SmalltalkEditor container: nil predecessor: nil successor: nil backgroundColor: nil margins: 3@0 corner: 0@0 readOnly: false autoFit: true editView: a PluggableTextMorphPlus(2181400) acceptOnCR: false
--- The full stack --- SmalltalkImage(Object)>>error: SmalltalkImage>>snapshot:andQuit:withExitCode:embedded: SmalltalkImage>>snapshot:andQuit:embedded: SmalltalkImage>>snapshot:andQuit: UndefinedObject>>DoIt Compiler>>evaluateCue:ifFail: Compiler>>evaluateCue:ifFail:logged: Compiler>>evaluate:in:to:environment:notifying:ifFail:logged: [] in SmalltalkEditor(TextEditor)>>evaluateSelectionAndDo: FullBlockClosure(BlockClosure)>>on:do: SmalltalkEditor(TextEditor)>>evaluateSelectionAndDo: SmalltalkEditor(TextEditor)>>evaluateSelection SmalltalkEditor(TextEditor)>>doIt SmalltalkEditor(TextEditor)>>doIt: SmalltalkEditor(TextEditor)>>dispatchOnKeyboardEvent: SmalltalkEditor(TextEditor)>>keyStroke: [] in [] in TextMorphForEditView(TextMorph)>>keyStroke: TextMorphForEditView(TextMorph)>>handleInteraction:fromEvent: TextMorphForEditView>>handleInteraction:fromEvent: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - [] in TextMorphForEditView(TextMorph)>>keyStroke: StandardToolSet class>>codeCompletionAround:textMorph:keyStroke: ToolSet class>>codeCompletionAround:textMorph:keyStroke: TextMorphForEditView(TextMorph)>>keyStroke: TextMorphForEditView>>keyStroke: TextMorphForEditView(Morph)>>handleKeystroke: TextMorphForEditView(TextMorph)>>handleKeystroke: KeyboardEvent>>sentTo: TextMorphForEditView(Morph)>>handleEvent: TextMorphForEditView(Morph)>>handleFocusEvent: MorphicEventDispatcher>>doHandlingForFocusEvent:with: MorphicEventDispatcher>>dispatchFocusEvent:with: TextMorphForEditView(Morph)>>processFocusEvent:using: TextMorphForEditView(Morph)>>processFocusEvent: [] in [] in [] in HandMorph>>sendFocusEvent:to:clear: [] in ActiveEventVariable class(DynamicVariable class)>>value:during: FullBlockClosure(BlockClosure)>>ensure: ActiveEventVariable class(DynamicVariable class)>>value:during: [] in ActiveEventVariable class>>value:during: FullBlockClosure(BlockClosure)>>ensure: ActiveEventVariable class>>value:during: KeyboardEvent(MorphicEvent)>>becomeActiveDuring: [] in [] in HandMorph>>sendFocusEvent:to:clear: [] in ActiveHandVariable class(DynamicVariable class)>>value:during: FullBlockClosure(BlockClosure)>>ensure: ActiveHandVariable class(DynamicVariable class)>>value:during: [] in ActiveHandVariable class>>value:during: FullBlockClosure(BlockClosure)>>ensure: ActiveHandVariable class>>value:during: HandMorph>>becomeActiveDuring: [] in HandMorph>>sendFocusEvent:to:clear: [] in ActiveWorldVariable class(DynamicVariable class)>>value:during: FullBlockClosure(BlockClosure)>>ensure: ActiveWorldVariable class(DynamicVariable class)>>value:during: [] in ActiveWorldVariable class>>value:during: FullBlockClosure(BlockClosure)>>ensure: ActiveWorldVariable class>>value:during: PasteUpMorph>>becomeActiveDuring: HandMorph>>sendFocusEvent:to:clear: HandMorph>>sendEvent:focus:clear: HandMorph>>sendKeyboardEvent: HandMorph>>handleEvent: -- and more not shown --