Sample Application
To illustrate, the sample application we present here implements a basic access-control system, using eXtremeDB-KM to create and maintain the access-control database in the kernel space. The database maintains file-access rules, and the runtime provides drivers and user-level applications with high-performance access to the storage. The example code uses UNIX-like notations.
The application in Figure 2 contains three major components:
- The database kernel module, responsible for storage, maintains database access logic.
- A user-mode application that utilizes a user-mode database API.
- The "filter" or kernel module that intercepts filesystem calls and provides a file access authorization mechanism to the system.
The database kernel module implements kernel-mode data storage and provides the API to manipulate the data. The module is integrated with the eXtremeDB database runtime, which is responsible for providing "standard" database functionality such as transaction control, data access coordination and locking, lookup algorithms, and the like. Example 1 presents the data layout using eXtremeDB Data Definition Language syntax.
The class File describes a file object that is identified by the file's name, and the inode and device on which it is located. The rest of the fields (owner, defaccess, and acl vector) define file-access rules. The database maintains two hash-based indices that facilitate fast data access.
struct ACL { uint4 uid; // user id uint4 access; // access allowed for some user id }; class File { char<100> name; // file name uint4 inode; // file inode uint4 device; // device uint4 owner; // owner of the File uint4 defaccess; vector< ACL > acls; // access control lists hash < name > hname[4096]; hash < inode ,device > hfile[4096]; };
Because the database could grow large, the database pool is allocated in virtual memory. To use the allocated memory pool, it is mapped to the physical page (Examples 2 and 3). Once memory is allocated, the in-memory database is created and supports connections using standard database runtime functions.
/* allocate the database memory pool */ mem_ua_ptr = (char*)vmalloc( arg+PAGE_SIZE*2 ); if ( mem_ua_ptr == 0 ) { return -ENOMEM; /* error allocating memory */ }
/* calculate page aligned address */ mem_ptr = (char*) ( ((unsigned long)mem_ua_ptr+PAGE_SIZE-1) & PAGE_MASK ); /* lock pages */ for ( va=(unsigned long)mem_ptr; va<(unsigned long)(&(mem_ptr[mem_size/sizeof(int)])); va+=PAGE_SIZE) { SetPageReserved(vmalloc_to_page((void *)(((unsigned long)va)))); }
The module exports two types of interface: the "direct" API available to other kernel modules and drivers, and the "indirect" API that implements eXtremeDB-KM's ioctl interface to the database module. The direct API is not available for user-mode processes, but is efficient because it maintains only kernel-space references and eliminates expensive (in performance terms) translations from kernel-address space to user-address space. The ioctl interface provides user-mode applications with access to the kernel-mode database.
The user-level application creates a user-level database access API exposed by the database module via the ioctl interface. This API lets user-mode processes (such as administrative applications) interact with eXtremeDB-KM.
To facilitate the implementation of the "indirect" system call API, eXtremeDB-KM provides a simple "interface compiler" utility. This utility is similar to that of a standard remote procedure call compiler, except it generates the user/kernel mode interface files instead of remote access stubs. Developers define the API for C functions that the user-mode applications use to access the kernel-mode database. The eXtremeDB-KM interface compiler generates interface files that implement the user-to-kernel-mode interface through the generic ioctl function. In particular, the interface compiler generates stub files that should be linked with the user-mode applications and the kernel-mode stubs that are included into the kernel module (Figure 3). The generated files encapsulate the user-to-kernel-mode transition and hide ioctl-based implementation details from kernel modules and user-mode applications.
In contrast to other IDL implementations, the eXtremeDB-KM interface compiler accepts standard C header files to declare user-mode database access interfaces. The compiler recognizes a number of keywords in the form of comments to declare string, union, and array data types used as a part of the interface declaration. The IDL in Example 4 illustrates the concept.
#ifndef __EX_DB_API_H #define __EX_DB_API_H typedef struct tagFile { unsigned char result; char name[100]; unsigned int inode; unsigned int device; unsigned int owner; unsigned int defaccess; }File_t, *File_h; typedef char* zstring /*sero-terminated string*/; typedef unsigned int* pint; int exdb_init_database ( unsigned long mem_size ); int exdb_shutdown_database( ); int exdb_add_file (zstring file_name, unsigned int inode, unsigned int device, unsigned int owner, unsigned int defaccess ); int exdb_remove_file( zstring file_name ); int exdb_find_file( zstring file_name, /*out*/ pint pinode, /*out*/ pint pdevice, /*out*/ pint powner, /*out*/ pint pdefaccess ); int exdb_authorize_file( zstring file_name, unsigned int uid, unsigned int access ); #endif /* __EX_DB_API_H */
The interface compiler approach simplifies access to databases created in the context of a kernel module. The user-mode application code that implements database access is almost undistinguishable from that used by the kernel-mode application, with the exception of a simple initialization step (Example 5). There is no need for the user-mode application to serialize/deserialize function parameters, and similar technicalities. The application only needs to define and implement its database access interface, regardless of whether the interface is used inside or outside the kernel.
(a) if ( 0 != (i = exdb_find_file( name, &inode, &device, &owner, &access ))) { printf("exdb_find_file(\"%s\")==%d\n", name, i ); return (0); }; (b) if ( 0 != (i = exdb_find_file( name,&inode,&device,&owner,&access ))) { pr_info(DEV_NAME" exdb_find_file(\"%s\")==%d\n", name, i ); return (0); };
The third component of our sample applicationthe filter moduleintercepts calls to the filesystem and replaces standard file-access functions with its own, providing the user application with authorization to obtain the sought-after system resource. The implementation involves registering the custom module's file-access functions upon module initialization (Examples 6 and 7). In turn, these custom functions provide authentication. This is a standard technique used in numerous applications. However, the filter we present here benefits from using the database access API exposed by the eXtremeDB-KM-based database module to authenticate file access; see Example 8.
static int __init eACmod_init( void ) { if (!sys_call_table) { return -1; } if ( (major = register_chrdev( 0, DEV_NAME, &eACmod_fops )) < 0 ) { return -EIO; }; eACm_api_Init(); intercept_syscalls(); return 0; };
extern void *sys_call_table[]; typedef int (*syscall_t)(); extern int my_open(); extern int my_creat(); extern int my_chmod(); extern int my_chown(); extern int my_unlink(); extern int my_execve(); struct replace_syscall replace_syscall[]={ {INDEX_NR_open, __NR_open, (int(*)())0, my_open}, {INDEX_NR_creat, __NR_creat, (int(*)())0, my_creat}, {INDEX_NR_chmod, __NR_chmod, (int(*)())0, my_chmod}, {INDEX_NR_chown, __NR_chown, (int(*)())0, my_chown}, {INDEX_NR_unlink, __NR_unlink, (int(*)())0, my_unlink}, {INDEX_NR_execve, __NR_execve, (int(*)())0, my_execve}, {-1, -1, (int(*)())0, (int(*)())0} }; int nreplace_syscall = sizeof(replace_syscall)/sizeof(*replace_syscall)-1; void intercept_syscalls() { int i, f; for(i = 0; i < nreplace_syscall; i++) { f = replace_syscall[i].index; replace_syscall[i].original = sys_call_table[f]; sys_call_table[f] = replace_syscall[i].seos_syscall; } }
asmlinkage int my_open(const char* fname, int fmode, int mode) { int access, rv; /* some processing */ rv = replace_syscall[INDEX_NR_open].original(fname, fmode, mode); return rv; }