NetBSD-Bugs archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

kern/49687: Unexpected truncation of file occurs in open(2) when O_EXLOCK|O_TRUNC specified.



>Number:         49687
>Category:       kern
>Synopsis:       Unexpected truncation of file occurs in open(2) when O_EXLOCK|O_TRUNC specified.
>Confidential:   no
>Severity:       non-critical
>Priority:       medium
>Responsible:    kern-bug-people
>State:          open
>Class:          sw-bug
>Submitter-Id:   net
>Arrival-Date:   Mon Feb 23 08:25:00 +0000 2015
>Originator:     Hiroki Suenaga
>Release:        NetBSD-current(7.99.5)
>Organization:
Internet Initiative Japan Inc.
>Environment:
NetBSD netbsd2.vmware 7.99.5 NetBSD 7.99.5 (GENERIC) #0: Mon Feb 23 07:25:43 UTC 2015  hsuenaga@netbsd2.vmware:/usr/src/sys/arch/amd64/compile/GENERIC amd64
>Description:
If open(2) is called with both of O_EXLOCK(or O_SHLOCK) and O_TRUNC flags specified and it failed to obtain a lock, the file should not be truncated though O_EXLOCK and O_SHLOCK are non-standard extension, and there is no definite specifications. 

But, on current NetBSD system, the file is always truncated even if lock operation is failed. This behavior should be fixed.

>How-To-Repeat:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>

int
main(int argc, char *argv[])
{
        int fd0, fd1;
        char buf[] =  { 't',  'e',  's',  't', '\n'};
        char buf2[] = {'\0', '\0', '\0', '\0', '\0'};
        char *fname = "./file.txt";
        int flag = O_RDWR|O_TRUNC|O_EXLOCK|O_CREAT|O_NONBLOCK;

        if ((fd0 = open(fname, flag, 0644)) < 0) {
                perror("1st descriptor");
                exit(EXIT_FAILURE);
        }
        if (write(fd0, buf, sizeof(buf)) < 0) {
                perror("write");
                exit(EXIT_FAILURE);
        }
        if ((fd1 = open(fname, flag, 0644)) >= 0) {
                printf("O_EXLOCK is not working");
                exit(EXIT_FAILURE);
        }
        if (lseek(fd0, 0, SEEK_SET) < 0) {
                perror("lseek");
                exit(EXIT_FAILURE);
        }
        if (read(fd0, buf2, sizeof(buf2)) < 0) {
                perror("read");
                exit(EXIT_FAILURE);
        }
        if (memcmp(buf, buf2, sizeof(buf)) != 0) {
                printf("File is corrupted.\n");
                exit(EXIT_FAILURE);
        }
        printf("OK.\n");
        exit(EXIT_SUCCESS);
}

The open(2) of fd1 will be fail because a lock is already held by fd0. In this case, the file "file.txt" should not be truncated and it should keep the data 'test¥n.' But file.txt is truncated after open(2) of fd1.
>Fix:
OpenBSD has fixed the problem. Here is a patch that modified for NetBSD.

Index: vfs_syscalls.c
===================================================================
RCS file: /cvsroot/src/sys/kern/vfs_syscalls.c,v
retrieving revision 1.493
diff -u -w -p -r1.493 vfs_syscalls.c
--- vfs_syscalls.c      15 Feb 2015 10:48:21 -0000      1.493
+++ vfs_syscalls.c      23 Feb 2015 08:16:59 -0000
@@ -1553,8 +1553,10 @@ do_open(lwp_t *l, struct vnode *dvp, str
        struct cwdinfo *cwdi = p->p_cwdi;
        file_t *fp;
        struct vnode *vp;
+       struct vattr vattr;
        int flags, cmode;
        int indx, error;
+       int localtrunc = 0;
        struct nameidata nd;
 
        if (open_flags & O_SEARCH) {
@@ -1564,6 +1566,10 @@ do_open(lwp_t *l, struct vnode *dvp, str
        flags = FFLAGS(open_flags);
        if ((flags & (FREAD | FWRITE)) == 0)
                return EINVAL;
+       if ((flags & O_TRUNC) && (flags & (O_EXLOCK|O_SHLOCK))) {
+               localtrunc = 1;
+               flags &= ~O_TRUNC;
+       }
 
        if ((error = fd_allocfile(&fp, &indx)) != 0) {
                return error;
@@ -1596,6 +1602,25 @@ do_open(lwp_t *l, struct vnode *dvp, str
        if ((error = open_setfp(l, fp, vp, indx, flags)))
                return error;
 
+       if (localtrunc) {
+               if ((fp->f_flag & FWRITE) == 0)
+                       error = EACCES;
+               else if (vp->v_mount->mnt_flag & MNT_RDONLY)
+                       error = EROFS;
+               else if (vp->v_type == VDIR)
+                       error = EISDIR;
+               else if ((error = vn_writechk(vp)) == 0) {
+                       vattr_null(&vattr);
+                       vattr.va_size = 0;
+                       error = VOP_SETATTR(vp, &vattr, fp->f_cred);
+               }
+               if (error) {
+                       VOP_UNLOCK(vp);
+                       closef(fp);
+                       return error;
+               }
+       }
+
        VOP_UNLOCK(vp);
        *fd = indx;
        fd_affix(p, fp, indx);



Home | Main Index | Thread Index | Old Index