bool bdb_create_snapshot_record(JCR *jcr, SNAPSHOT_DBR *snap);
int bdb_create_file_record(JCR *jcr, ATTR_DBR *ar);
bool bdb_create_batch_file_attributes_record(JCR *jcr, ATTR_DBR *ar);
+ bool bdb_create_fileevent_record(JCR *jcr, FILEEVENT_DBR *event);
/* sql_get.c */
char *bdb_get_jobids(const char *jobids, POOLMEM **ret, bool append);
void bdb_list_fileevents_for_job(JCR *jcr, uint32_t jobid, char etype, DB_LIST_HANDLER sendit, void *ctx, e_list_type type);
void bdb_list_media_records(JCR *jcr, MEDIA_DBR *mdbr, DB_LIST_HANDLER *sendit, void *ctx, e_list_type type);
void bdb_list_jobmedia_records(JCR *jcr, JobId_t JobId, char *volume, DB_LIST_HANDLER *sendit, void *ctx, e_list_type type);
+ void bdb_list_fileevents_records(JCR *jcr, FILEEVENT_DBR *event, DB_LIST_HANDLER *sendit, void *ctx, e_list_type type);
void bdb_list_filemedia_records(JCR *jcr, JobId_t JobId, uint32_t FileIndex, DB_LIST_HANDLER *sendit, void *ctx, e_list_type type);
void bdb_list_joblog_records(JCR *jcr, JobId_t JobId, const char *pattern, DB_LIST_HANDLER *sendit, void *ctx, e_list_type type);
int bdb_list_sql_query(JCR *jcr, const char *title, const char *query, DB_LIST_HANDLER *sendit, void *ctx, int verbose, e_list_type type);
bool check(); /* check if valid */
};
+class FILEEVENT_DBR: public SMARTALLOC
+{
+public:
+ DBId_t FileIndex; // FileIndex of the File
+ DBId_t JobId; // JobId of the file
+ DBId_t SourceJobId; // Verify/Restore JobId
+ char Type; // antivirus, malware, lost file
+ int Severity; // level of severity (0 OK, 100 Important)
+ char Description[MAX_NAME_LENGTH];
+ char Source[MAX_NAME_LENGTH];
+ FILEEVENT_DBR(): FileIndex(0), JobId(0), Type(0),
+ Severity(0)
+ {
+ *Description = *Source = 0;
+ };
+ ~FILEEVENT_DBR() {};
+
+ bool unpack(uint32_t stream, const char *data, int data_len) {
+ SourceJobId = JobId = FileIndex = 0;
+ if (scan_string(data, "%c %d %127s %127s 0", &Type, &Severity, Source, Description) == 4) {
+ unbash_spaces(Source);
+ unbash_spaces(Description);
+ return true;
+ }
+ Dmsg1(50, "Got incorrect stream for FileEvent %s\n", data);
+ return false;
+ };
+};
+
/* Call back context for getting a 32/64 bit value from the database */
class db_int64_ctx {
public:
bdb_disable_batch_insert(disable)
#define db_create_snapshot_record(jcr, mdb, sr) \
mdb->bdb_create_snapshot_record(jcr, sr)
-
+#define db_create_fileevent_record(jcr, mdb, ev) \
+ mdb->bdb_create_fileevent_record(jcr, ev)
/* sql_delete.c */
#define db_delete_pool_record(jcr, mdb, pool_dbr) \
mdb->bdb_list_jobmedia_records(jcr, JobId, volume, sendit, ctx, type)
#define db_list_filemedia_records(jcr, mdb, JobId, FI, sendit, ctx, type) \
mdb->bdb_list_filemedia_records(jcr, JobId, FI, sendit, ctx, type)
+#define db_list_fileevents_records(jcr, mdb, event, sendit, ctx, type) \
+ mdb->bdb_list_fileevents_records(jcr, event, sendit, ctx, type)
#define db_list_joblog_records(jcr, mdb, JobId, pattern, sendit, ctx, type) \
mdb->bdb_list_joblog_records(jcr, JobId, pattern, sendit, ctx, type)
#define db_list_sql_query(jcr, mdb, title, query, sendit, ctx, verbose, type) \
return ret;
}
+bool BDB::bdb_create_fileevent_record(JCR *jcr, FILEEVENT_DBR *event)
+{
+ char esc1[MAX_ESCAPE_NAME_LENGTH];
+ char esc2[MAX_ESCAPE_NAME_LENGTH];
+ bool ret=false;
+
+ bdb_lock();
+ bdb_escape_string(jcr, esc1, event->Description, strlen(event->Description));
+ bdb_escape_string(jcr, esc2, event->Source, strlen(event->Source));
+ Mmsg(cmd, "INSERT INTO FileEvents (SourceJobId, JobId, FileIndex, Type, Description, Severity, Source) "
+ " VALUES (%lu, %lu, %ld, '%c', '%s', %d, '%s')",
+ event->SourceJobId, event->JobId, event->FileIndex, event->Type, esc1, event->Severity, esc2);
+ ret = bdb_sql_query(cmd, NULL, (void *)NULL);
+ bdb_unlock();
+ return ret;
+}
+
#endif /* HAVE_SQLITE3 || HAVE_MYSQL || HAVE_POSTGRESQL */
bdb_unlock();
}
+/* List FileEvents records for a given job/file */
+void BDB::bdb_list_fileevents_records(JCR *jcr, FILEEVENT_DBR *rec,
+ DB_LIST_HANDLER *sendit, void *ctx, e_list_type type)
+{
+ POOL_MEM tmp, filter;
+ char ed1[50];
+
+ Mmsg(filter, "FileEvents.JobId=%s ", edit_int64(rec->JobId, ed1));
+
+ if (rec->FileIndex > 0) {
+ Mmsg(tmp, "AND FileEvents.FileIndex=%s ", edit_int64(rec->FileIndex, ed1));
+ pm_strcat(filter, tmp.c_str());
+ }
+
+ if (B_ISALPHA(rec->Type)) {
+ Mmsg(tmp, "AND FileEvents.Type='%c' ", rec->Type);
+ pm_strcat(filter, tmp.c_str());
+ }
+
+ if (rec->Severity > 0) {
+ Mmsg(tmp, "AND FileEvents.Severity >= %d ", rec->Severity);
+ pm_strcat(filter, tmp.c_str());
+ }
+
+ bdb_lock();
+ const char *where = get_acls(DB_ACL_BIT(DB_ACL_JOB) | DB_ACL_BIT(DB_ACL_CLIENT), false);
+ const char *join = *where ? get_acl_join_filter(DB_ACL_BIT(DB_ACL_CLIENT)) : "";
+
+ if (type == VERT_LIST || type == JSON_LIST) {
+ Mmsg(cmd, "SELECT JobId,FileIndex,Path,Filename,Source,Severity,Type,Description "
+ "FROM FileEvents JOIN File USING (Jobid, FileIndex) JOIN Path USING (PathId) %s WHERE "
+ "%s %s ORDER BY JobId, FileIndex ASC", join, filter.c_str(), where);
+ } else {
+ Mmsg(cmd, "SELECT JobId,Path,Filename,Severity,Type,Description "
+ "FROM FileEvents JOIN File USING (Jobid, FileIndex) JOIN Path USING (PathId) %s WHERE "
+ "%s %s ORDER BY JobId, FileIndex ASC", join, filter.c_str(), where);
+ }
+ if (!QueryDB(jcr, cmd)) {
+ bdb_unlock();
+ return;
+ }
+
+ list_result(jcr, this, "fileevents", sendit, ctx, type);
+
+ sql_free_result();
+ bdb_unlock();
+}
void BDB::bdb_list_copies_records(JCR *jcr, uint32_t limit, char *JobIds,
DB_LIST_HANDLER *sendit, void *ctx, e_list_type type)
" VolFirstWritten=%lld VolType=%u VolParts=%d VolCloudParts=%d"
" LastPartBytes=%lld Enabled=%d Recycle=%d\n";
+static char FileEvent_add[] = "%c %d %127s %127s 0";
+/* Full format when coming from the Verify Job */
+static char FileEvent_fd_add[] = "CatReq JobId=%ld FileEvent %lu %lu %lu %c %d %127s %127s 0";
+static char FileEvent_fd_add1[] = "CatReq JobId=%ld FileEvent %lu";
+
static char Create_jobmedia[] = "CatReq JobId=%ld CreateJobMedia\n";
static char Create_filemedia[] = "CatReq JobId=%ld CreateFileMedia\n";
return stat;
}
+static bool catreq_fileevent(JCR *jcr, DBId_t FileIndex, const char *p)
+{
+ FILEEVENT_DBR event;
+ bool ok=false;
+ uint32_t t1, t2, t3, t4;
+ event.FileIndex = FileIndex;
+
+ /* If the string is going through the attribute flow, we don't have the CatReq header */
+ if (scan_string(p, FileEvent_add, &event.Type, &event.Severity, event.Source, event.Description) == 4)
+ {
+ event.JobId = jcr->JobId; // Might need to look for previous jobid for example
+ event.FileIndex = FileIndex;
+ ok = true;
+
+ /* We get the VolSessionTime, VolSessionId and FileIndex in the protocol, but we keep it for later */
+ } else if (scan_string(p, FileEvent_fd_add, &t1, &t2, &t3, &t4,
+ &event.Type, &event.Severity, event.Source, event.Description) == 8)
+ {
+ event.JobId = jcr->previous_jr.JobId;
+ ok = true;
+ }
+ if (ok) {
+ event.SourceJobId = jcr->JobId;
+ unbash_spaces(event.Source);
+ unbash_spaces(event.Description);
+ if (!db_create_fileevent_record(jcr, jcr->db, &event)) {
+ Jmsg(jcr, M_WARNING, 0, _("Catalog error creating FileEvent record. %s"),
+ db_strerror(jcr->db));
+ }
+ return true;
+
+ } else {
+ Dmsg1(50, "Error while decoding FileEvent %s\n", p);
+ }
+ return false;
+}
+
+/* Can be Snapshot or FileEvent requests from the FD */
+void fd_catreq(JCR *jcr, BSOCK *bs)
+{
+ DBId_t FileIndex=0, xjobid;
+ if (scan_string(bs->msg, FileEvent_fd_add1, &xjobid, &FileIndex) == 2) {
+ catreq_fileevent(jcr, FileIndex, bs->msg);
+
+ } else {
+ snapshot_catreq(jcr, bs);
+ }
+}
+
void catalog_request(JCR *jcr, BSOCK *bs)
{
MEDIA_DBR mr, sdmr;
if (!db_create_restore_object_record(jcr, jcr->db, &ro)) {
Jmsg1(jcr, M_FATAL, 0, _("Restore object create error. %s"), db_strerror(jcr->db));
}
+ } else if (Stream == STREAM_FILEEVENT) {
+ catreq_fileevent(jcr, FileIndex, p);
} else if (crypto_digest_stream_type(Stream) != CRYPTO_DIGEST_NONE) {
fname = p;
"DevWriteBytes=%d\n";
#endif
-
static char OK_msg[] = "1000 OK\n";
catalog_request(jcr, bs);
continue;
}
- /* Only the Snapshot commands are authorized for the FD */
+ /* Only the Snapshot and FileEvent commands are authorized for the FD */
if (role==BSOCK_TYPE_FD && bs->msg[0] == 'C') {
- snapshot_catreq(jcr, bs);
+ fd_catreq(jcr, bs);
continue;
}
if (role==BSOCK_TYPE_SD && bs->msg[0] == 'U') { /* SD sending attributes */
/* catreq.c */
+extern void fd_catreq(JCR *jcr, BSOCK *bs);
extern void catalog_request(JCR *jcr, BSOCK *bs);
extern void catalog_update(JCR *jcr, BSOCK *bs);
extern bool despool_attributes_from_file(JCR *jcr, const char *file);
* list objects [type=objecttype job_id=id clientname=n,status=S] - list plugin objects
* list pluginrestoreconf jobid=x,y,z [id=k]
* list filemedia jobid=x fileindex=z
+ * list fileevents jobid=x fileindex=z
* list metadata type=[email|attachment] tenant=xxx owner=xxx jobid=<x,w,z> client=<cli>
* from=<str>
* to=<str> cc=<str> tags=<str>
}
return 1;
+ } else if (strcasecmp(ua->argk[i], NT_("fileevents")) == 0) {
+ FILEEVENT_DBR event;
+ for (j=i+1; j<ua->argc; j++) {
+ if (strcasecmp(ua->argk[j], NT_("jobid")) == 0 && ua->argv[j]) {
+ event.JobId = str_to_int64(ua->argv[j]);
+
+ } else if (strcasecmp(ua->argk[j], NT_("fileindex")) == 0 && ua->argv[j]) {
+ event.FileIndex = str_to_int64(ua->argv[j]);
+
+ } else if (strcasecmp(ua->argk[j], NT_("severity")) == 0 && ua->argv[j]) {
+ event.Severity = str_to_int64(ua->argv[j]);
+
+ } else if (strcasecmp(ua->argk[j], NT_("Type")) == 0 && ua->argv[j]) {
+ event.Type = ua->argv[j][0];
+ }
+ }
+ if (event.JobId) {
+ db_list_fileevents_records(ua->jcr, ua->db, &event, prtit, ua, llist);
+ }
+ return 1;
+
/* List JOBLOG */
} else if (strcasecmp(ua->argk[i], NT_("joblog")) == 0) {
bool done = false;
return stat;
}
+
+/*
+ * Send the fileevent_pkt to the SD for the record
+ * Returns: true if OK
+ * false if error
+ */
+bool fileevent_save(JCR *jcr, fileevent_pkt *fevent)
+{
+ bool stat = false;
+ BSOCK *sd = jcr->store_bsock;
+
+ Dmsg1(50, "Sending STREAM_FILEEVENT %d\n", jcr->JobFiles);
+ stat = sd->fsend("%ld %d 0", jcr->JobFiles, STREAM_FILEEVENT);
+ if (!stat) {
+ goto bail_out;
+ }
+
+ bash_spaces(fevent->Description);
+ bash_spaces(fevent->Source);
+ stat = sd->fsend("%c %d %s %s 0", fevent->Type, fevent->Severity, fevent->Source, fevent->Description);
+ if (!stat) {
+ goto bail_out;
+ }
+ sd->signal(BNET_EOD); /* indicate end of attributes data */
+
+bail_out:
+ if (!stat) {
+ if (!jcr->is_canceled() && !jcr->is_incomplete()) {
+ Jmsg1(jcr, M_FATAL, 0, _("Network send error to SD. ERR=%s\n"),
+ sd->bstrerror());
+ }
+ }
+ return stat;
+}
+
/**
* Called here by find() for each file included.
* This is a callback. The original is find_files() above.
goto bail_out;
}
+ /*
+ * Handle FileEvent that might have been generated during the backup
+ * of the file.
+ */
+ struct fileevent_pkt *fevent;
+ foreach_alist(fevent, jcr->fileevents) {
+ if (!fileevent_save(jcr, fevent)) {
+ break;
+ }
+ }
+
good_rtn:
rtnstat = 1;
static bRC baculaAccurateAttribs(bpContext *ctx, accurate_attribs_pkt *att);
static void plugin_register_verify_data(bpContext *ctx);
static bRC baculaAddPlugin(bpContext *ctx, const char *file);
+static bRC baculaAddFileEvent(bpContext *ctx, struct fileevent_pkt *event);
/*
* These will be plugged into the global pointer structure for
baculaCheckChanges,
baculaAcceptFile,
baculaAccurateAttribs,
- baculaAddPlugin
+ baculaAddPlugin,
+ baculaAddFileEvent
};
/*
return bRC_OK;
}
+/**
+ * Let the plugin create FileEvent to report problems on files
+ */
+static bRC baculaAddFileEvent(bpContext *ctx, struct fileevent_pkt *event)
+{
+ JCR *jcr;
+ bacula_ctx *bctx;
+ struct fileevent_pkt *e;
+ Dsm_check(999);
+ if (!is_ctx_good(ctx, jcr, bctx)) {
+ return bRC_Error;
+ }
+ // Sanity check
+ if (!event) {
+ return bRC_Error;
+ }
+ e = (struct fileevent_pkt*) malloc(sizeof(struct fileevent_pkt));
+ memcpy(e, event, sizeof(struct fileevent_pkt));
+ jcr->fileevents->append(e);
+
+ Dsm_check(999);
+ return bRC_OK;
+}
+
/**
* Let the plugin define plugin to be included
* from the main backup.
bRC (*AcceptFile)(bpContext *ctx, struct save_pkt *sp); /* Need fname and statp */
bRC (*getAccurateAttribs)(bpContext *ctx, accurate_attribs_pkt *att);
bRC (*AddPlugin)(bpContext *ctx, const char *file);
-
+ bRC (*AddFileEvent)(bpContext *ctx, struct fileevent_pkt *ev);
} bFuncs;
#define FD_PLUGIN_MAGIC "*FDPluginData*"
-#define FD_PLUGIN_INTERFACE_VERSION ( 22 )
+#define FD_PLUGIN_INTERFACE_VERSION ( 23 + BEEF )
typedef struct s_pluginInfo {
uint32_t size;
#define plug_func(plugin) ((pFuncs *)(plugin->pfuncs))
#define plug_info(plugin) ((pInfo *)(plugin->pinfo))
+#define FILEEVENT_TYPE_ANTIVIRUS 'a'
+
+struct fileevent_pkt {
+ int32_t FileIndex;
+ char Type;
+ int Severity;
+ char Description[MAX_NAME_LENGTH];
+ char Source[MAX_NAME_LENGTH];
+};
+
#ifdef __cplusplus
}
#endif
jcr->ff = init_find_files();
jcr->start_time = time(NULL);
jcr->RunScripts = New(alist(10, not_owned_by_alist));
+ jcr->fileevents = New(alist(5, owned_by_alist));
jcr->last_fname = get_pool_memory(PM_FNAME);
jcr->last_fname[0] = 0;
jcr->client_name = get_memory(strlen(my_name) + 1);
bdelete_and_null(jcr->plugin_options_list);
free_plugins(jcr); /* release instantiated plugins */
+ delete jcr->fileevents;
free_runscripts(jcr->RunScripts);
delete jcr->RunScripts;
free_path_list(jcr);
plugin_name_stream(jcr, bmsg->rbuf);
break;
+ case STREAM_FILEEVENT:
case STREAM_RESTORE_OBJECT:
break; /* these are sent by Director */
return rtn;
}
+static bool fileevent_dir_save(JCR *jcr, fileevent_pkt *fevent)
+{
+ bool stat = false;
+ BSOCK *dir = jcr->dir_bsock;
+
+ bash_spaces(fevent->Description);
+ bash_spaces(fevent->Source);
+ /* We keep some space for SessionTime and SessionId */
+ stat = dir->fsend("CatReq JobId=%lu FileEvent %lu 0 0 %c %d %s %s 0",
+ jcr->JobId, fevent->FileIndex, fevent->Type, fevent->Severity,
+ fevent->Source, fevent->Description);
+ if (!stat) {
+ goto bail_out;
+ }
+
+bail_out:
+ if (!stat) {
+ if (!jcr->is_canceled() && !jcr->is_incomplete()) {
+ Jmsg1(jcr, M_FATAL, 0, _("Network send error to DIR. ERR=%s\n"),
+ dir->bstrerror());
+ }
+ }
+ return stat;
+}
+
/*
* Verify attributes or data of the requested files on the Volume
*
jcr->unlock();
break;
+ case STREAM_FILEEVENT:
+ break; // information only
+
case STREAM_PLUGIN_META_BLOB:
case STREAM_PLUGIN_META_CATALOG:
//TODO Add some metadata verification when it is possible
if (!accurate_finish(jcr)) {
goto bail_out;
}
+ /*
+ * Handle FileEvent that might have been generated during the verification
+ * of the file.
+ */
+ struct fileevent_pkt *fevent;
+ foreach_alist(fevent, jcr->fileevents) {
+ if (!fileevent_dir_save(jcr, fevent)) {
+ break;
+ }
+ }
+
jcr->setJobStatus(JS_Terminated);
goto ok_out;
return _("Plugin Metadata Blob");
case STREAM_PLUGIN_META_CATALOG:
return _("Plugin Metadata Catalog");
+ case STREAM_FILEEVENT:
+ return _("FileEvent");
default:
sprintf(buf, "%d", stream);
return (const char *)buf;
VSSClient *pVSSClient; /* VSS handler */
alist *plugin_verify; /* Registered plugins that need a copy of the data in verify job */
alist *plugin_options_list; /* list of the options to use in a job */
+ alist *fileevents; /* list of the current file events to record and send to the DIR */
#endif /* FILE_DAEMON */
p_ctx->fd = NULL;
}
io->status = 0;
+ {
+ struct fileevent_pkt event;
+ bstrncpy(event.Description, "A good description", sizeof(event.Description));
+ bstrncpy(event.Source, "A very good source", sizeof(event.Source));
+ event.Type = 'c';
+ event.Severity = 100;
+ bfuncs->AddFileEvent(ctx, &event);
+ bfuncs->JobMessage(ctx, fi, li, M_INFO, 0, "Adding FileEvent\n");
+ }
+
break;
case IO_SEEK:
/* Send attributes and digest to Director for Catalog */
bool send_attrs_to_dir(JCR *jcr, DEV_RECORD *rec)
{
- if (rec->maskedStream == STREAM_UNIX_ATTRIBUTES ||
+ if (rec->maskedStream == STREAM_FILEEVENT ||
+ rec->maskedStream == STREAM_UNIX_ATTRIBUTES ||
rec->maskedStream == STREAM_UNIX_ATTRIBUTES_EX ||
rec->maskedStream == STREAM_RESTORE_OBJECT ||
rec->maskedStream == STREAM_PLUGIN_OBJECT ||
if (are_attributes_spooled(jcr)) {
dir->set_spooling();
}
- Dmsg1(850, "Send attributes to dir. FI=%d\n", rec->FileIndex);
+ Dmsg2(850, "Send attributes to dir. FI=%d Stream=%s\n", rec->FileIndex, stream_to_ascii(rec->Stream));
if (!dir_update_file_attributes(jcr->dcr, rec)) {
Jmsg(jcr, M_FATAL, 0, _("Error updating file attributes. ERR=%s\n"),
dir->bstrerror());
// TODO landonf: Investigate crypto support in the storage daemon
break;
+ case STREAM_FILEEVENT: // Nothing to do in particular
+ break;
+
case STREAM_PROGRAM_NAMES:
case STREAM_PROGRAM_DATA:
if (!prog_name_msg) {
DEV_BLOCK *block = dcr->block;
POOL_MEM sql_buffer;
db_int64_ctx jmr_count;
+ FILEEVENT_DBR fevent;
char digest[BASE64_SIZE(CRYPTO_DIGEST_MAX_SIZE)];
}
break;
+ case STREAM_FILEEVENT:
+ if (verbose > 1) {
+ Pmsg0(000, _("Got FileEvent Stream record.\n"));
+ }
+ if (!fevent.unpack(rec->Stream, rec->data, rec->data_len)) {
+ Emsg0(M_ERROR, 0, _("Unable to decode FileEvent.\n"));
+ break;
+ }
+ mjcr = get_jcr_by_session(rec->VolSessionId, rec->VolSessionTime);
+ if (!mjcr) {
+ Pmsg2(000, _("Could not find SessId=%d SessTime=%d for File record.\n"),
+ rec->VolSessionId, rec->VolSessionTime);
+ break;
+ }
+ fevent.SourceJobId = fevent.JobId = mjcr->JobId;
+ fevent.FileIndex = mjcr->JobFiles;
+ if (!db_create_fileevent_record(mjcr, mjcr->db, &fevent)) {
+ Jmsg2(mjcr, M_ERROR, 0, _("Failed to insert FileEvent record for file: %d. err: %s\n"),
+ fevent.FileIndex, db_strerror(db));
+ }
+ mjcr->dec_use_count(); /* Decrease reference counter increased by get_jcr_by_session call */
+ break;
+
case STREAM_UNIX_ACCESS_ACL: /* Deprecated Standard ACL attributes on UNIX */
case STREAM_UNIX_DEFAULT_ACL: /* Deprecated Default ACL attributes on UNIX */
case STREAM_HFSPLUS_ATTRIBUTES:
return "contADATA-BLOCK-HEADER";
case STREAM_ADATA_RECORD_HEADER:
return "contADATA-RECORD-HEADER";
-
+ case STREAM_FILEEVENT:
+ return _("FileEvent");
default:
sprintf(buf, "%d", -stream);
return buf;
return "ADATA-BLOCK-HEADER";
case STREAM_ADATA_RECORD_HEADER:
return "ADATA-RECORD-HEADER";
+ case STREAM_FILEEVENT:
+ return _("FileEvent");
default:
sprintf(buf, "%d", stream);
return buf;
#define STREAM_PLUGIN_NAME 26 /* Plugin "file" string */
#define STREAM_PLUGIN_DATA 27 /* Plugin specific data */
#define STREAM_RESTORE_OBJECT 28 /* Plugin restore object */
+
/*
* Non-gzip compressed streams. Those streams can handle arbitrary
* compression algorithm data as an additional header is stored
#define STREAM_PLUGIN_META_BLOB 35 /* Plugin metadata (blob) for file being backed up */
#define STREAM_PLUGIN_META_CATALOG 36 /* Plugin metadata (to be stored in catalog) for file being backed up */
#define STREAM_UNIX_ATTRIBUTE_UPDATE 37 /* File's updated metadata */
+#define STREAM_FILEEVENT 38 /* FileEvent associated with the current object */
#define STREAM_ADATA_BLOCK_HEADER 200 /* Adata block header */
#define STREAM_ADATA_RECORD_HEADER 201 /* Adata record header */