Errno: Difference between revisions

From Hackepedia
Jump to navigationJump to search
mNo edit summary
mNo edit summary
Line 1: Line 1:
When a system call fails inside a [[process]], the system sets a variable called '''errno''' in the process with a certain integer.  Usually a subsequent error message will contain the human readable string associated with the errno.  Ie.  if you do:
When a [[syscall|system call]] fails inside a [[process]], the system sets a variable called '''errno''' in the process with a certain integer.  Usually a subsequent error message will contain the human readable string associated with the errno.  Ie.  if you do:


  $ cat /dev/pf
  $ cat /dev/pf
  cat: /dev/pf: Permission denied
  cat: /dev/pf: Permission denied


You get the string "Permission denied".  If you cross reference this string in the errno(2) or intro(2) manpage you'll see that this belongs to errno 13:
You get the string "Permission denied".  If you cross reference this string in the errno(2) or intro(2) [[manual|manpage]] you'll see that this belongs to errno 13:
      
      
       ...
       ...

Revision as of 13:42, 3 December 2005

When a system call fails inside a process, the system sets a variable called errno in the process with a certain integer. Usually a subsequent error message will contain the human readable string associated with the errno. Ie. if you do:

$ cat /dev/pf
cat: /dev/pf: Permission denied

You get the string "Permission denied". If you cross reference this string in the errno(2) or intro(2) manpage you'll see that this belongs to errno 13:

     ...
     13 EACCES Permission denied.  An attempt was made to access a file in a
           way forbidden by its file access permissions.
     ...

Great! We now know that the errno that cat failed with was errno 13 (EACCES) also known as Permission denied. Better check who you are!


A Journey into the Userland Source Code

So how does that help anyone? Well you can now use grep on the systems source code (you do have source code don't you? Don't leave home without it) to track down what location of the source code returns with EACCES. So let's go and do it here is an example run:

$ cd /usr/src/bin/cat
$ ls
CVS      Makefile cat.1    cat.c    cat.cat1 obj
$ grep -4 open\( cat.c
       do {
               if (*argv) {
                       if (!strcmp(*argv, "-"))
                               fp = stdin;
                       else if ((fp = fopen(*argv, "r")) == NULL) {
                               warn("%s", *argv);
                               rval = 1;
                               ++argv;
                               continue;
--
       do {
               if (*argv) {
                       if (!strcmp(*argv, "-"))
                               fd = fileno(stdin);
                       else if ((fd = open(*argv, O_RDONLY, 0)) < 0) {
                               warn("%s", *argv);
                               rval = 1;
                               ++argv;
                               continue;

OK I knew that we were failing on the open syscall (out of experience) and it is actually wrapped by fopen() which then calls open(), when fopen() fails by returning NULL it is warn() that prints the filename that was attempted to be opened and if we were to dig deeper in warn() we would see that it displays the errno string with strerror(), watch:

$ cd /usr/src/lib/libc/gen
$ grep -l warn\( *
auth_subr.c
authenticate.c
devname.c
getnetgrent.c
vwarn.c
warn.c
$ more warn.c
...
void
_warn(const char *fmt, ...)
{
       va_list ap;
...
       va_start(ap, fmt);
       _vwarn(fmt, ap);
       va_end(ap);
}
...
__weak_alias(warn, _warn);
...

Ok so we've found warn() and it calls _vwarn(), so we'll have to dig deeper (hey noone said this was going to be a breeze).

$ more vwarn.c
...
_vwarn(const char *fmt, va_list ap)
{
       int sverrno;
...
       sverrno = errno;
       (void)fprintf(stderr, "%s: ", __progname);
       if (fmt != NULL) {
               (void)vfprintf(stderr, fmt, ap);
               (void)fprintf(stderr, ": ");
       }
       (void)fprintf(stderr, "%s\n", strerror(sverrno));
} 

Bingo! _vwarn() prints the program name followed by a colon and then prints the argument followed with a colon and then strerror() of the errno followed by a newline. So we know how the error message is created and how it is tied in with the errno variable.

Does this look like our error message?

cat: /dev/pf: Permission denied

Yes it does. The source code matches reality (a good thing).

Wandering into the Kernel

How does the errno come from the kernel though? Remember it was the open syscall right? So we'll dig in the kernel a little:

$ cd /usr/src/sys/kern
$ grep -l open\( *
kern_acct.c
kern_descrip.c
kern_ktrace.c
kern_lkm.c
kern_sig.c
makesyscalls.sh
subr_log.c
subr_prf.c
syscalls.master
tty.c
tty_conf.c
tty_pty.c
tty_tb.c
tty_tty.c
vfs_syscalls.c
vfs_vnops.c

Whew! lots of files, which one is it? Let's just check them all if we don't know or I'm going to make a lucky guess here:

$ grep open\( vfs_syscalls.c
sys_open(struct proc *p, void *v, register_t *retval)
       if ((error = vn_open(&nd, flags, cmode)) != 0) {
                       dupfdopen(fdp, indx, p->p_dupfd, flags, error)) == 0) {
sys_fhopen(struct proc *p, void *v, register_t *retval)

Looks good! sys_open() is the function we're looking for. So let's take a look at it:

...
/*
 * Check permissions, allocate an open file structure,
 * and call the device open routine if any.
 */
int
sys_open(struct proc *p, void *v, register_t *retval)
{
...

Looking at it we don't see any mention of EACCES, but there is return conditions on other functions that return with that error, so let's dig into all the functions:

       ...
       if ((error = vn_open(&nd, flags, cmode)) != 0) {
               if ((error == ENODEV || error == ENXIO) &&
                   p->p_dupfd >= 0 &&                  /* XXX from fdopen */
                   (error =
                       dupfdopen(fdp, indx, p->p_dupfd, flags, error)) == 0) {
                       closef(fp, p);
                       *retval = indx;
                       goto out;
               }
               if (error == ERESTART)
                       error = EINTR;
               fdremove(fdp, indx);
               closef(fp, p);
               goto out;
       }
       ...

if vn_open() doesn't return with 0, it sets the error with the return value and goes to 'out.

...
out:
       fdpunlock(fdp);
       return (error);
...

So what does vn_open() look like? Can we find the EACCES in it? It's found in the file /usr/src/sys/kern/vfs_vnops.c in the same directory (we found it with grep) and at first glance it does not show any obvious returns with EACCES, so we dig further. This line looks interesting:

/*
 * Common code for vnode open operations.
 * Check permissions, and call the VOP_OPEN or VOP_CREATE routine.
 */
int
vn_open(struct nameidata *ndp, int fmode, int cmode)
{
...
       if ((fmode & O_CREAT) == 0) {
               if (fmode & FREAD) {
                       if ((error = VOP_ACCESS(vp, VREAD, cred, p)) != 0)
                               goto bad;
...

We're not creating a file, and we are reading the file so VOP_ACCESS() in /usr/src/sys/kern/vnode_if.c is going to determine whether we can access this file or not.

int VOP_ACCESS(vp, mode, cred, p)
       struct vnode *vp;
       int mode;
       struct ucred *cred;
       struct proc *p;
{
...
       return (VCALL(vp, VOFFSET(vop_access), &a));
}


VOP_ACCESS() returns only what VCALL() returns so we look at that.

sys/vnode.h:#define VCALL(VP,OFF,AP) VOCALL((VP)->v_op,(OFF),(AP))

VCALL() is a macro for VOCALL(), (hey noone said this was going to be easy!).

sys/vnode.h:#define VOCALL(OPSV,OFF,AP) (( *((OPSV)[(OFF)])) (AP))

It's a macro as well so we are going to function *((vp->v_op)[VOFFSET(vop_access)])(&a) ... so a pointer to a function. Let's pretend it points to ufs_access() in /usr/src/sys/ufs/ufs/ufs_vnops.c:

int
ufs_access(void *v)
{
...
       return (vaccess(ip->i_ffs_mode, ip->i_ffs_uid, ip->i_ffs_gid,
                                       mode, ap->a_cred));
}

Looks like it returns what vaccess() returns, we found this in /usr/src/sys/kern/vfs_subr.c:

/*
 * Do the usual access checking.
 * file_mode, uid and gid are from the vnode in question,
 * while acc_mode and cred are from the VOP_ACCESS parameter list
 */
int
vaccess(mode_t file_mode, uid_t uid, gid_t gid, mode_t acc_mode,
   struct ucred *cred)
...
         return (file_mode & mask) == mask ? 0 : EACCES;
...
               return (file_mode & mask) == mask ? 0 : EACCES;
...
                return (file_mode & mask) == mask ? 0 : EACCES;
...
       return (file_mode & mask) == mask ? 0 : EACCES;


Looks like it can return EACCES at many spots, seems like the function vaccess() determines whether we have permissions or not return either 0 or EACCES.

So when all these functions return in the end sys_open() will also return with EACCES. It's an almost crazy foxhunt but we located it.

So now you should know where errno errors originate (the kernel), and how they are treated in userland to provide you with an error message.