mirror of
https://github.com/samba-team/samba.git
synced 2024-12-27 03:21:53 +03:00
f0210601d1
of the FAM API.
(This used to be commit dc96ce90e5
)
464 lines
12 KiB
C
464 lines
12 KiB
C
/*
|
|
* FAM file notification support.
|
|
*
|
|
* Copyright (c) James Peach 2005
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include "includes.h"
|
|
|
|
#ifdef HAVE_FAM_CHANGE_NOTIFY
|
|
|
|
#include <fam.h>
|
|
|
|
#if !defined(HAVE_FAM_H_FAMCODES_TYPEDEF)
|
|
/* Gamin provides this typedef which means we can't use 'enum FAMCodes' as per
|
|
* every other FAM implementation. Phooey.
|
|
*/
|
|
typedef enum FAMCodes FAMCodes;
|
|
#endif
|
|
|
|
/* NOTE: There are multiple versions of FAM floating around the net, each with
|
|
* slight differences from the original SGI FAM implementation. In this file,
|
|
* we rely only on the SGI features and do not assume any extensions. For
|
|
* example, we do not look at FAMErrno, because it is not set by the original
|
|
* implementation.
|
|
*
|
|
* Random FAM links:
|
|
* http://oss.sgi.com/projects/fam/
|
|
* http://savannah.nongnu.org/projects/fam/
|
|
* http://sourceforge.net/projects/bsdfam/
|
|
*/
|
|
|
|
struct fam_req_info
|
|
{
|
|
FAMRequest req;
|
|
int generation;
|
|
FAMCodes code;
|
|
enum
|
|
{
|
|
/* We are waiting for an event. */
|
|
FAM_REQ_MONITORING,
|
|
/* An event has been receive, but we haven't been able to send it back
|
|
* to the client yet. It is stashed in the code member.
|
|
*/
|
|
FAM_REQ_FIRED
|
|
} state;
|
|
};
|
|
|
|
/* Don't initialise this until the first register request. We want a single
|
|
* FAM connection for each worker smbd. If we allow the master (parent) smbd to
|
|
* open a FAM connection, multiple processes talking on the same socket will
|
|
* undoubtedly create havoc.
|
|
*/
|
|
static FAMConnection global_fc;
|
|
static int global_fc_generation;
|
|
|
|
#define FAM_TRACE 8
|
|
#define FAM_TRACE_LOW 10
|
|
|
|
#define FAM_EVENT_DRAIN ((uint32_t)(-1))
|
|
|
|
static void * fam_register_notify(connection_struct * conn,
|
|
char * path,
|
|
uint32 flags);
|
|
|
|
static BOOL fam_check_notify(connection_struct * conn,
|
|
uint16_t vuid,
|
|
char * path,
|
|
uint32_t flags,
|
|
void * data,
|
|
time_t when);
|
|
|
|
static void fam_remove_notify(void * data);
|
|
|
|
static struct cnotify_fns global_fam_notify =
|
|
{
|
|
fam_register_notify,
|
|
fam_check_notify,
|
|
fam_remove_notify,
|
|
-1,
|
|
-1
|
|
};
|
|
|
|
/* Turn a FAM event code into a string. Don't rely on specific code values,
|
|
* because that might not work across all flavours of FAM.
|
|
*/
|
|
static const char *
|
|
fam_event_str(FAMCodes code)
|
|
{
|
|
static const struct { FAMCodes code; const char * name; } evstr[] =
|
|
{
|
|
{ FAMChanged, "FAMChanged"},
|
|
{ FAMDeleted, "FAMDeleted"},
|
|
{ FAMStartExecuting, "FAMStartExecuting"},
|
|
{ FAMStopExecuting, "FAMStopExecuting"},
|
|
{ FAMCreated, "FAMCreated"},
|
|
{ FAMMoved, "FAMMoved"},
|
|
{ FAMAcknowledge, "FAMAcknowledge"},
|
|
{ FAMExists, "FAMExists"},
|
|
{ FAMEndExist, "FAMEndExist"}
|
|
};
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(evstr); ++i) {
|
|
if (code == evstr[i].code)
|
|
return(evstr[i].name);
|
|
}
|
|
|
|
return("<unknown>");
|
|
}
|
|
|
|
static BOOL
|
|
fam_check_reconnect(void)
|
|
{
|
|
if (FAMCONNECTION_GETFD(&global_fc) < 0) {
|
|
fstring name;
|
|
|
|
global_fc_generation++;
|
|
snprintf(name, sizeof(name), "smbd (%lu)", (unsigned long)sys_getpid());
|
|
|
|
if (FAMOpen2(&global_fc, name) < 0) {
|
|
DEBUG(0, ("failed to connect to FAM service\n"));
|
|
return(False);
|
|
}
|
|
}
|
|
|
|
global_fam_notify.notification_fd = FAMCONNECTION_GETFD(&global_fc);
|
|
return(True);
|
|
}
|
|
|
|
static BOOL
|
|
fam_monitor_path(connection_struct * conn,
|
|
struct fam_req_info * info,
|
|
const char * path,
|
|
uint32 flags)
|
|
{
|
|
SMB_STRUCT_STAT st;
|
|
pstring fullpath;
|
|
|
|
DEBUG(FAM_TRACE, ("requesting FAM notifications for '%s'\n", path));
|
|
|
|
/* FAM needs an absolute pathname. */
|
|
|
|
/* It would be better to use reduce_name() here, but reduce_name does not
|
|
* actually return the reduced result. How utterly un-useful.
|
|
*/
|
|
pstrcpy(fullpath, path);
|
|
if (!canonicalize_path(conn, fullpath)) {
|
|
DEBUG(0, ("failed to canonicalize path '%s'\n", path));
|
|
return(False);
|
|
}
|
|
|
|
if (*fullpath != '/') {
|
|
DEBUG(0, ("canonicalized path '%s' into `%s`\n", path, fullpath));
|
|
DEBUGADD(0, ("but expected an absolute path\n"));
|
|
return(False);
|
|
}
|
|
|
|
if (SMB_VFS_STAT(conn, path, &st) < 0) {
|
|
DEBUG(0, ("stat of '%s' failed: %s\n", path, strerror(errno)));
|
|
return(False);
|
|
}
|
|
/* Start monitoring this file or directory. We hand the state structure to
|
|
* both the caller and the FAM library so we can match up the caller's
|
|
* status requests with FAM notifications.
|
|
*/
|
|
if (S_ISDIR(st.st_mode)) {
|
|
FAMMonitorDirectory(&global_fc, fullpath, &(info->req), info);
|
|
} else {
|
|
FAMMonitorFile(&global_fc, fullpath, &(info->req), info);
|
|
}
|
|
|
|
/* Grr. On IRIX, neither of the monitor functions return a status. */
|
|
|
|
/* We will stay in initialising state until we see the FAMendExist message
|
|
* for this file.
|
|
*/
|
|
info->state = FAM_REQ_MONITORING;
|
|
info->generation = global_fc_generation;
|
|
return(True);
|
|
}
|
|
|
|
static BOOL
|
|
fam_handle_event(const FAMCodes code, uint32 flags)
|
|
{
|
|
#define F_CHANGE_MASK (FILE_NOTIFY_CHANGE_FILE | \
|
|
FILE_NOTIFY_CHANGE_ATTRIBUTES | \
|
|
FILE_NOTIFY_CHANGE_SIZE | \
|
|
FILE_NOTIFY_CHANGE_LAST_WRITE | \
|
|
FILE_NOTIFY_CHANGE_LAST_ACCESS | \
|
|
FILE_NOTIFY_CHANGE_CREATION | \
|
|
FILE_NOTIFY_CHANGE_EA | \
|
|
FILE_NOTIFY_CHANGE_SECURITY)
|
|
|
|
#define F_DELETE_MASK (FILE_NOTIFY_CHANGE_FILE_NAME | \
|
|
FILE_NOTIFY_CHANGE_DIR_NAME)
|
|
|
|
#define F_CREATE_MASK (FILE_NOTIFY_CHANGE_FILE_NAME | \
|
|
FILE_NOTIFY_CHANGE_DIR_NAME)
|
|
|
|
switch (code) {
|
|
case FAMChanged:
|
|
if (flags & F_CHANGE_MASK)
|
|
return(True);
|
|
break;
|
|
case FAMDeleted:
|
|
if (flags & F_DELETE_MASK)
|
|
return(True);
|
|
break;
|
|
case FAMCreated:
|
|
if (flags & F_CREATE_MASK)
|
|
return(True);
|
|
break;
|
|
default:
|
|
/* Ignore anything else. */
|
|
break;
|
|
}
|
|
|
|
return(False);
|
|
|
|
#undef F_CHANGE_MASK
|
|
#undef F_DELETE_MASK
|
|
#undef F_CREATE_MASK
|
|
}
|
|
|
|
static BOOL
|
|
fam_pump_events(struct fam_req_info * info, uint32_t flags)
|
|
{
|
|
FAMEvent ev;
|
|
|
|
for (;;) {
|
|
|
|
/* If we are draining the event queue we must keep going until we find
|
|
* the correct FAMAcknowledge event or the connection drops. Otherwise
|
|
* we should stop when there are no more events pending.
|
|
*/
|
|
if (flags != FAM_EVENT_DRAIN && !FAMPending(&global_fc)) {
|
|
break;
|
|
}
|
|
|
|
if (FAMNextEvent(&global_fc, &ev) < 0) {
|
|
DEBUG(0, ("failed to fetch pending FAM event\n"));
|
|
DEBUGADD(0, ("resetting FAM connection\n"));
|
|
FAMClose(&global_fc);
|
|
FAMCONNECTION_GETFD(&global_fc) = -1;
|
|
return(False);
|
|
}
|
|
|
|
DEBUG(FAM_TRACE_LOW, ("FAM event %s on '%s' for request %d\n",
|
|
fam_event_str(ev.code), ev.filename, ev.fr.reqnum));
|
|
|
|
switch (ev.code) {
|
|
case FAMAcknowledge:
|
|
/* FAM generates an ACK event when we cancel a monitor. We need
|
|
* this to know when it is safe to free out request state
|
|
* structure.
|
|
*/
|
|
if (info->generation == global_fc_generation &&
|
|
info->req.reqnum == ev.fr.reqnum &&
|
|
flags == FAM_EVENT_DRAIN) {
|
|
return(True);
|
|
}
|
|
|
|
case FAMEndExist:
|
|
case FAMExists:
|
|
/* Ignore these. FAM sends these enumeration events when we
|
|
* start monitoring. If we are monitoring a directory, we will
|
|
* get a FAMExists event for each directory entry.
|
|
*/
|
|
|
|
/* TODO: we might be able to use these to implement recursive
|
|
* monitoring of entire subtrees.
|
|
*/
|
|
case FAMMoved:
|
|
/* These events never happen. A move or rename shows up as a
|
|
* create/delete pair.
|
|
*/
|
|
case FAMStartExecuting:
|
|
case FAMStopExecuting:
|
|
/* We might get these, but we just don't care. */
|
|
break;
|
|
|
|
case FAMChanged:
|
|
case FAMDeleted:
|
|
case FAMCreated:
|
|
if (info->generation != global_fc_generation) {
|
|
/* Ignore this; the req number can't be matched. */
|
|
break;
|
|
}
|
|
|
|
if (info->req.reqnum == ev.fr.reqnum) {
|
|
/* This is the event the caller was interested in. */
|
|
DEBUG(FAM_TRACE, ("handling FAM %s event on '%s'\n",
|
|
fam_event_str(ev.code), ev.filename));
|
|
/* Ignore events if we are draining this request. */
|
|
if (flags != FAM_EVENT_DRAIN) {
|
|
return(fam_handle_event(ev.code, flags));
|
|
}
|
|
break;
|
|
} else {
|
|
/* Caller doesn't want this event. Stash the result so we
|
|
* can come back to it. Unfortunately, FAM doesn't
|
|
* guarantee to give us back evinfo.
|
|
*/
|
|
struct fam_req_info * evinfo =
|
|
(struct fam_req_info *)ev.userdata;
|
|
|
|
if (evinfo) {
|
|
DEBUG(FAM_TRACE, ("storing FAM %s event for winter\n",
|
|
fam_event_str(ev.code)));
|
|
evinfo->state = FAM_REQ_FIRED;
|
|
evinfo->code = ev.code;
|
|
} else {
|
|
DEBUG(2, ("received FAM %s notification for %s, "
|
|
"but userdata was unexpectedly NULL\n",
|
|
fam_event_str(ev.code), ev.filename));
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
DEBUG(0, ("ignoring unknown FAM event code %d for `%s`\n",
|
|
ev.code, ev.filename));
|
|
}
|
|
}
|
|
|
|
/* No more notifications pending. */
|
|
return(False);
|
|
}
|
|
|
|
static BOOL
|
|
fam_test_connection(void)
|
|
{
|
|
FAMConnection fc;
|
|
|
|
/* On IRIX FAMOpen2 leaks 960 bytes in 48 blocks. It's a deliberate leak
|
|
* in the library and there's nothing we can do about it here.
|
|
*/
|
|
if (FAMOpen2(&fc, "smbd probe") < 0)
|
|
return(False);
|
|
|
|
FAMClose(&fc);
|
|
return(True);
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------- */
|
|
|
|
static void *
|
|
fam_register_notify(connection_struct * conn,
|
|
char * path,
|
|
uint32 flags)
|
|
{
|
|
struct fam_req_info * info;
|
|
|
|
if (!fam_check_reconnect()) {
|
|
return(False);
|
|
}
|
|
|
|
if ((info = SMB_MALLOC_P(struct fam_req_info)) == NULL) {
|
|
DEBUG(0, ("malloc of %d bytes failed\n", sizeof(struct fam_req_info)));
|
|
return(NULL);
|
|
}
|
|
|
|
if (fam_monitor_path(conn, info, path, flags)) {
|
|
return(info);
|
|
} else {
|
|
SAFE_FREE(info);
|
|
return(NULL);
|
|
}
|
|
}
|
|
|
|
static BOOL
|
|
fam_check_notify(connection_struct * conn,
|
|
uint16_t vuid,
|
|
char * path,
|
|
uint32_t flags,
|
|
void * data,
|
|
time_t when)
|
|
{
|
|
struct fam_req_info * info;
|
|
|
|
info = (struct fam_req_info *)data;
|
|
SMB_ASSERT(info != NULL);
|
|
|
|
DEBUG(10, ("checking FAM events for `%s`\n", path));
|
|
|
|
if (info->state == FAM_REQ_FIRED) {
|
|
DEBUG(FAM_TRACE, ("handling previously fired FAM %s event\n",
|
|
fam_event_str(info->code)));
|
|
info->state = FAM_REQ_MONITORING;
|
|
return(fam_handle_event(info->code, flags));
|
|
}
|
|
|
|
if (!fam_check_reconnect()) {
|
|
return(False);
|
|
}
|
|
|
|
if (info->generation != global_fc_generation) {
|
|
DEBUG(FAM_TRACE, ("reapplying stale FAM monitor to %s\n", path));
|
|
fam_monitor_path(conn, info, path, flags);
|
|
return(False);
|
|
}
|
|
|
|
return(fam_pump_events(info, flags));
|
|
}
|
|
|
|
static void
|
|
fam_remove_notify(void * data)
|
|
{
|
|
struct fam_req_info * info;
|
|
|
|
if ((info = (struct fam_req_info *)data) == NULL)
|
|
return;
|
|
|
|
/* No need to reconnect. If the FAM connection is gone, there's no need to
|
|
* cancel and we can safely let FAMCancelMonitor fail. If it we
|
|
* reconnected, then the generation check will stop us cancelling the wrong
|
|
* request.
|
|
*/
|
|
|
|
if (info->generation == global_fc_generation) {
|
|
DEBUG(FAM_TRACE, ("removing FAM notification for request %d\n",
|
|
info->req.reqnum));
|
|
FAMCancelMonitor(&global_fc, &(info->req));
|
|
|
|
/* Soak up all events until the FAMAcknowledge. We can't free
|
|
* our request state until we are sure there are no more events in
|
|
* flight.
|
|
*/
|
|
fam_pump_events(info, FAM_EVENT_DRAIN);
|
|
}
|
|
|
|
SAFE_FREE(info);
|
|
}
|
|
|
|
struct cnotify_fns * fam_notify_init(void)
|
|
{
|
|
FAMCONNECTION_GETFD(&global_fc) = -1;
|
|
|
|
if (!fam_test_connection()) {
|
|
DEBUG(0, ("FAM file change notifications not available\n"));
|
|
return(NULL);
|
|
}
|
|
|
|
DEBUG(FAM_TRACE, ("enabling FAM change notifications\n"));
|
|
return &global_fam_notify;
|
|
}
|
|
|
|
#endif /* HAVE_FAM_CHANGE_NOTIFY */
|