Is this a bug? Linux - String(contentsOfFile:encoding:) hangs for 'special' files

Hello,

String(contentsOfFile:encoding:) appears to hang on Linux when reading certain types of files. Famously in Unix 'everything is a file', so should I expect to be able to use the method on anything that can be 'less'ed on Linux?

e.g. on a RaspberryPi 3B+ (Raspian Stretch) and with Swift 4.2 the following code in case 1 works, and in case 2 hangs without throwing.

import Foundation
print("Test of getContentsOfFile:")

// Normal Text File (case 1)
var testFilePath = "/home/pi/swift/hello.txt"
do {
    let fileText = try String(contentsOfFile: testFilePath, encoding: .utf8)
    print("fileText: \(fileText)")
} catch {
    print("could not read contents of file...")
}

// Linux Special 'File' (case 2)
testFilePath = "/sys/devices/w1_bus_master1/w1_master_name"
do {
    let fileText = try String(contentsOfFile: testFilePath, encoding: .utf8)
    print("fileText: \(fileText)")
} catch {
    print("could not read contents of file...")
}

Can you append a backtrace (via sample $PID) to a bug at https://bugs.swift.org/?

Sorry - I'm very much an amateur at this. I've done some Obj-C development on MacOS, but I'm rather new to Linux.

Isn't 'sample' a MacOS/BSD tool? This is an issue on Swift on Linux.

I don't seem to have sample available via the OS or via apt-get.

Welp, you are correct and I was distracted. I meant pstack $PID.

Again - apologies for being an idiot - but assume that I have very limited knowledge of command-line debugging. My experience is of dragging lovely blue arrows in Xcode. I have a rather perverse inverted knowledge of coding in a top-down way, knowing little about the fundamentals and more about composing Foundation/AppKit objects.

I think that pstack is, alas, not available on the architecture of the Pi (Armv7)

An alternative is to run the process in gdb or lldb, wait until it hangs, send it a signal for which the debugger will stop (say, hit ^C), then use the bt command.

Thanks, I'll do some reading and see what I can generate..

Now I feel very stupid. That's not going to be of any use as I'm using a pre-compiled swift binary, from which I assume that all debugging info has been stripped.

Working out how to compile my own version of foundation and use that in place is way beyond the scope of this thread, so I think I'll bow out now and try and learn more outside this forum...

Thanks.

GetSensorName
path: /sys/bus/w1/devices/28-031590d3d1ff
nameFilePath: /sys/bus/w1/devices/28-031590d3d1ff/name
ContentsOfFile:
Process 833 stopped
* thread #1, name = 'OneWireTemperat', stop reason = signal SIGSTOP
    frame #0: 0x7610c39c
->  0x7610c39c: ldr    r7, [sp], #4
    0x7610c3a0: cmn    r0, #4096
    0x7610c3a4: bxlo   lr
    0x7610c3a8: b      0x7610c3e8
Target 0: (No executable module.) stopped.
(lldb) bt
* thread #1, name = 'OneWireTemperat', stop reason = signal SIGSTOP
  * frame #0: 0x7610c39c
    frame #1: 0x76a47af4
    frame #2: 0x76a3eb80
    frame #3: 0x76a3ead4
    frame #4: 0x76c884d4
    frame #5: 0x76c89478
    frame #6: 0x76c88b2c
    frame #7: 0x76c97e54
    frame #8: 0x004053d4
    frame #9: 0x00404d1c
    frame #10: 0x00406fc8
    frame #11: 0x00402308
(lldb) quit

@Diggory can you run strace -p PID_OF_YOUR_PROGRAM when it's stuck? it'll probably print something like [read unfinished...] or something. You can then immediately press Ctrl+C and just paste the output here.

I assume it's stuck in some system call and the strace should tell you which one.

@millenomi suggested using lldb which should totally work too. Given that it was unable to symbolicate anything I assume you tried using the system's lldb (under /usr/bin/lldb) instead of the one that comes with Swift? There should be a lldb binary next to where your swift and swiftc binaries live. So if you Swift binary is /usr/local/bin/swift, there should be a /usr/local/bin/lldb too and that should work better. I realise you're on ARM though and I don't know how well Swift's lldb is tested under (Linux on) ARM so strace might be enough in this case.

But if you can't get the lldb that ships with Swift to work on ARM, please do file a bug on bugs.swift.org too.

Lots of this: I'll see what I can do with the swift version of lldb.

read(5, "", 4080)                       = 0
read(5, "", 4080)                       = 0
read(5, "", 4080)                       = 0

By the way, I did some very caveman-like debugging a few days ago and looked through the source of String / URL and Data and worked through what functions would be called by String's contentsOfFile:encoding: method. I think that the hang takes place in Data's init(contentsOf:) function.

@Diggory ok, I just the code that does the file reading and there are quite a number of bugs there... The bugs there also explain your issue. The code in question relies on two things that both are not true:

  1. files don't shrink
  2. one can always read st_size bytes from any file

How does this code rely on those untrue conditions:

        var remaining = Int(info.st_size)
        var total = 0
        while remaining > 0 {
            let amt = read(fd, data.advanced(by: total), remaining)
            if amt < 0 {
                break
            }
            remaining -= amt
            total += amt
        }

This will spin in a loop until it has read remaining bytes in total which might never happen: For one the file might have shrunk between that fstat and the read calls and on top of that some pseudo-files in Linux (especially in /sys) return a large st_size and you can't actually read any bytes. For example on my machine:

$ ls -la /sys/devices/pnp0/00\:00/uevent
-rw-r--r-- 1 root root 4096 Oct 15 13:54 /sys/devices/pnp0/00:00/uevent
$ stat /sys/devices/pnp0/00\:00/uevent
  File: '/sys/devices/pnp0/00:00/uevent'
  Size: 4096      	Blocks: 0          IO Block: 4096   regular file
Device: 77h/119d	Inode: 2450        Links: 1
Access: (0644/-rw-r--r--)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2018-10-15 14:01:25.211472722 +0000
Modify: 2018-10-15 14:01:25.211472722 +0000
Change: 2018-10-15 14:01:25.211472722 +0000
 Birth: -

so the file has st_size = 4096 but

$ cat /sys/devices/pnp0/00\:00/uevent | wc -c 
0

so we can't actually read anything from it... Testing this with Foundation shows this problem nicely

  1> import Foundation
  2> String(contentsOfFile: "/sys/devices/pnp0/00:00/uevent")
[... hangs forever ...]

which is exactly the problem you're hitting. I filed a bug.

4 Likes

Thanks very much!

Thank you for filing!