Subject: kern/34267: kevent(2): EVFILT_VNODE filter returns events upon denied link(2) of a directory
To: None <kern-bug-people@netbsd.org, gnats-admin@netbsd.org,>
From: None <rudolf@eq.cz>
List: netbsd-bugs
Date: 08/23/2006 13:10:00
>Number:         34267
>Category:       kern
>Synopsis:       kevent(2): EVFILT_VNODE filter returns events upon denied link(2) of a directory
>Confidential:   no
>Severity:       non-critical
>Priority:       low
>Responsible:    kern-bug-people
>State:          open
>Class:          sw-bug
>Submitter-Id:   net
>Arrival-Date:   Wed Aug 23 13:10:00 +0000 2006
>Originator:     rudolf
>Release:        4.0_BETA
>Organization:
>Environment:
NetBSD 4.0_BETA (GENERIC) Sat Aug 19 02:01:55 CEST 2006
>Description:
EVFILT_VNODE kqueue filter returns events upon denied link(2) of a directory, but according to manual (kevent(2)), the events should be returned only when the requested action succeeds:
``NOTE_WRITE     A write occurred on the file referenced by                   the descriptor.''
``NOTE_LINK      The link count on the file changed.''
Tested on ffs with NetBSD 3.99.21 (i386) and 4.0_BETA (amd64).

(the 'ln' of a directory is denied because ffs doesn't permit to use of link() on a directory ('man 2 ln':
``EPERM          The file named by name1 is a directory and the effective user ID is not super-user, or the file system containing the file does not permit the use of link() on a directory.''))

I also tested that the NOTE_LINK event is not returned when the watched vnode belongs to a regular file and the link is denied (EEXIST - 'ln /tmp/watchfile /tmp/watchfile').

>How-To-Repeat:
Here is my naive test code:

-- 8< --

#include <sys/event.h>
#include <fcntl.h>
#include <stdio.h>

#define WATCHVNODE "/tmp/watchdir"

int
main()
{
  int kq, ke, wvnfd;
  struct kevent eventlist[1], changelist[1];
  char *wvn = WATCHVNODE;
    
  wvnfd = open(wvn, O_RDONLY, 0);
  if (wvnfd < 0) {
    printf("error: open\n");
    return 111;
  }

  kq = kqueue();
  if (kq < 0) {
    printf("error: kqueue\n");
    return 111;
  }

  EV_SET(&eventlist[0], wvnfd, EVFILT_VNODE,
    EV_ADD | EV_ONESHOT, NOTE_DELETE |
    NOTE_WRITE | NOTE_EXTEND | NOTE_ATTRIB |
    NOTE_LINK | NOTE_RENAME | NOTE_REVOKE, 0, 0);
  ke = kevent(kq, eventlist, 1, NULL, 0, NULL);
  if (ke < 0) {
    printf("error: kevent\n");
    return 111;
  }

  printf("NOTE_DELETE\t%d\nNOTE_WRITE\t%d\n"
    "NOTE_EXTEND\t%d\nNOTE_ATTRIB\t%d\n"
    "NOTE_LINK\t%d\nNOTE_RENAME\t%d\nNOTE_REVOKE\t%d\n",
    NOTE_DELETE, NOTE_WRITE, NOTE_EXTEND,
    NOTE_ATTRIB, NOTE_LINK, NOTE_RENAME, NOTE_REVOKE);
  printf("waiting for kevent() ...\n");
  ke = kevent(kq, NULL, 0, changelist, 1, NULL);
  if (ke < 0) {
    printf("error: kevent\n");
    return 111;
  } else {
    printf("%d\n", (int)changelist[0].fflags);
  }
  return 0;
}

-- >8 --

make the directory:
$ mkdir /tmp/watchdir

run the binary on one terminal and try to make a hardlink on another one (directories "/tmp/w", "/tmp/watchdir/w" don't exist):

_#1_ - kevent returns NOTE_LINK:
(1)$ ./watchvnode
NOTE_DELETE     1
NOTE_WRITE      2
NOTE_EXTEND     4
NOTE_ATTRIB     8
NOTE_LINK       16
NOTE_RENAME     32
NOTE_REVOKE     64
waiting for kevent() ...
16

(2)$ ln /tmp/watchdir /tmp/w
ln: /tmp/w: Operation not permitted

_#2_ - kevent returns NOTE_WRITE and NOTE_LINK:
(1)$ ./watchvnode 
NOTE_DELETE     1
NOTE_WRITE      2
NOTE_EXTEND     4
NOTE_ATTRIB     8
NOTE_LINK       16
NOTE_RENAME     32
NOTE_REVOKE     64
waiting for kevent() ...
18

(2)$ ln /tmp/watchdir /tmp/watchdir/w
ln: /tmp/watchdir/w: Operation not permitted

>Fix: