Repository: yt-dlp/yt-dlp

Issue:
[Downloader] ERROR: unable to open for writing: [Errno 29] Illegal seek
### Checklist

- [X] I'm reporting a bug unrelated to a specific site
- [X] I've verified that I'm running yt-dlp version **2022.04.08** ([update instructions](https://github.com/yt-dlp/yt-dlp#update)) or later (specify commit)
- [X] I've checked that all provided URLs are alive and playable in a browser
- [X] I've checked that all URLs and arguments with special characters are [properly quoted or escaped](https://github.com/ytdl-org/youtube-dl#video-url-contains-an-ampersand-and-im-getting-some-strange-output-1-2839-or-v-is-not-recognized-as-an-internal-or-external-command)
- [X] I've searched the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=) for similar issues including closed ones. DO NOT post duplicates
- [X] I've read the [guidelines for opening an issue](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#opening-an-issue)

### Description

To reproduce this issue:

1. create a fifo:
mkfifo /tmp/test

2. run yt-dlp:
python3 yt-dlp -v -f 140 -o /tmp/test http://www.youtube.com/watch?v=WSRWo4ZhnHM

3. run a player instance to play the tmp fifo:
mplayer -ao null -vo null /tmp/test
EDIT:
you can also use:
cat /tmp/test > /dev/null
to just read from the fifo, no player needed to reproduce this issue.

yt-dlp crashes with the given verbose log.


I can note, that an older version which I tested is still working fine. The latest version crashes with "ERROR: unable to open for writing: [Errno 29] Illegal seek".

An old version tested:
yt-dlp version 2021.10.10 (zip) <-- which is working
yt-dlp version 2022.04.08 [7884ade65] (zip) <-- fails

Can someone tell if something fundamental changed here? First time seeing this.
I am currently trying to figure out, which newer version started breaking it, because my older version which still works is outdated.

Maybe someone can help.




### Verbose log

```shell
python3 yt-dlp -v -f 140 -o /tmp/test http://www.youtube.com/watch?v=WSRWo4ZhnHM
[debug] Command-line config: ['-v', '-f', '140', '-o', '/tmp/test', 'http://www.youtube.com/watch?v=WSRWo4ZhnHM']
[debug] Encodings: locale UTF-8, fs utf-8, out utf-8, err utf-8, pref UTF-8
[debug] yt-dlp version 2022.04.08 [7884ade65] (zip)
[debug] Python version 3.7.3 (CPython 64bit) - Linux-4.9.0-13-amd64-x86_64-with-debian-10.12
[debug] Checking exe version: ffmpeg -bsfs
[debug] Checking exe version: ffprobe -bsfs
[debug] exe versions: ffmpeg 4.1.8-0, ffprobe 4.1.8-0
[debug] Optional libraries: certifi, sqlite3
[debug] Proxy map: {}
[debug] [youtube] Extracting URL: http://www.youtube.com/watch?v=WSRWo4ZhnHM
[youtube] WSRWo4ZhnHM: Downloading webpage
[youtube] WSRWo4ZhnHM: Downloading android player API JSON
[debug] Loading youtube-sigfuncs.js_596ef930_105 from cache
[debug] Loading youtube-nsig.596ef930 from cache
[debug] [youtube] Decrypted nsig T0SxpikMkB9eNsRln => OFaNYkq205V1WQ
[debug] Sort order given by extractor: quality, res, fps, hdr:12, source, codec:vp9.2, lang, proto
[debug] Formats sorted by: hasvid, ie_pref, quality, res, fps, hdr:12(7), source, vcodec:vp9.2(10), acodec, lang, proto, filesize, fs_approx, tbr, vbr, abr, asr, vext, aext, hasaud, id
[info] WSRWo4ZhnHM: Downloading 1 format(s): 140
[debug] Invoking downloader on "https://rr4---sn-4g5lzner.googlevideo.com/videoplayback?expire=1650997027&ei=w-JnYrq6Bobj7gOO3JbYDg&ip=5.231.196.145&id=o-AN8w2oHcOdM1ZLbAoB_GOhd6JZRw9S8Ik-PvK0rlxf2O&itag=140&source=youtube&requiressl=yes&mh=nl&mm=31%2C26&mn=sn-4g5lzner%2Csn-f5f7lnel&ms=au%2Conr&mv=m&mvi=4&pl=16&initcwndbps=2270000&vprv=1&mime=audio%2Fmp4&gir=yes&clen=1896926&dur=117.167&lmt=1650896162842003&mt=1650974936&fvip=1&keepalive=yes&fexp=24001373%2C24007246&c=ANDROID&txp=5432434&sparams=expire%2Cei%2Cip%2Cid%2Citag%2Csource%2Crequiressl%2Cvprv%2Cmime%2Cgir%2Cclen%2Cdur%2Clmt&sig=AOq0QJ8wRQIhAIGGR2ggBWF-gL7_23XC2cZXpe0daxuLszCB99afjdjjAiACAeX5W6V3rN5Y7rb_S1-HF5Hc6TEK1G-WWduGlUYE0A%3D%3D&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&lsig=AG3C_xAwRAIgNqe4SSki3jjtFCcG5VB7oivzTAlbF_S7CvfZjiJuFfwCIA_Mmj4fbfS9WioJzIKf-wnN6AhggYfE6MlatCxDYxuu"
ERROR: unable to open for writing: [Errno 29] Illegal seek
Traceback (most recent call last):
  File "yt-new-broken/yt_dlp/downloader/http.py", line 261, in download
    ctx.tmpfilename, ctx.open_mode)
  File "yt-new-broken/yt_dlp/downloader/common.py", line 218, in inner
    return func(self, *args, **kwargs)
  File "yt-new-broken/yt_dlp/downloader/common.py", line 235, in sanitize_open
    f, filename = sanitize_open(filename, open_mode)
  File "yt-new-broken/yt_dlp/utils.py", line 619, in sanitize_open
    stream = locked_file(filename, open_mode, block=False).__enter__()
  File "yt-new-broken/yt_dlp/utils.py", line 2004, in __enter__
    self.f.truncate()
OSError: [Errno 29] Illegal seek
```



Discussion:
Most likely, either a3125791c7a5cdf2c8c025b99788bf686edd1a8a or fcfa8853e41ca04714a7aa28a783e2804c184375 is the culprit

cc @jakeogh
> I am currently trying to figure out, which newer version started breaking it, because my older version which still works is outdated.

@Crypto90 Either 2022.03.08 or 2022.02.04 should work
Will this 'locked_file' behavior stay like this or is this a bug?
Depending on, if this gets fixed or if I have to patch a custom version for myself in the future?

I didn't close the issue, did I? 😏 So it is a bug.
![image](https://user-images.githubusercontent.com/6623388/165381385-3aec8907-113d-4cec-aaef-902e79024dcb.png)


But will need to investigate how it can be fixed
Ah interesting. For the special case of writing to a FIFO we could either catch the OSError on truncate (better option I think, need to test), as named pipe's don't support seek, or skip the truncation when the fd is a fifo. I'll test and send a pull today.
How does `io.open` deal with this? We should do exactly what Python does, so that we avoid other edge cases like this
@jakeogh have you found a solution for this?

Resolving patch:
diff --git a/yt_dlp/utils.py b/yt_dlp/utils.py
--- a/yt_dlp/utils.py
+++ b/yt_dlp/utils.py
@@ -2011,7 +2011,11 @@ def __enter__(self):
             self.f.close()
             raise
         if 'w' in self.mode:
-            self.f.truncate()
+            try:
+                self.f.truncate()
+            except OSError as e:
+                if e.errno != 29:  # Illegal seek, expected when self.f is a FIFO
+                    raise e
         return self
 
     def unlock(self):


Test patch: