Here's another interesting thing: - getaddrinfo is defined as threadsafe - getaddrinfo (in glibc) calls getenv - getaddrinfo is practically a hard requirement if you want to use IPv6 (if you don't want to implement its logic fully yourself) Have fun with that.
Thank you, good case study why you can't avoid or wrap or be smart and careful with getenv. The only way is that setenv shouldn't exist. But it also can't not exist since your dependencies call it. OK if steam wanted to be sneaky they could just ld-preload a setenv which always allocates new memory. And i get why it's not the default behaviour, since shell interpreters call setenv a lot and long running shell scripts exist.
The correct answer is that getaddrinfo should be fixed or redefined as not thread safe. If not fixed, a thread safe implementation should be used either through local implementation or using another library (this is open source, after all). I'm not sure the logic of why getaddrinfo calls getenv but it might be worth looking into that too.
@@chaos.corner getaddrinfo must inspect environment variable LOCALDOMAIN for example. Making it non-threadsafe is a horrible idea, you're effectively plain forbidding MT networked software, which is not something you can just do especially not all these decads later. Unfortunately getenv isn't the only legal way to retrieve an environment variable, you also have an external symbol that is always populated by the executable loader as per POSIX. None of the ways are threadsafe if setenv or putenv is ever called, and are all threadsafe if env is effectively constant. So the real problem is that setenv and putenv are garbage that shouldn't exist in MT software. Just compose an explicit environment for child processes with execve and purge all setenv using libraries from MT software, or fix them to not do that.
getaddrinfo is a terrible 40 year old solution. We really need some more modern and OS wide service DNS caching solution. Lets put it into systemd. But the getenv is no problem at all as modern programming had banned the use of setenv long ago. You never modify, just copy and modify when you start a new process. It's unfixable, just like "select" call.
Most certainly, the only way to fix the bug is to return a copy of the string instead, but that would pass the responsibility for dealocating the copy to the caler
Opting to trigger a memory leak rather than a crash in this scenario seems pretty enlightened; while environment variables can and do change, it's pretty rare that they're changing frequently enough with large enough values that they'd leak a problematic amount of memory in any realistic scenario. Certainly not great, but better than the alternative if you're working with a broken API like this.
@@SianaGearznope, the environment is part of a process's address space created by exec(2), shell scripts can alter the environment for new child processes but it's not a substitute for shared memory.
It's NOT a broken API, the environment is part of the address space of a new process. If you are stupid enough to attempt to fake shared memory between threads using environment functions you are simply breaking the C memory model. There are no guarantees about what the program will do, memory is copied into different caches and registers to actually be used and application programmers have to know about it.
@@RobBCactive what does shared memory have to do with anything? Shell is single threaded. The child process runs off a fresh cow copy. I'm saying deliberately leaking memory in setenv implementation may cause shell scripts to aggressively leak memory unless shell stops using setenv and moves to execve.
@@SianaGearz because threads share memory, doh. Why would glibc have to leak memory in the main process thread? It's only a problem seen by incorrect programs I don't agree a band aid is required, the Valve dev quotes are from the point of view of people doing hacks rather than getting client programs fixed. People are allowed to write bad code and incorrect programs, they can be ignorant too.
The land of C where all memory is allocated and deallocated by hand, and every single bug is either pointers, pointerarithmetic or straight up WTF territory. :D I love C. It's whacky, bonkers, but yet a dozen times more stable than JavaScript could ever dream. XD
@@framegrace1 is it a libc bug or linux bug? in first case you dont need to use libc, and is easily rewritable unless libc is plain syscalls in setenv/getenv implementations
@@mtarek2005 they should really reimplement setenv/getenv from the ABI spec of how the kernel/program loader puts environment variables right above the top of the stack when the process gets fork()'d but this time making it thread safe using some kind of synchronization primitives around them.
@@FrankHarwald the problem is that "the current environment" is a libc concept. You could hypothetically not use libc and just copy the envp before main, but then your copy of the environment wouldn't be synchronized with what other libraries / programming languages see when doing interop. Which would make your "current environment" basically useless.
@@chevychavanne4433 Some people (the same people that are arguing that memory corruption bugs are "a skill issue") aren't capable of that level of reasoning
Yes that works and indeed every instruction is self documenting and all but if you call someone else's code you are still hosed. You need to do it in the "the whole machine is mine" mode and handle everything your self. Doing disk I/O is a bit more tricky than a lot of other things.
@BrodieRobertson fixed* might be a better word, though it probably needs a little "going back to the whiteboard" action at the very least by this point
In most cases, you actually don't want to modify your own environment. The places I have seen that change the environment actually want to set a different environment for a child process. POSIX and C don't have functions to do this, so you have to do it yourself, but this way the process environment remains unchanged. Of course, sometimes you do need to adjust TZ or PATH or something, so this isn't universal.
Leaking memory and returning strings that are constant for the rest of program runtime is a completely viable approach to fully safeguard this function family, and given the spec, the only one that is actually robust. Since there isn't just about any software that is long running and updates env all the time. OR IS THERE. Indeed there is, shell scripts, particularly long running ones aren't uncommon. To be actually good, shell interpreters should not use setenv, and should just maintain variables on heap, composing them into new process's environment on execution. But they'll probably be like "why should we be smart about this, we aren't multithreaded!"
Fork system call was a "virtue of necessity" back in the '70s in PDP days. Just spawn new process and explicitly enumerate what should be inherited into it.
here I am being a programmer myself, assuming other programmers are doing things properly. I only access environment variables in the main thread, when the program is starting. process or copy the values and store in a place I know for sure it will be safe. never assume something is thread safe, it probably is not.
@@moarjank race conditions can be extremely difficult to find. it's not as simple as testing. program crashes, ok there must be a bug, let me look deeper into this... it's working fine now, I cannot reproduce the bug. what can I do now. as for docs, it's rare I find them to be actually useful. they don't say much beyond the obvious, this function takes these arguments and one line to describe what the function does.
you should check most of your standard library calls also. anything marked "MT-Safe env" or "MT-Safe locale" is calling getenv() or reading the locale and allowed in a threaded context. other libraries certainly call this stuff. obvious stuff like gettext() definitely accesses the environment to see the fuller locale. strftime() might be a little less obvious and more required, you can't by your standards call that in anything but the main thread, which you might really want to do at times. that same thing goes for most time functions. lastly the gethostbyname/getaddrinfo type network functions also use getenv, so you'd have to also prefetch all the IP addresses network connections you're making on the main thread before splitting. it's a little hard to adhere to this.
Why didn't they just wrap them with a lock? Oh, I guess because then direct calls would be safe but indirect calls via other library functions would still be unsafe. Still, surely libc could add locking inside setenv/getenv to fix it pretty easily.
@@nicholasvinenLocking would maybe help a bit, but not once you get the env var’s value pointer and it gets invalidated by setenv, since you don’t manage its lifetime after the getenv call. So setenv can either free the old pointer and cause crash or leak it like Mac did. And if you iterate over the whole environment array, then even more so problems are bound to happen… The only way to fix it is to make it leak some memory one way or another and then have a fixed versions of the API that’d require you to free a copy of env vars instead of getting the raw original pointer, at the cost of some performance overhead…
The problem is how ubiquitous those two are, and how third party libraries rarely flag themselves as "thread unsafe" because they're using those two calls. And of course, many libraries might only be using them indirectly, and their devs might not even be aware that they're pulling in a library that uses setenv/getenv and making themselves thread unsafe. Also, 'getaddrinfo()', a glibc function that is basically unavoidable if you want to do IPv6, uses those two calls and is therefore thread unsafe. The other thread unsafe POSIX calls are generally easier to avoid.
Well, maybe POSIX needs to be change to work well with modern tech and expectations. As Brodie said, expecting people to write single threaded code simply because system spec is stuck in nineties is stupid. Likewise, people cannot read all the source code of the modules they import.
@@elzabethtatcher9570 Dude, it’s completely trivial to create a new module that performs your getenv/setenv operations and wraps a mutex around them. This is 200% skill issue. Also, no, integrating that mutex in getenv and setenv themselves doesn’t solve the problem since that enables TOCTOU conditions.
@@elzabethtatcher9570 The programs don't actually have to be single threaded. It's just that multi-threaded programs have to do their own locking, which doesn't seem like an unreasonable requirement to me. There are other common patterns as well, such as delegating all OS interactions to a single main thread. But there has been an effort to extend POSIX for multi-threading, and there are thread safe versions of at least a few of the calls these days.
I know the C devs kinda poopoo on Rusts embedding of semantics into the type system, but this is an example where it could be used to communicate this hazard. If std::env::var was NOT marked Sync then we'd know it's not safe across threads. Which would then propagate to any libraries that used them and didn't account for this
Wow, this is... a real crap API. I can understand the original getenv, but adding a setenv without that's supposed to interact with it is just a disaster. They should have made a totally separate, reentrant API for getting/setting stuff in envp.
setenv() being non thread safe is one of my pet peeve. Mixing fork() and threads is another one. It’s too easy to hit a threading lock in the CoW address space.
The easy way to solve this would be to copy the environment space each time a new thread is spun up. The down side would be that different tasks could then disagree about the value of a variable. A far more difficult way to solve it is to copy as needed only but then you have to keep multiple cores in sync.
I’ve been having an issue where the library page will just freeze indefinitely after a minute or two. Closing the window and reopening it works for a bit. Notably I only have to close the GUI, I can leave steam open in the systray. Not sure what it’s about, maybe this is related
What's really confusing to me is, why we don't use files for this instead. Even magic kernel files (like /proc) are preferable, since going through syscalls means the problems can be much more easily solved. Obviously there were reasons for them at some point, but it shouldn't be to hard to rewird getenv and setenv to a read/write operation on sth like /proc/ID/env instead, shouldn't it? (Obviously, the problem is partly in C / posix standard, but we still could have a better getenv2 / setenv2 and hope everyone switches over at least..)
Files generally need to be closed when you're done, which isn't really any different from just allocating a temporary buffer and then requiring the user to free it later. Not closing the file when you're done is basically the same as just leaking the temporary buffer. Yes, it'll be cleaned up when the program exits, but that could be arbitrarily far away. C++ and Rust programmers wouldn't really need to worry about this since they can automatically clean up resources when they go out of scope, but in any other language, this would require garbage collection, which only works for in-memory buffers but not files.
There might be several reasons for this. Keep in mind that the POSIX standard is from a time where memory was extremely slow. So reading from actual files was a no go back then because the environment variables are used at runtime quite often, which would SIGNIFICANTLY slow down programs. Now, obviously with virtual filesystems like /proc this is no longer an issue, but POSIX is POSIX. The POSIX standard also doesn't really assume that things are saved to disk or that VFS even exist (keep in mind that Linux is just one of the POSIX compliant systems, not every POSIX system uses a VFS, take MINIX for example). Using a /proc/id/env thing is not a specifically great idea imo, because that'd create problems when spawning threads and processes from the application itself (since the child would now need the parent-id to access its proc directory + environment, which, however, might have already been offed making the process an orphan, which will probably trigger a cascade of exception handling that needs to occur that would be costly and REALLY annoying). I'm all for some form of globally accessible file in the /proc system though, but to make this compliant with POSIX the standard would be subject to change which can, as I said, mean that some POSIX systems will have heavy performance loss. This is an extremely complex subject, but it should have been addressed decades ago.
@@Finkelfunk I agree that integrating it with POSIX is probably the biggest issue. However, I don't really see that compatibility between Linux and BSD on the lower level is necessary if we could have a new getenv2 or setenv2 call that would be implemented differently on both OSs and just be threadsafe by definition, and Linux could just have that before it gets into POSIX. Fixing the old getenv might not even be possible, but maybe there is a way. For the proc files, I don't even think you need to have the mother process id, you could just have threads or locally spawned processes have the same env file, that is mapped correctly under the open/close and read/write syscalls, so that two threads cannot have it open at the same time, or so that they are synced regardless, or something along those lines. After all, since theyre in VFS, they could just be memory mapped and the kernel just needs to make sure that they don't override each other. We could even have a kind of "env mutex lock" syscall, that would specifically lock this file for read/write operation for any other thread, or something like that. Idk if this is possible in the kernel, but putting it there would probably the easiest since it already has to do the scheduling and syscalls for the os threads to begin with.
@@svaira The issue there is now you have two types of setenv that ultimately both need to set global variables for the user. How would you make that threadsafe? How do you sync those? If an application decides to call "legacy" setenv, then the new getenv2 still needs to function, but how will it guarantee interoperability, if setenv doesn't provide multithreading-security? Just slapping a 2 at the end and calling it a day isn't the way to go about fixing this issue, especially considering they still need to share the same resource under the hood. Yes and no, what if the parent process spawns two new children who each modify the environment? How will you communicate that the environment has changed between them? There's dozens of edge cases you need to think about, remember that we are talking about millions of active users that use this API. This would be an example of an API that is incredibly easy to use wrong again. If you let the Kernel clean up environment variables and synchronize them across users, then the Kernel breaches user space because environment variables are not set in Kernel space but are user specific. So now the Kernel manages a file that needs to be synced across the user space, which goes against the entire philosophy of Linux. Adding a syscall to lock a user space file just adds insult to injury in this regard. This also has nothing to do with scheduling, that is a direct interaction with the hardware and that is why that is a kernel job. Setting variables for local programs to run is entirely a user-land task.
@@Finkelfunk Easy. You don't. New functions write and read their own set of variables. Subscribing to changes is trivial, not sure what's the problem here?... Surely C has patters for this, whether it's callbacks or something else.
I remember steam used to(maybe still does idk) crash after a very specific amount of time of having the screenshot manager open(with thousands upon thousands of screenshots)
on a related but different note, i noticed that if I add certain (graphics related) environment variables in /etc/environment in the wrong order, gnome session manager won't start. I never realized that could be relevant and i still don't understand why.
I guess the workaround way i would handle this would be create a threat who's sole job is to manage environment variables for the application. This doesn't fix libraries doing this, but does ensure application code don't add to it.
Correct! Yet MT code needs to be sure its libraries are designed to be thread safe. Fundamentally you cannot share memory between threads without mechanisms to ensure coherency or undefined behaviour ie an incorrect program will result. It seems like using multiple processes that can operate independently and be simultaneous is forgotten by many. Changing environment as a replacement for a shared data structure between threads is simply brain-dead. You'll simply serialise the algorithm in any correct program anyway.
11:27 - x11, dbus, etc using getenv, a known race-condition read of publicly writable null terminated char**array? how come nobody uses this for more *exotic* manipulation of state?
Steam usually works perfectly ..it now throws 2 errors on start up though related to Boxtron, which im confused about, other than that it's been flawless.
There is no such thing as something immutable in classic early C, it predates the (leaky) attempts to introduce that. Setenv has indeed always been a mistake and it's something that is very apparent retroactively. As in there really isn't a good reason for it other than it just happened that way and was cast into standard by the time someone actually thought about it.
When launching a game that needs env vars set, just spit out a new shell script, chmod it to +x, then run that to launch the game. Bonus points for creating it on tmpfs somewhere. Or /tmp, whatever. Use the same shell script path when you launch every game, you only need one at a time. No need to mess up your whole Steam client's memory address space just for some tiny env vars. If you need global variables in your code (boo!) just use a mutex and use globals, who cares. Or use a config file which you parse every time, I mean Steam is slow enough already, who cares. Don't, I repeat, don't mess up your memory. I mean, once they've figured out that thou shalt not set env vars, then simply don't, let bash handle it.
Shell scripts are jank. You don't do that, eww. You use an exec from the e group. They said they now do that, so apparently they need to use setenv for something else than launching games, though I do wonder what they need it for.
@@GrzesiekJedenastka Sure, there's probably a recommended proper way, but a shell script is a quick and dirty solution that will work and you can do it right now without changing much code... I think.
I still don't fully understand the root cause. In a hypothetical scenario, if I made a component to my application that I'll call "env manager" that runs as a thread/forked process which is responsible for all setenv and getenv calls and is being interacted with through IPC like with zeromq for example, would the issue still occur? Is the environment only allocated for the first process? Or is it more that any one of the threads can "take ownership of" the environment and just once another thread comes along and tries to access it causes segfaults?
Win32 is much better designed than POSIX because it actually had the foresight to avoid some mistakes. It is overall more consistent and pretty reliable. The only thing that it suffers from is that it is bloated because many of the things that Linux relegates to the userland are integrated in Win32 and also has a lot of cruft. But then, so does Linux, but people are trying to pretend they're not actually cosplaying as a 1970s mainframe once you track down the core heritage and POSIX.
I've noticed Steam not running (probably crashed) every now and then on my Steam Deck when using the Desktop mode. Curiously I don't remember ever seeing this on my LTS Ubuntu.
I had a lot of crashes on steam since I upgraded to Fedora 41 (Fedora 41 is acting weirdly here, not sure why, but my experience is worse in comparison to 40). Then at some day I started using Steam Beta, which fixed the issue.
Set and get them at the start of code, never update them. Think thats been a rule for 20 years? How could any library fuck this up? I suppose its a problem with being allowed to do so.
Probably because it's convinient? As you can literally use said env vars as global variable, even across files Devs tend to choose convinience over stability lol
Yes. steamwebhelper is by an unfathomable margin the least stable program I’ve ever used. Three quarters of my coredumpctl list is steamwebhelper dying on SIGILL. That’s supposed to be like the most unlikely signal a well written program would raise…
You can't. This is an API problem, not an implementation problem. Forget about setenv/getenv. In POSIX programs can just straight up mutate the char **environ pointer without locking anything. Good luck with implementing a thread safe getenv when the contents of the environment can just change while you are iterating over them.
Rust Edit: 5:25 Okay, watching further the video I see even Rust is affected by it because it uses glibc in the backend. However using musl should at least avoid this I assume, but I'm not sure at the moment.
@@NJ-wb1cz I edited the comment already, because at the time of making the initial comment about Rust I didn't know it was using glibc for this function in the back. Rust cannot run unsafe code safely.
rust kind of fixes this, if you only ever use rust code you can freely and safely set environment stuff because it's behind a mutex lock, but the second you call any c code that calls any of the env functions you're fucked as well because (for obvious reasons) the c code doesn't lock the mutex of the rust standard library. you kind of have the same issue with go: all of their env access is also mutex protected, until you use cgo and interop with c code. but it's worse in rust because, not only because a lot more people depend on c libraries than in go, but the rust std also calls libc functions that may use the getenv function, for example host resolving is done via libc getaddrinfo, which calls getenv on glibc (but it doesn't on musl).
@@m4rch3n1ng As far as I understand this is not fixable in Rust, when using glibc to set/get environment variables. So it doesn't fix it at all. Or is it possible to set environment variables in Rust without using glibc?
musl doesn't fix this either. This is an API problem, not an implementation problem. Forget about setenv/getenv. In POSIX you can just straight up mutate the char **environ pointer without locking anything. Good luck with implementing a thread safe getenv when the contents of the environment can just change while you are iterating over them.
luckily i never had much crashes to begin with but i have noticed actually over the past few month that the number of times i see an error screen in the client has significantly gone down.
THis is not a bug, is a POSIX implementation incompatibility for Linux. Few linux apps will do that because they will use a workarounf, and has not been fixed exactly for the same reasom, everyone knows and there is an easy workaround and libc developers have things more urgent to do. Valve is seeing this because they are running code from other OS's where the implementation doesn't break.
ENVIRONMENT variable that means a global variable effectively. even a name suggests that. surprise surprise: global variables are bad for multithreaded code
@@tablettablete186 yes, there are DRM free games on Steam that will even run without Steam, but the big reason for why Steam is proprietary is because it's a kind of DRM. And I'm not aware of any open source DRMs. Plus I doubt Valve would want to make Steam itself FOSS or open source in some other way
One massively glaring error here is that setenv isn't in the C standard library. You're not meant to set environment variables, they should already be set before your program runs. The fact that there are shit programmers who think setting environment variables at runtime is in any way a good idea is sad. It's kind of stupid how many people in the comments are blaming C for non-standard bullshit that people do. It's a shame that people refuse to educate themselves and TH-cam keeps censoring so many of my posts so clearly no one will learn from me, but it's even worse when the only voices allowed to speak do such a sloppy job with little to no research and get basic facts wrong. It's tantamount to spreading misinformation which is something they claim to hate, but in reality they perpetuate that fuckery all on their own.
1) setenv is in POSIX, it's technically not in the ISO C standard, but this is an insane position to take, because by that logic, C also doesn't have file descriptors or sockets or a bunch of other stuff. Unless you are doing Windows-only development or are using C for ultra low-level embedded programming, POSIX is a "part" of C for all intents and purposes. 2) Modifying the environment of the current program without starting a whole new process *is* actually a useful feature. The problem is that the current API is bad, not that the feature itself is fundamentally broken.
@@ruroruro Not only is that a stupid point of view to take, but it speaks to how fundamentally bad the industry has gotten that you actually believe what you're saying is in anyway an intelligent thought. First of all, you admit the caveats of such a point of view, which means you're already arguing in bad faith, and from a rather arrogant stance as well because there are other platforms, and they do count. Second of all, if you need to send yourself a message, then you're doing it horribly wrong by using setenv, both at all, and incorrectly for how it functions. Please quit now and go work as a fry cook.
@@ruroruro And since TH-cam is playing shenanigans, I'll reiterate that you shouldn't be programming if you're going to take the stance that you are. Go become a fry cook instead of littering the world with shit code from you who is a shit programmer.
@@ruroruro Fuck TH-cam for deleting perfectly innocuous posts. Stupid pieces of shit are the kind of idiot programmers that bring us dumbfucks like you who insist on doing things the wrong way and will never be corrected in your idiot thinking because you won't ever see the correction. Go ahead, continue making the world a worse place by writing code that will guaranteed fail. Don't bother learning the correct method of passing messages. Keep thinking that everything POSIX is correct. Although, if you insist on doing these things, the world would be better off if you worked for McDonald's instead.
I'm suuuuuuure TH-cam is censoring you, yup, you're right... Brodie comes down with a comically large spoon and scoops out your comments specifically, you're being persecuted for your truly based knowledge. Be real now
This video should have considered the C memory model rather than blindly repeating what Valve application programmers are saying. The hardware lies to you guys! You are NOT reading/writing to memory but copies held in caches and registers, so fundamentally accessing shared memory carelessly leads to undefined behaviour, meaning an incorrect program. The examples are demonstrations of application programmer incompetence & ignorance, which Valve are working around.
@@dynfoxx rubbish!! It's purely a MT issue. You're saying the equivalent that changing any value is unsafe and that includes the parameters of functions in purist languages like Haskell.
@@RobBCactive no, the API is broken, because to use it safely, you have to only interact with the environment while holding a mutex, but the standard doesn't include this mutex. This means that unless your program only runs code that *you* have written (no libraries, no interop between languages, no CFFI) then it is impossible to do so in a thread-safe manner. Because there is no way to make every piece of code to agree on a single locking mechanism to use for accessing the environment. You can tell that this is a practically impossible task, because even glibc itself sometimes gets it wrong internally.
@ruroruro it's not meant to be thread safe, Environment is inherently about processes and passing to child processes. You're just advocating that inefficient erroneous poorly designed code should be forced on every program. The environment is passed via a char **, like argv so the solution you propose fails. Get/setenv are merely optional wrappers used for convenience and not a substitute for knowledge.
@@RobBCactive I am perfectly aware that the POSIX API directly exposes char **environ. When I say that the API is broken, I am including **environ in that. A thread-safe version of this API would either not expose **environ directly at all (instead requiring you to call a function that would take a mutex, copy the current state of the environment and then return that copy) or the API would expose an "official" mutex that every MT-safe program would be required to hold while interacting with the **environ pointer. P.S. please tell me, where exactly in my comment am I advocating for forcing "inefficient erroneous poorly designed code" on every program?
"Knock Knock"
"Race Condition"
"Who's there?"
programmers are also human is such a great channel
At least there can't be a race condition in "Knock Knock"
@@NJ-wb1cz At the end, some instructions are ultimately atomic. Actually accurate meme
this made me chuckle
Here's another interesting thing:
- getaddrinfo is defined as threadsafe
- getaddrinfo (in glibc) calls getenv
- getaddrinfo is practically a hard requirement if you want to use IPv6 (if you don't want to implement its logic fully yourself)
Have fun with that.
Thank you, good case study why you can't avoid or wrap or be smart and careful with getenv. The only way is that setenv shouldn't exist. But it also can't not exist since your dependencies call it.
OK if steam wanted to be sneaky they could just ld-preload a setenv which always allocates new memory.
And i get why it's not the default behaviour, since shell interpreters call setenv a lot and long running shell scripts exist.
The correct answer is that getaddrinfo should be fixed or redefined as not thread safe. If not fixed, a thread safe implementation should be used either through local implementation or using another library (this is open source, after all). I'm not sure the logic of why getaddrinfo calls getenv but it might be worth looking into that too.
@@chaos.corner getaddrinfo must inspect environment variable LOCALDOMAIN for example.
Making it non-threadsafe is a horrible idea, you're effectively plain forbidding MT networked software, which is not something you can just do especially not all these decads later.
Unfortunately getenv isn't the only legal way to retrieve an environment variable, you also have an external symbol that is always populated by the executable loader as per POSIX. None of the ways are threadsafe if setenv or putenv is ever called, and are all threadsafe if env is effectively constant.
So the real problem is that setenv and putenv are garbage that shouldn't exist in MT software. Just compose an explicit environment for child processes with execve and purge all setenv using libraries from MT software, or fix them to not do that.
getaddrinfo is a terrible 40 year old solution. We really need some more modern and OS wide service DNS caching solution. Lets put it into systemd.
But the getenv is no problem at all as modern programming had banned the use of setenv long ago. You never modify, just copy and modify when you start a new process. It's unfixable, just like "select" call.
@@llothar68 getaddrinfo under the hood makes use of whatever DNS caching solution the system uses (that can include systemd)
The worst thing is that someone may depend on this bug in C
it's a grandfathered feature now, can't change it
Most certainly, the only way to fix the bug is to return a copy of the string instead, but that would pass the responsibility for dealocating the copy to the caler
@@phantom_stnd I agree, but that again means programmers will do things they shouldn't. solves one problem but introduces a new one
Being sometimes correct and sometimes wrong is not a feature.
This makes so much sense now why Rust returns allocated String's whenever you call std::env::args() @@phantom_stnd
I'm so early that I got to report fake comments.
Yeah, the same
Same! Thanks for your work!
Hey, same here
ThioJoe made a thing to automate reporting spam comments
Thanks for that
Opting to trigger a memory leak rather than a crash in this scenario seems pretty enlightened; while environment variables can and do change, it's pretty rare that they're changing frequently enough with large enough values that they'd leak a problematic amount of memory in any realistic scenario. Certainly not great, but better than the alternative if you're working with a broken API like this.
Long running shell scripts are an example of programs that can be susceptible, so in linux ecosystem, that may be a breaking change.
@@SianaGearznope, the environment is part of a process's address space created by exec(2), shell scripts can alter the environment for new child processes but it's not a substitute for shared memory.
It's NOT a broken API, the environment is part of the address space of a new process.
If you are stupid enough to attempt to fake shared memory between threads using environment functions you are simply breaking the C memory model.
There are no guarantees about what the program will do, memory is copied into different caches and registers to actually be used and application programmers have to know about it.
@@RobBCactive what does shared memory have to do with anything? Shell is single threaded. The child process runs off a fresh cow copy. I'm saying deliberately leaking memory in setenv implementation may cause shell scripts to aggressively leak memory unless shell stops using setenv and moves to execve.
@@SianaGearz because threads share memory, doh.
Why would glibc have to leak memory in the main process thread? It's only a problem seen by incorrect programs
I don't agree a band aid is required, the Valve dev quotes are from the point of view of people doing hacks rather than getting client programs fixed.
People are allowed to write bad code and incorrect programs, they can be ignorant too.
The land of C where all memory is allocated and deallocated by hand,
and every single bug is either pointers, pointerarithmetic or straight up WTF territory. :D
I love C. It's whacky, bonkers, but yet a dozen times more stable than JavaScript could ever dream. XD
Rust edition 2024 will actually mark setenv unsafe, forcing you to use unsafe to use it.
can't they reimplement it?
@@mtarek2005 How the env vars are stored is a standard, nothing anyone can change all by iself.
@@framegrace1 is it a libc bug or linux bug? in first case you dont need to use libc, and is easily rewritable unless libc is plain syscalls in setenv/getenv implementations
@@mtarek2005 they should really reimplement setenv/getenv from the ABI spec of how the kernel/program loader puts environment variables right above the top of the stack when the process gets fork()'d but this time making it thread safe using some kind of synchronization primitives around them.
@@FrankHarwald the problem is that "the current environment" is a libc concept. You could hypothetically not use libc and just copy the envp before main, but then your copy of the environment wouldn't be synchronized with what other libraries / programming languages see when doing interop. Which would make your "current environment" basically useless.
And people say experienced C developers just don't write unsafe code. SMH
Code is inherently unsafe.
@@linuxguy1199 Unless you write Haskell where your code is mathematically proven to be safe if it compiles.
I don't know anyone that can make that statement in good faith. Software is written by humans who make mistakes, and it's language agnostic
@@chevychavanne4433 There's a lot of "overly compassionate" criticizers when ever the topic of Rust in the kernel is brought up.
@@chevychavanne4433 Some people (the same people that are arguing that memory corruption bugs are "a skill issue") aren't capable of that level of reasoning
That's it. I'm writing all my code in assembly from now on.
Giga brain move. It can't break if it's never finished
@@R4d1o4ct1v3_ i hear 6502 is in bloom this time of the year.
Yes that works and indeed every instruction is self documenting and all but if you call someone else's code you are still hosed. You need to do it in the "the whole machine is mine" mode and handle everything your self. Doing disk I/O is a bit more tricky than a lot of other things.
Great explanation of why Rust started to mark setenv as unsafe recently
It should never have been safe in the first place. Is in the docs.
One wonders why the functions aren't marked unsafe :P
Because rust is a sham
everything in C is marked unsafe
cause everything is unsafe (anyway rust can't fix glibc fault)
@@BruhKhokhols IIRC Rust 2024 is marking the relevant functions as unsafe so yeah Rust can fix it on its end
set_var is marked as unsafe in rust, I think it's currently only enforced for nightly and the upcoming 2024 edition
New bcachefs drama dropped!
I'm aware, there's a lot
Why do we pretend like POSIX editions can't be updated though, it's not always 1970 there xD
They can be but this is part of modern POSIX lol
@BrodieRobertson fixed* might be a better word, though it probably needs a little "going back to the whiteboard" action at the very least by this point
I wish POSIX shells defined array-literals
In most cases, you actually don't want to modify your own environment. The places I have seen that change the environment actually want to set a different environment for a child process. POSIX and C don't have functions to do this, so you have to do it yourself, but this way the process environment remains unchanged. Of course, sometimes you do need to adjust TZ or PATH or something, so this isn't universal.
Leaking memory and returning strings that are constant for the rest of program runtime is a completely viable approach to fully safeguard this function family, and given the spec, the only one that is actually robust. Since there isn't just about any software that is long running and updates env all the time.
OR IS THERE. Indeed there is, shell scripts, particularly long running ones aren't uncommon. To be actually good, shell interpreters should not use setenv, and should just maintain variables on heap, composing them into new process's environment on execution. But they'll probably be like "why should we be smart about this, we aren't multithreaded!"
Wouldn’t they only do setenv if you called “export” to set them?
@p0358 yes but scripts which continuously write exported variables are not uncommon.
Fork system call was a "virtue of necessity" back in the '70s in PDP days. Just spawn new process and explicitly enumerate what should be inherited into it.
here I am being a programmer myself, assuming other programmers are doing things properly. I only access environment variables in the main thread, when the program is starting. process or copy the values and store in a place I know for sure it will be safe. never assume something is thread safe, it probably is not.
Good programmers read Docs. Great programmers test their code and don't trust the docs.
@@moarjank race conditions can be extremely difficult to find. it's not as simple as testing. program crashes, ok there must be a bug, let me look deeper into this... it's working fine now, I cannot reproduce the bug. what can I do now. as for docs, it's rare I find them to be actually useful. they don't say much beyond the obvious, this function takes these arguments and one line to describe what the function does.
you should check most of your standard library calls also. anything marked "MT-Safe env" or "MT-Safe locale" is calling getenv() or reading the locale and allowed in a threaded context. other libraries certainly call this stuff. obvious stuff like gettext() definitely accesses the environment to see the fuller locale. strftime() might be a little less obvious and more required, you can't by your standards call that in anything but the main thread, which you might really want to do at times. that same thing goes for most time functions. lastly the gethostbyname/getaddrinfo type network functions also use getenv, so you'd have to also prefetch all the IP addresses network connections you're making on the main thread before splitting. it's a little hard to adhere to this.
I'm kind in C++ but would't be possible to overwrite the setenv and getenv functions to make them run in the main thread?
In rust the issue is closed, because they just made all the *env functions unsafe...
Which is code for: Idk, you deal with it.
Why didn't they just wrap them with a lock?
Oh, I guess because then direct calls would be safe but indirect calls via other library functions would still be unsafe. Still, surely libc could add locking inside setenv/getenv to fix it pretty easily.
@@nicholasvinenLocking would maybe help a bit, but not once you get the env var’s value pointer and it gets invalidated by setenv, since you don’t manage its lifetime after the getenv call. So setenv can either free the old pointer and cause crash or leak it like Mac did. And if you iterate over the whole environment array, then even more so problems are bound to happen…
The only way to fix it is to make it leak some memory one way or another and then have a fixed versions of the API that’d require you to free a copy of env vars instead of getting the raw original pointer, at the cost of some performance overhead…
There are many POSIX calls that are not thread safe. Nothing really special about `setenv` / `getenv`.
The problem is how ubiquitous those two are, and how third party libraries rarely flag themselves as "thread unsafe" because they're using those two calls. And of course, many libraries might only be using them indirectly, and their devs might not even be aware that they're pulling in a library that uses setenv/getenv and making themselves thread unsafe. Also, 'getaddrinfo()', a glibc function that is basically unavoidable if you want to do IPv6, uses those two calls and is therefore thread unsafe. The other thread unsafe POSIX calls are generally easier to avoid.
Well, maybe POSIX needs to be change to work well with modern tech and expectations. As Brodie said, expecting people to write single threaded code simply because system spec is stuck in nineties is stupid. Likewise, people cannot read all the source code of the modules they import.
@@elzabethtatcher9570 Dude, it’s completely trivial to create a new module that performs your getenv/setenv operations and wraps a mutex around them. This is 200% skill issue. Also, no, integrating that mutex in getenv and setenv themselves doesn’t solve the problem since that enables TOCTOU conditions.
@@elzabethtatcher9570 The programs don't actually have to be single threaded. It's just that multi-threaded programs have to do their own locking, which doesn't seem like an unreasonable requirement to me. There are other common patterns as well, such as delegating all OS interactions to a single main thread. But there has been an effort to extend POSIX for multi-threading, and there are thread safe versions of at least a few of the calls these days.
@@elzabethtatcher9570 POSIX is dead, a dead man can't change.
I would never have thought about using these in a multithreaded env. I usually use it at startup in the main thread and then never again.
I know the C devs kinda poopoo on Rusts embedding of semantics into the type system, but this is an example where it could be used to communicate this hazard. If std::env::var was NOT marked Sync then we'd know it's not safe across threads. Which would then propagate to any libraries that used them and didn't account for this
Wow, this is... a real crap API.
I can understand the original getenv, but adding a setenv without that's supposed to interact with it is just a disaster. They should have made a totally separate, reentrant API for getting/setting stuff in envp.
setenv() being non thread safe is one of my pet peeve. Mixing fork() and threads is another one. It’s too easy to hit a threading lock in the CoW address space.
The easy way to solve this would be to copy the environment space each time a new thread is spun up. The down side would be that different tasks could then disagree about the value of a variable. A far more difficult way to solve it is to copy as needed only but then you have to keep multiple cores in sync.
It is a pointer problem. Processes are not a problem, as when one is created copies all variables.
I’ve been having an issue where the library page will just freeze indefinitely after a minute or two. Closing the window and reopening it works for a bit. Notably I only have to close the GUI, I can leave steam open in the systray. Not sure what it’s about, maybe this is related
I do remember steam crashing more often in the past. Not a crazy amount even before, but it seems to have gone down to almost never
What's really confusing to me is, why we don't use files for this instead. Even magic kernel files (like /proc) are preferable, since going through syscalls means the problems can be much more easily solved. Obviously there were reasons for them at some point, but it shouldn't be to hard to rewird getenv and setenv to a read/write operation on sth like /proc/ID/env instead, shouldn't it?
(Obviously, the problem is partly in C / posix standard, but we still could have a better getenv2 / setenv2 and hope everyone switches over at least..)
Files generally need to be closed when you're done, which isn't really any different from just allocating a temporary buffer and then requiring the user to free it later. Not closing the file when you're done is basically the same as just leaking the temporary buffer. Yes, it'll be cleaned up when the program exits, but that could be arbitrarily far away.
C++ and Rust programmers wouldn't really need to worry about this since they can automatically clean up resources when they go out of scope, but in any other language, this would require garbage collection, which only works for in-memory buffers but not files.
There might be several reasons for this. Keep in mind that the POSIX standard is from a time where memory was extremely slow. So reading from actual files was a no go back then because the environment variables are used at runtime quite often, which would SIGNIFICANTLY slow down programs. Now, obviously with virtual filesystems like /proc this is no longer an issue, but POSIX is POSIX. The POSIX standard also doesn't really assume that things are saved to disk or that VFS even exist (keep in mind that Linux is just one of the POSIX compliant systems, not every POSIX system uses a VFS, take MINIX for example).
Using a /proc/id/env thing is not a specifically great idea imo, because that'd create problems when spawning threads and processes from the application itself (since the child would now need the parent-id to access its proc directory + environment, which, however, might have already been offed making the process an orphan, which will probably trigger a cascade of exception handling that needs to occur that would be costly and REALLY annoying).
I'm all for some form of globally accessible file in the /proc system though, but to make this compliant with POSIX the standard would be subject to change which can, as I said, mean that some POSIX systems will have heavy performance loss.
This is an extremely complex subject, but it should have been addressed decades ago.
@@Finkelfunk I agree that integrating it with POSIX is probably the biggest issue. However, I don't really see that compatibility between Linux and BSD on the lower level is necessary if we could have a new getenv2 or setenv2 call that would be implemented differently on both OSs and just be threadsafe by definition, and Linux could just have that before it gets into POSIX. Fixing the old getenv might not even be possible, but maybe there is a way. For the proc files, I don't even think you need to have the mother process id, you could just have threads or locally spawned processes have the same env file, that is mapped correctly under the open/close and read/write syscalls, so that two threads cannot have it open at the same time, or so that they are synced regardless, or something along those lines. After all, since theyre in VFS, they could just be memory mapped and the kernel just needs to make sure that they don't override each other. We could even have a kind of "env mutex lock" syscall, that would specifically lock this file for read/write operation for any other thread, or something like that. Idk if this is possible in the kernel, but putting it there would probably the easiest since it already has to do the scheduling and syscalls for the os threads to begin with.
@@svaira The issue there is now you have two types of setenv that ultimately both need to set global variables for the user. How would you make that threadsafe? How do you sync those? If an application decides to call "legacy" setenv, then the new getenv2 still needs to function, but how will it guarantee interoperability, if setenv doesn't provide multithreading-security?
Just slapping a 2 at the end and calling it a day isn't the way to go about fixing this issue, especially considering they still need to share the same resource under the hood.
Yes and no, what if the parent process spawns two new children who each modify the environment? How will you communicate that the environment has changed between them? There's dozens of edge cases you need to think about, remember that we are talking about millions of active users that use this API. This would be an example of an API that is incredibly easy to use wrong again.
If you let the Kernel clean up environment variables and synchronize them across users, then the Kernel breaches user space because environment variables are not set in Kernel space but are user specific. So now the Kernel manages a file that needs to be synced across the user space, which goes against the entire philosophy of Linux. Adding a syscall to lock a user space file just adds insult to injury in this regard. This also has nothing to do with scheduling, that is a direct interaction with the hardware and that is why that is a kernel job. Setting variables for local programs to run is entirely a user-land task.
@@Finkelfunk Easy. You don't. New functions write and read their own set of variables.
Subscribing to changes is trivial, not sure what's the problem here?... Surely C has patters for this, whether it's callbacks or something else.
Loved the video! Thank you 👍
That is a very good explanation.
It's weird to have this kind of problem in 2024.
Game mode on Steam Deck does not run KDE.
About your last question: it seems to start faster as of late, if anything.
I remember steam used to(maybe still does idk) crash after a very specific amount of time of having the screenshot manager open(with thousands upon thousands of screenshots)
is this a problem in musl libc?
on a related but different note, i noticed that if I add certain (graphics related) environment variables in /etc/environment in the wrong order, gnome session manager won't start. I never realized that could be relevant and i still don't understand why.
sometimes the bug is glibc using a character input as an array offset
isalnum(3) iirc
omg hi
I learned a lot. Thanks!!
Yeah, if your libraries do stuff that breaks stuff, it's going to break stuff. That's always been the case.
100% that 'intended behavior' has to change
I do have my ideas on how to fix the situation while keeping the semantics, I'd have to do some experimentation 1st though
Couldn't they make a get_env_v2 and deprecate the old ones?
I guess the workaround way i would handle this would be create a threat who's sole job is to manage environment variables for the application. This doesn't fix libraries doing this, but does ensure application code don't add to it.
Correct!
Yet MT code needs to be sure its libraries are designed to be thread safe.
Fundamentally you cannot share memory between threads without mechanisms to ensure coherency or undefined behaviour ie an incorrect program will result.
It seems like using multiple processes that can operate independently and be simultaneous is forgotten by many.
Changing environment as a replacement for a shared data structure between threads is simply brain-dead. You'll simply serialise the algorithm in any correct program anyway.
who is this linux guy I keep hearing so much about
He is your father, Slack
At this point, it seems like it would be less effort to patch glibc and purge things relying on (ab)using the unsafe behavior
I'm so early to this video on some systems but I'm late on others
What if we put all these variables in some sort of registery system /s
11:27 - x11, dbus, etc using getenv, a known race-condition read of publicly writable null terminated char**array?
how come nobody uses this for more *exotic* manipulation of state?
Steam usually works perfectly ..it now throws 2 errors on start up though related to Boxtron, which im confused about, other than that it's been flawless.
But why envvar is mutable in the first place?
There is no such thing as something immutable in classic early C, it predates the (leaky) attempts to introduce that.
Setenv has indeed always been a mistake and it's something that is very apparent retroactively. As in there really isn't a good reason for it other than it just happened that way and was cast into standard by the time someone actually thought about it.
Because it's not envconst?
Hopefully Florian Weimer's patches land sooner than later.
When launching a game that needs env vars set, just spit out a new shell script, chmod it to +x, then run that to launch the game. Bonus points for creating it on tmpfs somewhere. Or /tmp, whatever. Use the same shell script path when you launch every game, you only need one at a time. No need to mess up your whole Steam client's memory address space just for some tiny env vars. If you need global variables in your code (boo!) just use a mutex and use globals, who cares. Or use a config file which you parse every time, I mean Steam is slow enough already, who cares. Don't, I repeat, don't mess up your memory. I mean, once they've figured out that thou shalt not set env vars, then simply don't, let bash handle it.
Shell scripts are jank. You don't do that, eww. You use an exec from the e group. They said they now do that, so apparently they need to use setenv for something else than launching games, though I do wonder what they need it for.
@@GrzesiekJedenastka Sure, there's probably a recommended proper way, but a shell script is a quick and dirty solution that will work and you can do it right now without changing much code... I think.
Steam needs an official Flatpak. The unofficial one works better than a native install, and much better than any distro package (Snap... just, no...)
such a pain in the behind! Writing multitasking code is
misc race conditions shall b treated as a enuscible imo so they can b easier to
I still don't fully understand the root cause. In a hypothetical scenario, if I made a component to my application that I'll call "env manager" that runs as a thread/forked process which is responsible for all setenv and getenv calls and is being interacted with through IPC like with zeromq for example, would the issue still occur? Is the environment only allocated for the first process? Or is it more that any one of the threads can "take ownership of" the environment and just once another thread comes along and tries to access it causes segfaults?
Race condition in a financial software project: ☠
The reality is Linux is not better than Windows at the core. Both OSes have ultra terrible comedy tier APIs.
Win32 is much better designed than POSIX because it actually had the foresight to avoid some mistakes. It is overall more consistent and pretty reliable. The only thing that it suffers from is that it is bloated because many of the things that Linux relegates to the userland are integrated in Win32 and also has a lot of cruft. But then, so does Linux, but people are trying to pretend they're not actually cosplaying as a 1970s mainframe once you track down the core heritage and POSIX.
i bet musl doesn't have this problem because it doesn't support environment variables
The library is fine, C was perfect in 1989. The problem is that unqualified people are writing software. These people should not have jobs, full stop.
I've noticed Steam not running (probably crashed) every now and then on my Steam Deck when using the Desktop mode. Curiously I don't remember ever seeing this on my LTS Ubuntu.
I had a lot of crashes on steam since I upgraded to Fedora 41 (Fedora 41 is acting weirdly here, not sure why, but my experience is worse in comparison to 40). Then at some day I started using Steam Beta, which fixed the issue.
Set and get them at the start of code, never update them. Think thats been a rule for 20 years? How could any library fuck this up? I suppose its a problem with being allowed to do so.
Probably because it's convinient? As you can literally use said env vars as global variable, even across files
Devs tend to choose convinience over stability lol
Do people actual have problem with Steam crash on Linux
Yes. steamwebhelper is by an unfathomable margin the least stable program I’ve ever used. Three quarters of my coredumpctl list is steamwebhelper dying on SIGILL. That’s supposed to be like the most unlikely signal a well written program would raise…
the one most terrifying singular word for a programmer is "race condition"
There was also that .sh bug where it could delete your homedir
yes
This sounds like broken by design to me.
just use windows
POSIX malding, crying and shitting its pants at the fact Win32 is simply better designed
Poor linux users
what if we rewrite setenv in rust tho?
You can't. This is an API problem, not an implementation problem.
Forget about setenv/getenv. In POSIX programs can just straight up mutate the char **environ pointer without locking anything. Good luck with implementing a thread safe getenv when the contents of the environment can just change while you are iterating over them.
Rust
Edit: 5:25 Okay, watching further the video I see even Rust is affected by it because it uses glibc in the backend. However using musl should at least avoid this I assume, but I'm not sure at the moment.
How would rust convert extern non-threadsafe code into threadsafe code?...
@@NJ-wb1cz I edited the comment already, because at the time of making the initial comment about Rust I didn't know it was using glibc for this function in the back. Rust cannot run unsafe code safely.
rust kind of fixes this, if you only ever use rust code you can freely and safely set environment stuff because it's behind a mutex lock, but the second you call any c code that calls any of the env functions you're fucked as well because (for obvious reasons) the c code doesn't lock the mutex of the rust standard library.
you kind of have the same issue with go: all of their env access is also mutex protected, until you use cgo and interop with c code. but it's worse in rust because, not only because a lot more people depend on c libraries than in go, but the rust std also calls libc functions that may use the getenv function, for example host resolving is done via libc getaddrinfo, which calls getenv on glibc (but it doesn't on musl).
@@m4rch3n1ng As far as I understand this is not fixable in Rust, when using glibc to set/get environment variables. So it doesn't fix it at all.
Or is it possible to set environment variables in Rust without using glibc?
musl doesn't fix this either. This is an API problem, not an implementation problem.
Forget about setenv/getenv. In POSIX you can just straight up mutate the char **environ pointer without locking anything. Good luck with implementing a thread safe getenv when the contents of the environment can just change while you are iterating over them.
luckily i never had much crashes to begin with but i have noticed actually over the past few month that the number of times i see an error screen in the client has significantly gone down.
Linux in general is kind of weird
Thread safety just does not exist.
Quite sure that is objectively incorrect
Retrofitting concurrency safety is hard.
THis is not a bug, is a POSIX implementation incompatibility for Linux. Few linux apps will do that because they will use a workarounf, and has not been fixed exactly for the same reasom, everyone knows and there is an easy workaround and libc developers have things more urgent to do.
Valve is seeing this because they are running code from other OS's where the implementation doesn't break.
ENVIRONMENT variable
that means a global variable effectively. even a name suggests that.
surprise surprise: global variables are bad for multithreaded code
Why is Steam app proprietary?
'cuz it's a DRM
@@xXRealXx Yes-ish, some Steam games are DRM free and even neeed Sream to run
@@tablettablete186 yes, there are DRM free games on Steam that will even run without Steam, but the big reason for why Steam is proprietary is because it's a kind of DRM. And I'm not aware of any open source DRMs. Plus I doubt Valve would want to make Steam itself FOSS or open source in some other way
@@xXRealXx I see, you're right about it.
If you use the operator "&" to run multiple commands in the background (aka async), you have manually setup a race condition
One massively glaring error here is that setenv isn't in the C standard library. You're not meant to set environment variables, they should already be set before your program runs. The fact that there are shit programmers who think setting environment variables at runtime is in any way a good idea is sad. It's kind of stupid how many people in the comments are blaming C for non-standard bullshit that people do. It's a shame that people refuse to educate themselves and TH-cam keeps censoring so many of my posts so clearly no one will learn from me, but it's even worse when the only voices allowed to speak do such a sloppy job with little to no research and get basic facts wrong. It's tantamount to spreading misinformation which is something they claim to hate, but in reality they perpetuate that fuckery all on their own.
1) setenv is in POSIX, it's technically not in the ISO C standard, but this is an insane position to take, because by that logic, C also doesn't have file descriptors or sockets or a bunch of other stuff. Unless you are doing Windows-only development or are using C for ultra low-level embedded programming, POSIX is a "part" of C for all intents and purposes.
2) Modifying the environment of the current program without starting a whole new process *is* actually a useful feature. The problem is that the current API is bad, not that the feature itself is fundamentally broken.
@@ruroruro Not only is that a stupid point of view to take, but it speaks to how fundamentally bad the industry has gotten that you actually believe what you're saying is in anyway an intelligent thought. First of all, you admit the caveats of such a point of view, which means you're already arguing in bad faith, and from a rather arrogant stance as well because there are other platforms, and they do count. Second of all, if you need to send yourself a message, then you're doing it horribly wrong by using setenv, both at all, and incorrectly for how it functions. Please quit now and go work as a fry cook.
@@ruroruro And since TH-cam is playing shenanigans, I'll reiterate that you shouldn't be programming if you're going to take the stance that you are. Go become a fry cook instead of littering the world with shit code from you who is a shit programmer.
@@ruroruro Fuck TH-cam for deleting perfectly innocuous posts. Stupid pieces of shit are the kind of idiot programmers that bring us dumbfucks like you who insist on doing things the wrong way and will never be corrected in your idiot thinking because you won't ever see the correction. Go ahead, continue making the world a worse place by writing code that will guaranteed fail. Don't bother learning the correct method of passing messages. Keep thinking that everything POSIX is correct. Although, if you insist on doing these things, the world would be better off if you worked for McDonald's instead.
I'm suuuuuuure TH-cam is censoring you, yup, you're right... Brodie comes down with a comically large spoon and scoops out your comments specifically, you're being persecuted for your truly based knowledge. Be real now
This video should have considered the C memory model rather than blindly repeating what Valve application programmers are saying.
The hardware lies to you guys!
You are NOT reading/writing to memory but copies held in caches and registers, so fundamentally accessing shared memory carelessly leads to undefined behaviour, meaning an incorrect program.
The examples are demonstrations of application programmer incompetence & ignorance, which Valve are working around.
There is no safe way to use the API in C
@@dynfoxx rubbish!! It's purely a MT issue. You're saying the equivalent that changing any value is unsafe and that includes the parameters of functions in purist languages like Haskell.
@@RobBCactive no, the API is broken, because to use it safely, you have to only interact with the environment while holding a mutex, but the standard doesn't include this mutex. This means that unless your program only runs code that *you* have written (no libraries, no interop between languages, no CFFI) then it is impossible to do so in a thread-safe manner. Because there is no way to make every piece of code to agree on a single locking mechanism to use for accessing the environment. You can tell that this is a practically impossible task, because even glibc itself sometimes gets it wrong internally.
@ruroruro it's not meant to be thread safe, Environment is inherently about processes and passing to child processes.
You're just advocating that inefficient erroneous poorly designed code should be forced on every program.
The environment is passed via a char **, like argv so the solution you propose fails. Get/setenv are merely optional wrappers used for convenience and not a substitute for knowledge.
@@RobBCactive I am perfectly aware that the POSIX API directly exposes char **environ. When I say that the API is broken, I am including **environ in that.
A thread-safe version of this API would either not expose **environ directly at all (instead requiring you to call a function that would take a mutex, copy the current state of the environment and then return that copy) or the API would expose an "official" mutex that every MT-safe program would be required to hold while interacting with the **environ pointer.
P.S. please tell me, where exactly in my comment am I advocating for forcing "inefficient erroneous poorly designed code" on every program?