[Bug 1750173] Re: getline doesn't terminate after fork

Navneeth Jayendran navneejay at gmail.com
Sun Feb 18 08:58:35 UTC 2018


Note that this behavior isn't specific to getline.

Contents of 123.txt: 14991 arbitrary bytes

Code:

```
#include <stdio.h>                                                               
#include <stdlib.h>                                                              
#include <unistd.h>                                                              
#include <sys/types.h>                                                           
#include <sys/wait.h>                                                            
#include <sys/types.h>                                                           
#include <sys/stat.h>                                                            
#include <fcntl.h>                                                               
                                                                                 
void h(FILE* fd) {                                                               
 (void)fd;                                                                       
 pid_t p = fork();                                                               
 if(!p) {                                                                        
  sleep(2);                                                                      
  exit(1);                                                                       
 } else if (p > 0){                                                              
  int s;                                                                         
  waitpid( p, &s,0);                                                             
 } else{                                                                         
  perror("Fork");                                                                
  exit(2);                                                                       
 }                                                                               
}                                                                                
                                                                                 
int main() {                                                                     
 FILE* fd = fopen("123.txt", "r");
 if (!fd){
   perror("fopen");
   exit(2);
 }                                                                                                              
 size_t limit = 10;                                                             
 while(limit--) {                                                                
  char output[10000] = {0};                                                      
  ssize_t retval = fread(output, 1, 9, fd);                                      
  fprintf(stderr, "fread: %zd\n", retval);                                       
  if (retval <= 0){                                                              
    exit(0);                                                                     
  }                                                                              
  h(fd);                                                                         
  retval = fread(output, 1, 4096, fd);                                           
  fprintf(stderr, "fread: %zu\n", retval);                                       
  if (retval <= 0){                                                              
    exit(1);                                                                     
  }                                                                              
  sleep(1);                                                                      
 }                                                                               
 fclose(fd);                                                                     
 return 0;                                                                       
} 
```

Expected output: 
```
fread: 9
fread: 4096
fread: 9
fread: 4096
fread: 9
fread: 4096
fread: 9
fread: 2667
fread: 0
```

Actual output:
```
fread: 9
fread: 4096
fread: 9
fread: 4096
fread: 9
fread: 4096
fread: 9
fread: 4096
fread: 9
fread: 4096
<doesn't terminate>
```

It seems that the origin of this problem is that block buffered input
streams in glibc perform calls to `lseek(2)` right before program
termination. The `lseek()` call appears to reset the file descriptor
offset to one after the last byte that was actually flushed out to user
buffers

e.g if `getline` reads "hello\n" from a file containing "hello\nworld",
the program calls `lseek(3, -6, SEEK_CUR)` at program exit. This can be
confirmed using the strace tool.


This can be resolved by flushing input buffers prior to calling fork(). So whether this is a bug or feature remains to be seen.

-- 
You received this bug notification because you are a member of Ubuntu
Foundations Bugs, which is subscribed to glibc in Ubuntu.
https://bugs.launchpad.net/bugs/1750173

Title:
  getline doesn't terminate after fork

Status in glibc package in Ubuntu:
  New

Bug description:
  1) lsb_release -rd
  Description:    Ubuntu 16.04.3 LTS
  Release:        16.04

  2) apt-cache policy libc6
  libc6:
    Installed: 2.23-0ubuntu10
    Candidate: 2.23-0ubuntu10
    Version table:
   *** 2.23-0ubuntu10 500
          500 http://archive.ubuntu.com/ubuntu xenial-updates/main amd64 Packages
          500 http://security.ubuntu.com/ubuntu xenial-security/main amd64 Packages
          100 /var/lib/dpkg/status
       2.23-0ubuntu3 500

  3) The following program fails to terminate, although it should when
  compiled with no optimizations. This can be compiled with both clang
  and gcc and the same behavior described happens. This doesn't seem to
  happen with older versions of the library [citation needed].

  Contents of 123.txt
  ```
  AA\n
  BB\n
  CC
  ```

  Code:

  ```
  #include <stdio.h>
  #include <stdlib.h>
  #include <unistd.h>
  #include <sys/types.h>
  #include <sys/wait.h>
  #include <sys/types.h>
  #include <sys/stat.h>
  #include <fcntl.h>

  void h(FILE* fd) {
  	(void)fd;
  	pid_t p = fork();
  	if(!p) {    
  		exit(1);   
  	} else {
  		int s;
  		waitpid( p, &s,0);
  	} 
  }  

  int main() {
  	FILE* fd = fopen("123.txt", "r");
  	size_t cap = 20;
  	ssize_t br = 0;
  	char* buf = malloc(cap);
  	int linecount = 0;
  	while(1)  {
  		br = getline( &buf , &cap, fd);
  		if(br == -1) break;
  		puts(buf);
  		h(fd);
  	}   
  	free(buf);
  	fclose(fd);
  	return 0; 
  } 
  ```

  Expected:

  ```
  AA  
      
  BB  
      
  CC  
      
  ```

  Actual:
  ```
  AA  
      
  BB  
      
  CC  
      
  AA  
      
  BB  
      
  CC  
      
  <doesn't terminate>
  ```

  
  * I believe it is with how the FILE* object is being buffered and how getline is interacting with the buffer with a combination of fork. 
  * If we change main to a syscall only version, it works as expected with the fork

  ```
  int main() {
  	int fd = open("123.txt", O_RDONLY);
  	size_t cap = 20;
  	ssize_t br = 0;
  	char* buf = malloc(cap);
  	int linecount = 0;
  	while(1)  {
  		br = read( fd , buf, cap);
  		if (br == -1) break;
  		buf[br] = 0;
  		puts(buf);
  		if (br < cap) break;
  		h(fd);
  	}   
  	free(buf);
  	close(fd);
  	return 0; 
  } 
  ```
  * The `fread` version of the program terminates as expected.
  * If the fork call is not there -- the program executes as normal. 
  * If there is any call to `fseek(fd, 0, SEEK_CUR)` after the getline but before the fork, the issues goes away. Not with `ftell(fd)` though.
  * If there is the same check after the fork, the file is outputted twice and then the program terminates.
  * The issues also goes away if we set the file object to be unbuffered (`setvbuf(fd, NULL, _IONBF, 0);`) right after the file's opening
  * Stepping through gdb, I came to an infinite cycle in glibc/libio/genops.c#underflow and glibc/libio/iogetdelim.c#_IO_getdelim . When the last line of the file is reached, an underflow calculation is done in iogetdelim and an execution path sets the flags of the file to _IO_EOF_SEEN, which getline (seems) to ignore and thinking that the file cache is invalidated starts over. I don't know how much this will help.

  Found by: Yuxuan Ren <yren18 at illinois.edu>
  Confirmation Testing/Validation: Lawrence Angrave <angrave at illinois.edu>

To manage notifications about this bug go to:
https://bugs.launchpad.net/ubuntu/+source/glibc/+bug/1750173/+subscriptions



More information about the foundations-bugs mailing list