I had a use-case to manipulate private, information-sensitive files in Racket, didn't find a way to set the umask so new directories/files have secure permissions (race-condition free (!)) so I made a small package to fill this need.
It's a little rough around the edges โ for example I don't believe this works on Mac. Any suggestions appreciated !
Without peaking, I'm assuming umask is a parameter, so with-umask is just a wrapper around parameterize? If so, I think it's completely normal for (umask x) to be void?.
Unfortunately it's not a parameter in this case because I wasn't sure how to configure the parameter to have a side-effect when set - it'll have to do a FFI call whenever the parameter is changed.
Instead with-umask wraps the body with a few umask FFI calls around it. Maybe there's a better way to do this?
I'd be interested to know whether this work-in-progress PR to add a #:permissions argument to make-temporary-file would work for your use-case: https://github.com/racket/racket/pull/4126
You also raise a good point that the docs don't specify how with-output-to-file, open-output-port, etc. handle #:permissions when #:exists is something like 'must-truncate
I gave it a go, but the tests appear to indicate my approach with parameters isn't quite working as expected, any hints?
I added dynamic-wind on the other hand, that appears to work in more cases. It does not work across threads however (though this didn't appear to work with parameters, possibly due to the FFI nature of the umask syscall).
It seems the guard is not applied when parameterize restores the old value, probably because that's not actually how parameterize works (something something thread cells something something ?).
I'm not sure there is a good workaround, esp. since, as you say, this is FFI-bound.
I think the intent for the guard is to prevent an unwanted value to be used as the parameter value, so it seems sensible that guard is only used once to check that the value is allowed.
Mutable values or side-effects don't seem to play nicely with that.
For opengl side-effect management I have used dynamic-wind (making setup/teardown calls).
(That opengl code is single threaded, so didn't run into any multi-threading issues)
But I am not entirely sure about the differences between dynamic wind and parameters, I get the first is based on continuations, while the latter is based on thread cells and parameterize (but indirectly also continuations?).
My technical understanding of how parameters / dynamic-wind are implemented is rusty, comparing the two could be interesting. Currently it seems to me like I would need to dig into both their implementations to understand fully in what ways they differ or are similar in their use.
I believe the intent of a parameter is to restrict dynamic-wind to a simple value binding, more or less. That is: a dynamic-wind can do any crazy thing it wants whenever control leaves or re-enters. A parameterize is a little more predictable; it just ensures that a particular binding is in place when the code in the dynamic extent of the body is being called.
In other words, if you need to perform an action on entry and a corresponding action on exit, I think you want dynamic-wind, and not parameterize.
However, I also think that @LiberalArtist 's proposal could be even better, and more well-behaved.
One difference between parameterize and dynamic-wind is that parameterize will also set the initial state for child (Racket-level) threads: in other words, thread will capture a parameterization (but probably not call the guard when restoring the initial value), but will not capture winders.
(In addition to raising exception, a common use for a parameter guard is to do some kind of coercion, e.g. converting any non-#false value to #true.)
But I think the FFI is not going to be enough to make a really robust and general interface to umask. (That doesn't mean it's bad to have a less-general solution that works for some use case!) In particular, IIUC umask is set at the level of the OS process, so uses of with-umask in different Racket places will interfere with each other.
Some possible approaches I see:
Don't rely on state: explicitly supply #:permissions as needed.
Have rktio manage the OS-level umask.
Make a parameter current-umask that works like current-directory: it is entirely independent of the OS-level state, but Racket's IO primitives respect it.