diff -Naur ext3/inode.c ext3sd/inode.c --- ext3/inode.c 2006-10-28 15:59:40.000000000 -0400 +++ ext3sd/inode.c 2006-10-28 16:00:00.000000000 -0400 @@ -36,6 +36,7 @@ #include #include #include +#include #include "xattr.h" #include "acl.h" @@ -192,6 +193,13 @@ if (is_bad_inode(inode)) goto no_delete; +#ifdef CONFIG_EXT3_FS_SECDEL + if (EXT3_I(inode)->i_flags & EXT3_SECRM_FL) { + if (ext3_secdel_inode(inode)) + goto no_delete; + } +#endif + handle = start_transaction(inode); if (IS_ERR(handle)) { /* If we're going to skip the normal cleanup, we still @@ -802,6 +810,295 @@ return ret; } +#ifdef CONFIG_EXT3_FS_SECDEL +static int ext3_ovwt_blocks(handle_t *handle, struct inode *inode, char param, + sector_t first, sector_t last) +{ + int retval = 0; + sector_t i; + sector_t j; + /* TODO: Change batch number to something better? */ + sector_t batch = 4; + struct buffer_head *bh = NULL; + struct buffer_head tmp; + struct super_block *sb; + + J_ASSERT(handle != NULL); + + tmp.b_state = 0; + tmp.b_blocknr = 0; + sb = inode->i_sb; + + J_ASSERT( last >= first ); + + if (IS_ERR(handle)) { + retval = PTR_ERR(handle); + goto out; + } + + if ( last - first < batch) + batch = last - first; + + j = first; +next_batch: + if (ext3_should_journal_data(inode)) { + retval = ext3_journal_extend(handle, batch); + if (retval) + retval = ext3_journal_restart(handle, batch); + if (retval) + goto out; + } + + /* Are we at the end? */ + if (batch > last - j) + batch = last - j; + + for (i = 0; i < batch; i++, j++) { + ext3_get_block(inode, j, &tmp, 0); + bh = sb_getblk(sb, tmp.b_blocknr); + + if (ext3_should_journal_data(inode)) { + retval = ext3_journal_get_write_access(handle, bh); + if (retval) + goto out; + } + + lock_buffer(bh); + if (param == 'R') + get_random_bytes(bh->b_data, bh->b_size); + else + memset(bh->b_data, param, bh->b_size); + unlock_buffer(bh); + + if (ext3_should_journal_data(inode)) { + retval = ext3_journal_dirty_metadata(handle, bh); + if (retval) + goto out; + } + else + mark_buffer_dirty(bh); + + brelse(bh); + } + if ( j < last) + goto next_batch; + bh = NULL; +out: + if (retval) + ext3_journal_abort_handle(NULL, NULL, bh, handle, retval); + if (bh) + brelse(bh); + return retval; +} + +static int ext3_ovwt_metadata(handle_t *handle, struct inode *inode, char param) +{ + int retval = 0; + int param_int = 0; + + if (param == 'R') + get_random_bytes( (char *)¶m_int, sizeof(int)); + else + param_int = param | param<<8 | param<<16 | param<<24; + + inode->i_uid = param_int; + inode->i_gid = param_int; + inode->i_atime.tv_sec = param_int; + inode->i_atime.tv_nsec = param_int; + inode->i_mtime.tv_sec = param_int; + inode->i_mtime.tv_nsec = param_int; + inode->i_ctime.tv_sec = param_int; + inode->i_ctime.tv_nsec = param_int; + + return retval; +} + +static +int ext3_secure_truncate(struct inode *inode, sector_t first, sector_t last) +{ + + struct ext3_sb_info *sbi = EXT3_SB(inode->i_sb); + int err = 0; + int num_ovwt = 0; + int char_pos = 0; + handle_t *handle; + + /* Copy all secure deletion options so I don't have to dereference + * every time. */ + int data_num = sbi->s_data_num_ovwt; + int data_size = sbi->s_data_char_size; + char *data_c = sbi->s_data_char; + + /* I'm adding the inode to the orphan in a dirty way (making it a + * transaction of it's own), since I don't want to add it inside the + * loop. Normally I should put it within the first transaction since + * I'll probably have a performance hit if I keep it here. + */ + handle = start_transaction(inode); + + if (IS_ERR(handle)) { + err = PTR_ERR(handle); + goto out; + } + /* + * Ok start secure truncation. Add the inode to the orhpan list + * for any possible crashes during the operation. + */ + if ( ext3_orphan_add(handle, inode) ) + goto out; + + ext3_journal_stop(handle); + + handle = NULL; + +next_ovwt: + char_pos = 0; + while ( char_pos < data_size ) { + + handle = start_transaction(inode); + + if (IS_ERR(handle)) { + err = PTR_ERR(handle); + goto del_orphan_out; + } + if ( char_pos < data_size && num_ovwt < data_num) { + err = ext3_ovwt_blocks(handle, inode, + *(data_c + char_pos), first, last); + if (err) + goto stop_out; + } + + ext3_journal_stop(handle); + + if ( !(char_pos == data_size -1 && num_ovwt == data_num -1) ) { + journal_lock_updates(EXT3_SB(inode->i_sb)->s_journal); + err = journal_flush(EXT3_SB(inode->i_sb)->s_journal); + journal_unlock_updates(EXT3_SB(inode->i_sb)->s_journal); + if (err) + goto del_orphan_out; + err = sync_blockdev(inode->i_sb->s_bdev); + if (err) + goto del_orphan_out; + + } + char_pos++; + } + num_ovwt++; + + if (num_ovwt < data_num) + goto next_ovwt; + return err; +stop_out: + ext3_journal_stop(handle); +del_orphan_out: + ext3_orphan_del(handle, inode); +out: + return err; +} + +int ext3_secdel_inode(struct inode *inode) +{ + + struct ext3_sb_info *sbi = EXT3_SB(inode->i_sb); + int err = 0; + int num_ovwt = 0; + int char_pos = 0; + handle_t *handle; + sector_t last_block; + + /* + * Copy all secure deletion options so I don't have to dereference + * every time. + */ + int data_num = sbi->s_data_num_ovwt; + int meta_num = sbi->s_meta_num_ovwt; + int data_size = sbi->s_data_char_size; + int meta_size = sbi->s_meta_char_size; + char *data_c = sbi->s_data_char; + char *meta_c = sbi->s_meta_char; + last_block = (inode->i_size + inode->i_blksize-1 ) + >> EXT3_BLOCK_SIZE_BITS(inode->i_sb); + + /* I'm adding the inode to the orphan in a dirty way (making it a + * transaction of it's own), since I don't want to add it inside the + * loop. Normally I should put it within the first transaction since + * I'll probably have a performance hit if I keep it here. + */ + handle = start_transaction(inode); + + if (IS_ERR(handle)) { + err = PTR_ERR(handle); + goto out; + } + + /* + * We need add to the inode to the orphan list. We want the + * block overwrites and the truncation to be an atomic operation, so + * we will remove the inode from the orphan list only after the + * truncation has sucessfully finished. + */ + if ( ext3_orphan_add(handle, inode) ) + goto out; + + ext3_journal_stop(handle); + + handle = NULL; + + +next_ovwt: + char_pos = 0; + while ( char_pos < data_size || + char_pos < meta_size ) { + + handle = start_transaction(inode); + + if (IS_ERR(handle)) { + err = PTR_ERR(handle); + goto del_orphan_out; + } + if ( char_pos < data_size && num_ovwt < data_num) { + err = ext3_ovwt_blocks(handle, inode, + *(data_c + char_pos), 0, last_block); + if (err) + goto stop_out; + } + if ( char_pos < meta_size && num_ovwt < meta_num) { + err = ext3_ovwt_metadata(handle, inode, + *(meta_c + char_pos)); + if (err) + goto stop_out; + mark_inode_dirty(inode); + } + + ext3_journal_stop(handle); + + if ( !(char_pos == data_size -1 && num_ovwt == data_num -1) ) { + journal_lock_updates(EXT3_SB(inode->i_sb)->s_journal); + err = journal_flush(EXT3_SB(inode->i_sb)->s_journal); + journal_unlock_updates(EXT3_SB(inode->i_sb)->s_journal); + if (err) + goto del_orphan_out; + err = sync_blockdev(inode->i_sb->s_bdev); + if (err) + goto del_orphan_out; + } + char_pos++; + } + num_ovwt++; + + if (num_ovwt < data_num || num_ovwt < meta_num) + goto next_ovwt; + return err; +stop_out: + ext3_journal_stop(handle); +del_orphan_out: + ext3_orphan_del(handle,inode); +out: + return err; + +} +#endif + #define DIO_CREDITS (EXT3_RESERVE_TRANS_BLOCKS + 32) static int @@ -1432,6 +1729,7 @@ static int ext3_invalidatepage(struct page *page, unsigned long offset) { + int err = 0; journal_t *journal = EXT3_JOURNAL(page->mapping->host); /* @@ -1440,7 +1738,8 @@ if (offset == 0) ClearPageChecked(page); - return journal_invalidatepage(journal, page, offset); + err = journal_invalidatepage(journal, page, offset); + return err; } static int ext3_releasepage(struct page *page, gfp_t wait) @@ -2137,6 +2436,14 @@ goto out_stop; /* error */ /* + * If secure deletion is compiled in the file-system, we have + * already added the inode to the orphan list because we need to + * make the overwrites and the truncation an atomic operation. + * Otherwise under and system crash our inode will not be correctly + * deleted. + */ +#ifndef CONFIG_EXT3_FS_SECDEL + /* * OK. This truncate is going to happen. We add the inode to the * orphan list, so that if this truncate spans multiple transactions, * and we crash, we will resume the truncate when the filesystem @@ -2147,6 +2454,7 @@ */ if (ext3_orphan_add(handle, inode)) goto out_stop; +#endif /* * The orphan list entry will now protect us from any crash which @@ -2763,6 +3071,11 @@ { struct inode *inode = dentry->d_inode; int error, rc = 0; + handle_t *handle; +#ifdef CONFIG_EXT3_FS_SECDEL + sector_t first; + sector_t last; +#endif const unsigned int ia_valid = attr->ia_valid; error = inode_change_ok(inode, attr); @@ -2798,7 +3111,22 @@ if (S_ISREG(inode->i_mode) && attr->ia_valid & ATTR_SIZE && attr->ia_size < inode->i_size) { - handle_t *handle; + +#ifdef CONFIG_EXT3_FS_SECDEL + /* Get range of blocks that will be freed and secure delete + * if necessary. */ + first = (attr->ia_size + inode->i_blksize-1) + >> EXT3_BLOCK_SIZE_BITS(inode->i_sb); + last = (inode->i_size + inode->i_blksize-1) + >> EXT3_BLOCK_SIZE_BITS(inode->i_sb); + + if (EXT3_I(inode)->i_flags & EXT3_SECRM_FL) { + error = ext3_secure_truncate(inode, first, last); + if (error) + goto err_out; + } +#endif + handle = ext3_journal_start(inode, 3); if (IS_ERR(handle)) { diff -Naur ext3/namei.c ext3sd/namei.c --- ext3/namei.c 2006-10-28 15:59:40.000000000 -0400 +++ ext3sd/namei.c 2006-10-28 16:00:00.000000000 -0400 @@ -36,6 +36,7 @@ #include #include #include +#include #include "namei.h" #include "xattr.h" @@ -2053,6 +2054,86 @@ return retval; } +#ifdef CONFIG_EXT3_FS_SECDEL +static int ext3_ovwt_dentry(handle_t *handle, struct buffer_head *bh, + struct ext3_dir_entry_2 *de, char param) +{ + int retval = 0; + get_bh(bh); + + if ( (retval = ext3_journal_get_write_access(handle, bh)) ) + goto out; + + if (test_set_buffer_locked(bh)) + goto out; + + if (param == 'R') + get_random_bytes(de->name, de->name_len); + else + memset(de->name, param, de->name_len); + unlock_buffer(bh); + + retval = ext3_journal_dirty_data(handle, bh); +out: + return retval; +} + +static int ext3_secdel_dentry(struct inode *inode, struct buffer_head *bh, + struct ext3_dir_entry_2 *de) +{ + struct ext3_sb_info *sbi = EXT3_SB(inode->i_sb); + int err = 0; + int num_ovwt = 0; + int char_pos = 0; + handle_t *handle; + + /* Copy secure deletion options for the filename so I don't have to + * dereference every time. */ + int file_num = sbi->s_file_num_ovwt; + int file_size = sbi->s_file_char_size; + char *file_c = sbi->s_file_char; + +next_ovwt: + char_pos = 0; + while ( char_pos < file_size ) { + + handle = ext3_journal_start(inode, 1); + + if (IS_ERR(handle)) { + err = PTR_ERR(handle); + goto out; + } + + err = ext3_ovwt_dentry(handle, bh, de, *(file_c + char_pos)); + if (err) + goto stop_out; + + ext3_journal_stop(handle); + + /* TODO: I don't think we need to explicitly flush the + * journal. Since each transaction is atomic, the jbd + * will take care of checkpointing the journal. Should + * check this out. If we can we should get rid of this, + * since it might be a performance hit for no reason. */ + journal_lock_updates(EXT3_SB(inode->i_sb)->s_journal); + err = journal_flush(EXT3_SB(inode->i_sb)->s_journal); + journal_unlock_updates(EXT3_SB(inode->i_sb)->s_journal); + if (err) + goto out; + char_pos++; + } + num_ovwt++; + + if (num_ovwt < file_num) + goto next_ovwt; + return err; +stop_out: + ext3_journal_stop(handle); +out: + return err; +} +#endif + static int ext3_unlink(struct inode * dir, struct dentry *dentry) { int retval; @@ -2064,23 +2145,17 @@ /* Initialize quotas before so that eventual writes go * in separate transaction */ DQUOT_INIT(dentry->d_inode); - handle = ext3_journal_start(dir, EXT3_DELETE_TRANS_BLOCKS(dir->i_sb)); - if (IS_ERR(handle)) - return PTR_ERR(handle); - - if (IS_DIRSYNC(dir)) - handle->h_sync = 1; - + retval = -ENOENT; bh = ext3_find_entry (dentry, &de); if (!bh) - goto end_unlink; + goto end_nostop; inode = dentry->d_inode; retval = -EIO; if (le32_to_cpu(de->inode) != inode->i_ino) - goto end_unlink; + goto end_nostop; if (!inode->i_nlink) { ext3_warning (inode->i_sb, "ext3_unlink", @@ -2088,6 +2163,20 @@ inode->i_ino, inode->i_nlink); inode->i_nlink = 1; } +#ifdef CONFIG_EXT3_FS_SECDEL + if (EXT3_I(dentry->d_inode)->i_flags & EXT3_SECRM_FL) { + retval = ext3_secdel_dentry(inode, bh, de); + if (retval) + goto end_nostop; + } +#endif + handle = ext3_journal_start(dir, EXT3_DELETE_TRANS_BLOCKS(dir->i_sb)); + if (IS_ERR(handle)) + return PTR_ERR(handle); + + if (IS_DIRSYNC(dir)) + handle->h_sync = 1; + retval = ext3_delete_entry(handle, dir, de, bh); if (retval) goto end_unlink; @@ -2103,6 +2192,7 @@ end_unlink: ext3_journal_stop(handle); +end_nostop: brelse (bh); return retval; } diff -Naur ext3/super.c ext3sd/super.c --- ext3/super.c 2006-10-28 15:59:40.000000000 -0400 +++ ext3sd/super.c 2006-10-28 16:00:00.000000000 -0400 @@ -42,6 +42,7 @@ #include "xattr.h" #include "acl.h" #include "namei.h" +#include "tb.h" static int ext3_load_journal(struct super_block *, struct ext3_super_block *, unsigned long journal_devnum); @@ -62,6 +63,10 @@ static void ext3_unlockfs(struct super_block *sb); static void ext3_write_super (struct super_block * sb); static void ext3_write_super_lockfs(struct super_block *sb); +#ifdef CONFIG_EXT3_FS_SECDEL +static int ext3_store_secdel_options(struct ext3_sb_info *sbi, char *args); +static void ext3_secdel_put_super(struct ext3_sb_info *sbi); +#endif /* * Wrappers for journal_start/end. @@ -412,6 +417,10 @@ kfree(sbi->s_qf_names[i]); #endif +#ifdef CONFIG_EXT3_FS_SECDEL + ext3_secdel_put_super(sbi); +#endif + /* Debugging code just in case the in-memory inode orphan list * isn't empty. The on-disk one can be non-empty if we've * detected an error and taken the fs readonly, but the @@ -634,7 +643,7 @@ Opt_usrjquota, Opt_grpjquota, Opt_offusrjquota, Opt_offgrpjquota, Opt_jqfmt_vfsold, Opt_jqfmt_vfsv0, Opt_quota, Opt_noquota, Opt_ignore, Opt_barrier, Opt_err, Opt_resize, Opt_usrquota, - Opt_grpquota + Opt_grpquota, Opt_secdel }; static match_table_t tokens = { @@ -672,6 +681,7 @@ {Opt_data_journal, "data=journal"}, {Opt_data_ordered, "data=ordered"}, {Opt_data_writeback, "data=writeback"}, + {Opt_secdel, "secdel=%s"}, {Opt_offusrjquota, "usrjquota="}, {Opt_usrjquota, "usrjquota=%s"}, {Opt_offgrpjquota, "grpjquota="}, @@ -720,6 +730,9 @@ int qtype; char *qname; #endif +#ifdef CONFIG_EXT3_FS_SECDEL + char *sd_params; +#endif if (!options) return 1; @@ -1011,6 +1024,31 @@ case Opt_nobh: set_opt(sbi->s_mount_opt, NOBH); break; +#ifdef CONFIG_EXT3_FS_SECDEL + case Opt_secdel: + sd_params = match_strdup(&args[0]); + if (!sd_params) { + printk(KERN_ERR + "EXT3-fs: not enough memory for " + "storing secure deletion options.\n"); + return 0; + } + if (ext3_store_secdel_options(sbi, sd_params)) { + printk(KERN_ERR + "EXT3-fs: invalid option(s) for " + "secure deletion.\n"); + kfree(sd_params); + return 0; + } + kfree(sd_params); + break; +#else + case Opt_secdel: + printk(KERN_ERR + "EXT3-fs: secure deletion option not " + "supported.\n"); + break; +#endif default: printk (KERN_ERR "EXT3-fs: Unrecognized mount option \"%s\" " @@ -1257,6 +1295,10 @@ __FUNCTION__, inode->i_ino, inode->i_size); jbd_debug(2, "truncating inode %ld to %Ld bytes\n", inode->i_ino, inode->i_size); +#ifdef CONFIG_EXT3_FS_SECDEL + if (EXT3_I(inode)->i_flags & EXT3_SECRM_FL) + ext3_secdel_inode(inode); +#endif ext3_truncate(inode); nr_truncates++; } else { @@ -1423,6 +1465,13 @@ sbi->s_resuid = le16_to_cpu(es->s_def_resuid); sbi->s_resgid = le16_to_cpu(es->s_def_resgid); +#ifdef CONFIG_EXT3_FS_SECDEL + /* Set default options for secure deletion. */ + sbi->s_data_num_ovwt = 0; + sbi->s_meta_num_ovwt = 0; + sbi->s_file_num_ovwt = 0; +#endif + set_opt(sbi->s_mount_opt, RESERVATION); if (!parse_options ((char *) data, sb, &journal_inum, &journal_devnum, @@ -2650,6 +2699,200 @@ #endif +#ifdef CONFIG_EXT3_FS_SECDEL +enum { + opt_ovwt, opt_chars, opt_data_ovwt, opt_data_chars, opt_meta_ovwt, + opt_meta_chars, opt_file_ovwt, opt_file_chars, opt_err, +}; + +static match_table_t sd_tokens = { + {opt_ovwt, "all_ovwt=%u"}, + {opt_chars, "all_chars=%s"}, + {opt_data_ovwt, "data_ovwt=%u"}, + {opt_data_chars, "data_chars=%s"}, + {opt_meta_ovwt, "meta_ovwt=%u"}, + {opt_meta_chars, "meta_chars=%s"}, + {opt_file_ovwt, "file_ovwt=%u"}, + {opt_file_chars, "file_chars=%s"}, + {opt_err, NULL}, + +}; + +static int ext3_store_secdel_options(struct ext3_sb_info *sbi, char *options) +{ + int err = 0; + u32 num_ovwt = 0; + u32 data_ovwt = 0; + u32 meta_ovwt = 0; + u32 file_ovwt = 0; + char *ovwt_chars = NULL; + char *data_chars = NULL; + char *meta_chars = NULL; + char *file_chars = NULL; + char * p; + substring_t args[MAX_OPT_ARGS]; + + if (!options) + return 1; + while ((p = strsep (&options, ";")) != NULL) { + int token; + if (!*p) + continue; + + token = match_token(p, sd_tokens, args); + switch (token) { + case opt_ovwt: + if (match_int(&args[0], &num_ovwt)) { + printk("EXT3-fs: Invalid argument to " + "all_ovwt.\n"); + err = -EINVAL; + goto err_out; + } + break; + case opt_chars: + ovwt_chars = match_strdup(&args[0]); + if (!ovwt_chars) { + printk("EXT3-fs: Not enough memory to set " + "secure deletion option.\n"); + err = -ENOMEM; + goto err_out; + } + break; + case opt_data_ovwt: + if (match_int(&args[0], &data_ovwt)) { + printk("EXT3-fs: Invalid argument to " + "data_ovwt.\n"); + err = -EINVAL; + goto err_out; + + } + break; + case opt_data_chars: + data_chars = match_strdup(&args[0]); + if (!data_chars) { + printk("EXT3-fs: Not enough memory to set " + "secure deletion option.\n"); + err = -ENOMEM; + goto err_out; + } + break; + case opt_meta_ovwt: + if (match_int(&args[0], &meta_ovwt)) { + printk("EXT3-fs: Invalid argument to " + "meta_ovwt.\n"); + err = -EINVAL; + goto err_out; + + } + break; + case opt_meta_chars: + meta_chars = match_strdup(&args[0]); + if (!meta_chars) { + printk("EXT3-fs: Not enough memory to set " + "secure deletion option.\n"); + err = -ENOMEM; + goto err_out; + } + break; + case opt_file_ovwt: + if (match_int(&args[0], &file_ovwt)) { + printk("EXT3-fs: Invalid argument to " + "file_ovwt.\n"); + err = -EINVAL; + goto err_out; + + } + break; + case opt_file_chars: + file_chars = match_strdup(&args[0]); + if (!file_chars) { + printk("EXT3-fs: Not enough memory to set " + "secure deletion option.\n"); + err = -ENOMEM; + goto err_out; + } + break; + default: + err = -EINVAL; + goto err_out; + } + } + + /* Now populate the sbi */ + if (data_ovwt) + sbi->s_data_num_ovwt = data_ovwt; + else + sbi->s_data_num_ovwt = num_ovwt; + + if (meta_ovwt) + sbi->s_meta_num_ovwt = meta_ovwt; + else + sbi->s_meta_num_ovwt = num_ovwt; + + if (file_ovwt) + sbi->s_file_num_ovwt = file_ovwt; + else + sbi->s_file_num_ovwt = num_ovwt; + + if (data_chars) { + sbi->s_data_char = data_chars; + sbi->s_data_char_size = strlen(data_chars); + } + else if (ovwt_chars) { + sbi->s_data_char = ovwt_chars; + sbi->s_data_char_size = strlen(ovwt_chars); + } + + if (meta_chars) { + sbi->s_meta_char = meta_chars; + sbi->s_meta_char_size = strlen(meta_chars); + } + else if (ovwt_chars) { + sbi->s_meta_char = ovwt_chars; + sbi->s_meta_char_size = strlen(ovwt_chars); + } + + if (file_chars) { + sbi->s_file_char = file_chars; + sbi->s_file_char_size = strlen(file_chars); + } + else if (ovwt_chars) { + sbi->s_file_char = ovwt_chars; + sbi->s_file_char_size = strlen(ovwt_chars); + } + + /* If all fields are individually allocated we don't need + * ovwt_chars anymore */ + if (data_chars && meta_chars && file_chars && data_chars) + kfree(ovwt_chars); + + return err; + +err_out: + if (ovwt_chars) + kfree(ovwt_chars); + if (data_chars) + kfree(data_chars); + if (meta_chars) + kfree(meta_chars); + if (file_chars) + kfree(file_chars); +out: + return err; +} + +static void ext3_secdel_put_super(struct ext3_sb_info *sbi) +{ + if (sbi->s_data_char) + kfree(sbi->s_data_char); + if (sbi->s_meta_char && sbi->s_data_char != sbi->s_meta_char) + kfree(sbi->s_meta_char); + if (sbi->s_file_char && sbi->s_data_char != sbi->s_file_char && + sbi->s_meta_char != sbi->s_file_char) + kfree(sbi->s_file_char); +} +#endif + static struct super_block *ext3_get_sb(struct file_system_type *fs_type, int flags, const char *dev_name, void *data) {