From e4675ce8db436ac572fcc476b9bfb1116e997f9f Mon Sep 17 00:00:00 2001 From: Tim Prouty Date: Sun, 15 Feb 2009 23:45:28 -0800 Subject: s3: Add extid to the dev/inode pair This extends the file_id struct to add an additional generic uint64_t field: extid. For backwards compatibility with dev/inodes stored in xattr_tdbs and acl_tdbs, the ext id is ignored for these databases. This patch should cause no functional change on systems that don't use SMB_VFS_FILE_ID_CREATE to set the extid. Existing code that uses the smb_share_mode library will need to be updated to be compatibile with the new extid. --- source3/include/locking.h | 1 + source3/include/proto.h | 3 ++- source3/include/smb.h | 48 +++++++++++++++++++++------------------ source3/include/smb_share_modes.h | 11 +++++++-- source3/lib/file_id.c | 30 +++++++++++++++++++----- source3/libsmb/smb_share_modes.c | 39 +++++++++++++++++++++---------- source3/locking/locking.c | 6 ++--- source3/modules/vfs_acl_tdb.c | 6 +++++ source3/modules/vfs_default.c | 1 + source3/modules/vfs_xattr_tdb.c | 3 +++ source3/smbd/globals.c | 3 ++- source3/smbd/nttrans.c | 2 ++ source3/smbd/open.c | 4 ++-- source3/smbd/oplock.c | 28 +++++++++++------------ 14 files changed, 122 insertions(+), 63 deletions(-) (limited to 'source3') diff --git a/source3/include/locking.h b/source3/include/locking.h index 3fd5b94de7..1833ba3f80 100644 --- a/source3/include/locking.h +++ b/source3/include/locking.h @@ -49,6 +49,7 @@ struct file_id { other than a dev_t for the device */ uint64_t devid; uint64_t inode; + uint64_t extid; /* Support systems that use an extended id (e.g. snapshots). */ }; struct byte_range_lock { diff --git a/source3/include/proto.h b/source3/include/proto.h index bd44e9fdf4..ab1f2f4f4f 100644 --- a/source3/include/proto.h +++ b/source3/include/proto.h @@ -512,7 +512,8 @@ struct file_id vfs_file_id_from_sbuf(connection_struct *conn, const SMB_STRUCT_S bool file_id_equal(const struct file_id *id1, const struct file_id *id2); const char *file_id_string_tos(const struct file_id *id); void push_file_id_16(char *buf, const struct file_id *id); -void pull_file_id_16(char *buf, struct file_id *id); +void push_file_id_24(char *buf, const struct file_id *id); +void pull_file_id_24(char *buf, struct file_id *id); /* The following definitions come from lib/gencache.c */ diff --git a/source3/include/smb.h b/source3/include/smb.h index bef0fd177b..ec5bb71798 100644 --- a/source3/include/smb.h +++ b/source3/include/smb.h @@ -758,18 +758,19 @@ Offset Data length. 16 uint32 private_options 4 20 uint32 time sec 4 24 uint32 time usec 4 -28 SMB_DEV_T dev 8 bytes. -36 SMB_INO_T inode 8 bytes -44 unsigned long file_id 4 bytes -48 uint32 uid 4 bytes -52 uint16 flags 2 bytes -54 +28 uint64 dev 8 bytes +36 uint64 inode 8 bytes +44 uint64 extid 8 bytes +52 unsigned long file_id 4 bytes +56 uint32 uid 4 bytes +60 uint16 flags 2 bytes +62 */ #ifdef CLUSTER_SUPPORT -#define MSG_SMB_SHARE_MODE_ENTRY_SIZE 58 +#define MSG_SMB_SHARE_MODE_ENTRY_SIZE 66 #else -#define MSG_SMB_SHARE_MODE_ENTRY_SIZE 54 +#define MSG_SMB_SHARE_MODE_ENTRY_SIZE 62 #endif struct share_mode_lock { @@ -1631,37 +1632,40 @@ struct inform_level2_message { /* kernel_oplock_message definition. struct kernel_oplock_message { - SMB_DEV_T dev; - SMB_INO_T inode; + uint64_t dev; + uint64_t inode; + unit64_t extid; unsigned long file_id; }; Offset Data length. -0 SMB_DEV_T dev 8 bytes. -8 SMB_INO_T inode 8 bytes -16 unsigned long file_id 4 bytes -20 +0 uint64_t dev 8 bytes +8 uint64_t inode 8 bytes +16 uint64_t extid 8 bytes +24 unsigned long file_id 4 bytes +28 */ -#define MSG_SMB_KERNEL_BREAK_SIZE 20 +#define MSG_SMB_KERNEL_BREAK_SIZE 28 /* file_renamed_message definition. struct file_renamed_message { - SMB_DEV_T dev; - SMB_INO_T inode; + uint64_t dev; + uint64_t inode; char names[1]; A variable area containing sharepath and filename. }; Offset Data length. -0 SMB_DEV_T dev 8 bytes. -8 SMB_INO_T inode 8 bytes -16 char [] name zero terminated namelen bytes -minimum length == 18. +0 uint64_t dev 8 bytes +8 uint64_t inode 8 bytes +16 unit64_t extid 8 bytes +24 char [] name zero terminated namelen bytes +minimum length == 24. */ -#define MSG_FILE_RENAMED_MIN_SIZE 16 +#define MSG_FILE_RENAMED_MIN_SIZE 24 /* * On the wire return values for oplock types. diff --git a/source3/include/smb_share_modes.h b/source3/include/smb_share_modes.h index 101bec8245..4a2d832cc2 100644 --- a/source3/include/smb_share_modes.h +++ b/source3/include/smb_share_modes.h @@ -46,6 +46,7 @@ struct smbdb_ctx; struct smb_share_mode_entry { uint64_t dev; uint64_t ino; + uint64_t extid; uint32_t share_access; uint32_t access_mask; struct timeval open_time; @@ -66,11 +67,13 @@ int smb_share_mode_db_close(struct smbdb_ctx *db_ctx); int smb_lock_share_mode_entry(struct smbdb_ctx *db_ctx, uint64_t dev, - uint64_t ino); + uint64_t ino, + uint64_t extid); int smb_unlock_share_mode_entry(struct smbdb_ctx *db_ctx, uint64_t dev, - uint64_t ino); + uint64_t ino, + uint64_t extid); /* * Share mode database accessor functions. @@ -79,23 +82,27 @@ int smb_unlock_share_mode_entry(struct smbdb_ctx *db_ctx, int smb_get_share_mode_entries(struct smbdb_ctx *db_ctx, uint64_t dev, uint64_t ino, + uint64_t extid, struct smb_share_mode_entry **pp_list, unsigned char *p_delete_on_close); int smb_create_share_mode_entry(struct smbdb_ctx *db_ctx, uint64_t dev, uint64_t ino, + uint64_t extid, const struct smb_share_mode_entry *set_entry, const char *path); int smb_delete_share_mode_entry(struct smbdb_ctx *db_ctx, uint64_t dev, uint64_t ino, + uint64_t extid, const struct smb_share_mode_entry *set_entry); int smb_change_share_mode_entry(struct smbdb_ctx *db_ctx, uint64_t dev, uint64_t ino, + uint64_t extid, const struct smb_share_mode_entry *set_entry, const struct smb_share_mode_entry *new_entry); diff --git a/source3/lib/file_id.c b/source3/lib/file_id.c index 355aafa344..0902e3d224 100644 --- a/source3/lib/file_id.c +++ b/source3/lib/file_id.c @@ -34,7 +34,8 @@ struct file_id vfs_file_id_from_sbuf(connection_struct *conn, const SMB_STRUCT_S */ bool file_id_equal(const struct file_id *id1, const struct file_id *id2) { - return id1->inode == id2->inode && id1->devid == id2->devid; + return id1->inode == id2->inode && id1->devid == id2->devid && + id1->extid == id2->extid; } /* @@ -42,15 +43,17 @@ bool file_id_equal(const struct file_id *id1, const struct file_id *id2) */ const char *file_id_string_tos(const struct file_id *id) { - char *result = talloc_asprintf(talloc_tos(), "%llx:%llx", + char *result = talloc_asprintf(talloc_tos(), "%llx:%llx:%llx", (unsigned long long)id->devid, - (unsigned long long)id->inode); + (unsigned long long)id->inode, + (unsigned long long)id->extid); SMB_ASSERT(result != NULL); return result; } /* - push a 16 byte version of a file id into a buffer + push a 16 byte version of a file id into a buffer. This ignores the extid + and is needed when dev/inodes are stored in persistent storage (tdbs). */ void push_file_id_16(char *buf, const struct file_id *id) { @@ -61,13 +64,28 @@ void push_file_id_16(char *buf, const struct file_id *id) } /* - pul a 16 byte version of a file id from a buffer + push a 24 byte version of a file id into a buffer */ -void pull_file_id_16(char *buf, struct file_id *id) +void push_file_id_24(char *buf, const struct file_id *id) +{ + SIVAL(buf, 0, id->devid&0xFFFFFFFF); + SIVAL(buf, 4, id->devid>>32); + SIVAL(buf, 8, id->inode&0xFFFFFFFF); + SIVAL(buf, 12, id->inode>>32); + SIVAL(buf, 16, id->extid&0xFFFFFFFF); + SIVAL(buf, 20, id->extid>>32); +} + +/* + pull a 24 byte version of a file id from a buffer + */ +void pull_file_id_24(char *buf, struct file_id *id) { ZERO_STRUCTP(id); id->devid = IVAL(buf, 0); id->devid |= ((uint64_t)IVAL(buf,4))<<32; id->inode = IVAL(buf, 8); id->inode |= ((uint64_t)IVAL(buf,12))<<32; + id->extid = IVAL(buf, 16); + id->extid |= ((uint64_t)IVAL(buf,20))<<32; } diff --git a/source3/libsmb/smb_share_modes.c b/source3/libsmb/smb_share_modes.c index af3f7b0dd5..177e0114b3 100644 --- a/source3/libsmb/smb_share_modes.c +++ b/source3/libsmb/smb_share_modes.c @@ -38,7 +38,8 @@ struct smbdb_ctx { #endif int smb_create_share_mode_entry_ex(struct smbdb_ctx *db_ctx, uint64_t dev, - uint64_t ino, const struct smb_share_mode_entry *new_entry, + uint64_t ino, uint64_t extid, + const struct smb_share_mode_entry *new_entry, const char *sharepath, const char *filename); static bool sharemodes_procid_equal(const struct server_id *p1, const struct server_id *p2) @@ -83,6 +84,7 @@ struct smbdb_ctx *smb_share_mode_db_open(const char *db_path) struct locking_key { SMB_DEV_T dev; SMB_INO_T inode; + uint64_t extid; }; int smb_share_mode_db_close(struct smbdb_ctx *db_ctx) @@ -93,13 +95,14 @@ int smb_share_mode_db_close(struct smbdb_ctx *db_ctx) } static TDB_DATA get_locking_key(struct locking_key *lk, uint64_t dev, - uint64_t ino) + uint64_t ino, uint64_t extid) { TDB_DATA ld; memset(lk, '\0', sizeof(*lk)); lk->dev = (SMB_DEV_T)dev; lk->inode = (SMB_INO_T)ino; + lk->extid = extid; ld.dptr = (uint8 *)lk; ld.dsize = sizeof(*lk); return ld; @@ -111,19 +114,22 @@ static TDB_DATA get_locking_key(struct locking_key *lk, uint64_t dev, int smb_lock_share_mode_entry(struct smbdb_ctx *db_ctx, uint64_t dev, - uint64_t ino) + uint64_t ino, + uint64_t extid) { struct locking_key lk; - return tdb_chainlock(db_ctx->smb_tdb, get_locking_key(&lk, dev, ino)); + return tdb_chainlock(db_ctx->smb_tdb, get_locking_key(&lk, dev, ino, + extid)); } int smb_unlock_share_mode_entry(struct smbdb_ctx *db_ctx, uint64_t dev, - uint64_t ino) + uint64_t ino, + uint64_t extid) { struct locking_key lk; return tdb_chainunlock(db_ctx->smb_tdb, - get_locking_key(&lk, dev, ino)); + get_locking_key(&lk, dev, ino, extid)); } /* @@ -140,7 +146,8 @@ static int share_mode_entry_equal(const struct smb_share_mode_entry *e_entry, e_entry->share_access == (uint32_t)entry->share_access && e_entry->access_mask == (uint32_t)entry->access_mask && e_entry->dev == entry->id.devid && - e_entry->ino == entry->id.inode); + e_entry->ino == entry->id.inode && + e_entry->extid == entry->id.extid); } /* @@ -160,6 +167,7 @@ static void create_share_mode_entry(struct share_mode_entry *out, out->access_mask = in->access_mask; out->id.devid = in->dev; out->id.inode = in->ino; + out->id.extid = in->extid; out->uid = (uint32)geteuid(); out->flags = 0; } @@ -172,6 +180,7 @@ static void create_share_mode_entry(struct share_mode_entry *out, int smb_get_share_mode_entries(struct smbdb_ctx *db_ctx, uint64_t dev, uint64_t ino, + uint64_t extid, struct smb_share_mode_entry **pp_list, unsigned char *p_delete_on_close) { @@ -187,7 +196,8 @@ int smb_get_share_mode_entries(struct smbdb_ctx *db_ctx, *pp_list = NULL; *p_delete_on_close = 0; - db_data = tdb_fetch(db_ctx->smb_tdb, get_locking_key(&lk, dev, ino)); + db_data = tdb_fetch(db_ctx->smb_tdb, get_locking_key(&lk, dev, ino, + extid)); if (!db_data.dptr) { return 0; } @@ -229,6 +239,7 @@ int smb_get_share_mode_entries(struct smbdb_ctx *db_ctx, /* Copy into the external list. */ sme->dev = share->id.devid; sme->ino = share->id.inode; + sme->extid = share->id.extid; sme->share_access = (uint32_t)share->share_access; sme->access_mask = (uint32_t)share->access_mask; sme->open_time.tv_sec = share->time.tv_sec; @@ -257,13 +268,14 @@ int smb_get_share_mode_entries(struct smbdb_ctx *db_ctx, int smb_create_share_mode_entry_ex(struct smbdb_ctx *db_ctx, uint64_t dev, uint64_t ino, + uint64_t extid, const struct smb_share_mode_entry *new_entry, const char *sharepath, /* Must be absolute utf8 path. */ const char *filename) /* Must be relative utf8 path. */ { TDB_DATA db_data; struct locking_key lk; - TDB_DATA locking_key = get_locking_key(&lk, dev, ino); + TDB_DATA locking_key = get_locking_key(&lk, dev, ino, extid); int orig_num_share_modes = 0; struct locking_data *ld = NULL; /* internal samba db state. */ struct share_mode_entry *shares = NULL; @@ -360,24 +372,26 @@ int smb_create_share_mode_entry_ex(struct smbdb_ctx *db_ctx, int smb_create_share_mode_entry(struct smbdb_ctx *db_ctx, uint64_t dev, uint64_t ino, + uint64_t extid, const struct smb_share_mode_entry *new_entry, const char *filename) /* Must be absolute utf8 path. */ { if (*filename != '/') { abort(); } - return smb_create_share_mode_entry_ex(db_ctx, dev, ino, new_entry, + return smb_create_share_mode_entry_ex(db_ctx, dev, ino, extid, new_entry, "/", &filename[1]); } int smb_delete_share_mode_entry(struct smbdb_ctx *db_ctx, uint64_t dev, uint64_t ino, + uint64_t extid, const struct smb_share_mode_entry *del_entry) { TDB_DATA db_data; struct locking_key lk; - TDB_DATA locking_key = get_locking_key(&lk, dev, ino); + TDB_DATA locking_key = get_locking_key(&lk, dev, ino, extid); int orig_num_share_modes = 0; struct locking_data *ld = NULL; /* internal samba db state. */ struct share_mode_entry *shares = NULL; @@ -475,12 +489,13 @@ int smb_delete_share_mode_entry(struct smbdb_ctx *db_ctx, int smb_change_share_mode_entry(struct smbdb_ctx *db_ctx, uint64_t dev, uint64_t ino, + uint64_t extid, const struct smb_share_mode_entry *set_entry, const struct smb_share_mode_entry *new_entry) { TDB_DATA db_data; struct locking_key lk; - TDB_DATA locking_key = get_locking_key(&lk, dev, ino); + TDB_DATA locking_key = get_locking_key(&lk, dev, ino, extid); int num_share_modes = 0; struct locking_data *ld = NULL; /* internal samba db state. */ struct share_mode_entry *shares = NULL; diff --git a/source3/locking/locking.c b/source3/locking/locking.c index cc0295e749..1737eab1c6 100644 --- a/source3/locking/locking.c +++ b/source3/locking/locking.c @@ -923,12 +923,12 @@ bool rename_share_filename(struct messaging_context *msg_ctx, return False; } - push_file_id_16(frm, &lck->id); + push_file_id_24(frm, &lck->id); DEBUG(10,("rename_share_filename: msg_len = %u\n", (unsigned int)msg_len )); - safe_strcpy(&frm[16], lck->servicepath, sp_len); - safe_strcpy(&frm[16 + sp_len + 1], lck->filename, fn_len); + safe_strcpy(&frm[24], lck->servicepath, sp_len); + safe_strcpy(&frm[24 + sp_len + 1], lck->filename, fn_len); /* Send the messages. */ for (i=0; inum_share_modes; i++) { diff --git a/source3/modules/vfs_acl_tdb.c b/source3/modules/vfs_acl_tdb.c index 57fe73c814..909de9d7c8 100644 --- a/source3/modules/vfs_acl_tdb.c +++ b/source3/modules/vfs_acl_tdb.c @@ -94,6 +94,8 @@ static struct db_record *acl_tdb_lock(TALLOC_CTX *mem_ctx, const struct file_id *id) { uint8 id_buf[16]; + + /* For backwards compatibility only store the dev/inode. */ push_file_id_16((char *)id_buf, id); return db->fetch_locked(db, mem_ctx, @@ -200,6 +202,7 @@ static NTSTATUS get_acl_blob(TALLOC_CTX *ctx, } id = vfs_file_id_from_sbuf(handle->conn, &sbuf); + /* For backwards compatibility only store the dev/inode. */ push_file_id_16((char *)id_buf, &id); if (db->fetch(db, @@ -285,6 +288,7 @@ static NTSTATUS store_acl_blob_fsp(vfs_handle_struct *handle, } id = vfs_file_id_from_sbuf(handle->conn, &sbuf); + /* For backwards compatibility only store the dev/inode. */ push_file_id_16((char *)id_buf, &id); rec = db->fetch_locked(db, talloc_tos(), make_tdb_data(id_buf, @@ -325,6 +329,8 @@ static NTSTATUS store_acl_blob_pathname(vfs_handle_struct *handle, } id = vfs_file_id_from_sbuf(handle->conn, &sbuf); + + /* For backwards compatibility only store the dev/inode. */ push_file_id_16((char *)id_buf, &id); rec = db->fetch_locked(db, talloc_tos(), diff --git a/source3/modules/vfs_default.c b/source3/modules/vfs_default.c index c18ec268d3..7384268dae 100644 --- a/source3/modules/vfs_default.c +++ b/source3/modules/vfs_default.c @@ -1053,6 +1053,7 @@ static struct file_id vfswrap_file_id_create(struct vfs_handle_struct *handle, key.devid = sbuf->st_dev; key.inode = sbuf->st_ino; + /* key.extid is unused by default. */ return key; } diff --git a/source3/modules/vfs_xattr_tdb.c b/source3/modules/vfs_xattr_tdb.c index 908a2a3872..4e37ed6477 100644 --- a/source3/modules/vfs_xattr_tdb.c +++ b/source3/modules/vfs_xattr_tdb.c @@ -100,6 +100,7 @@ static NTSTATUS xattr_tdb_load_attrs(TALLOC_CTX *mem_ctx, NTSTATUS status; TDB_DATA data; + /* For backwards compatibility only store the dev/inode. */ push_file_id_16((char *)id_buf, id); if (db_ctx->fetch(db_ctx, mem_ctx, @@ -122,6 +123,8 @@ static struct db_record *xattr_tdb_lock_attrs(TALLOC_CTX *mem_ctx, const struct file_id *id) { uint8 id_buf[16]; + + /* For backwards compatibility only store the dev/inode. */ push_file_id_16((char *)id_buf, id); return db_ctx->fetch_locked(db_ctx, mem_ctx, make_tdb_data(id_buf, sizeof(id_buf))); diff --git a/source3/smbd/globals.c b/source3/smbd/globals.c index 3f8cb411e5..9e7d103562 100644 --- a/source3/smbd/globals.c +++ b/source3/smbd/globals.c @@ -66,7 +66,8 @@ struct fsp_singleton_cache fsp_fi_cache = { .fsp = NULL, .id = { .devid = 0, - .inode = 0 + .inode = 0, + .extid = 0 } }; unsigned long file_gen_counter = 0; diff --git a/source3/smbd/nttrans.c b/source3/smbd/nttrans.c index ad2366efae..86a46505a2 100644 --- a/source3/smbd/nttrans.c +++ b/source3/smbd/nttrans.c @@ -1856,6 +1856,8 @@ static void call_nt_transact_ioctl(connection_struct *conn, reply_nterror(req, NT_STATUS_NO_MEMORY); return; } + + /* For backwards compatibility only store the dev/inode. */ push_file_id_16(pdata, &fsp->file_id); memcpy(pdata+16,create_volume_objectid(conn,objid),16); push_file_id_16(pdata+32, &fsp->file_id); diff --git a/source3/smbd/open.c b/source3/smbd/open.c index 9971ffa679..239d4ff0c4 100644 --- a/source3/smbd/open.c +++ b/source3/smbd/open.c @@ -2597,8 +2597,8 @@ void msg_file_was_renamed(struct messaging_context *msg, } /* Unpack the message. */ - pull_file_id_16(frm, &id); - sharepath = &frm[16]; + pull_file_id_24(frm, &id); + sharepath = &frm[24]; newname = sharepath + strlen(sharepath) + 1; sp_len = strlen(sharepath); diff --git a/source3/smbd/oplock.c b/source3/smbd/oplock.c index b39e5bf634..22870283fa 100644 --- a/source3/smbd/oplock.c +++ b/source3/smbd/oplock.c @@ -40,8 +40,8 @@ void break_kernel_oplock(struct messaging_context *msg_ctx, files_struct *fsp) uint8_t msg[MSG_SMB_KERNEL_BREAK_SIZE]; /* Put the kernel break info into the message. */ - push_file_id_16((char *)msg, &fsp->file_id); - SIVAL(msg,16,fsp->fh->gen_id); + push_file_id_24((char *)msg, &fsp->file_id); + SIVAL(msg,24,fsp->fh->gen_id); /* Don't need to be root here as we're only ever sending to ourselves. */ @@ -583,8 +583,8 @@ static void process_kernel_oplock_break(struct messaging_context *msg_ctx, } /* Pull the data from the message. */ - pull_file_id_16((char *)data->data, &id); - file_id = (unsigned long)IVAL(data->data, 16); + pull_file_id_24((char *)data->data, &id); + file_id = (unsigned long)IVAL(data->data, 24); DEBUG(10, ("Got kernel oplock break message from pid %d: %s/%u\n", (int)procid_to_pid(&src), file_id_string_tos(&id), @@ -865,12 +865,12 @@ void share_mode_entry_to_message(char *msg, const struct share_mode_entry *e) SIVAL(msg,16,e->private_options); SIVAL(msg,20,(uint32)e->time.tv_sec); SIVAL(msg,24,(uint32)e->time.tv_usec); - push_file_id_16(msg+28, &e->id); - SIVAL(msg,44,e->share_file_id); - SIVAL(msg,48,e->uid); - SSVAL(msg,52,e->flags); + push_file_id_24(msg+28, &e->id); + SIVAL(msg,52,e->share_file_id); + SIVAL(msg,56,e->uid); + SSVAL(msg,60,e->flags); #ifdef CLUSTER_SUPPORT - SIVAL(msg,54,e->pid.vnn); + SIVAL(msg,62,e->pid.vnn); #endif } @@ -888,12 +888,12 @@ void message_to_share_mode_entry(struct share_mode_entry *e, char *msg) e->private_options = IVAL(msg,16); e->time.tv_sec = (time_t)IVAL(msg,20); e->time.tv_usec = (int)IVAL(msg,24); - pull_file_id_16(msg+28, &e->id); - e->share_file_id = (unsigned long)IVAL(msg,44); - e->uid = (uint32)IVAL(msg,48); - e->flags = (uint16)SVAL(msg,52); + pull_file_id_24(msg+28, &e->id); + e->share_file_id = (unsigned long)IVAL(msg,52); + e->uid = (uint32)IVAL(msg,56); + e->flags = (uint16)SVAL(msg,60); #ifdef CLUSTER_SUPPORT - e->pid.vnn = IVAL(msg,54); + e->pid.vnn = IVAL(msg,62); #endif } -- cgit