connections, bug #572.
* NEW: afpd: new option "force xattr with sticky bit = yes|no"
(default: no), FR #94
+* UPD: afpd: FCE version 2 with new event types and new config options
+ "fce ignore names" and "fce notify script"
Changes in 3.1.3
================
"FCE_FILE_DELETE",
"FCE_DIR_DELETE",
"FCE_FILE_CREATE",
- "FCE_DIR_CREATE"
+ "FCE_DIR_CREATE",
+ "FCE_FILE_MOVE",
+ "FCE_DIR_MOVE",
+ "FCE_LOGIN",
+ "FCE_LOGOUT"
};
static int unpack_fce_packet(unsigned char *buf, struct fce_packet *packet)
{
unsigned char *p = buf;
+ uint16_t uint16;
+ uint32_t uint32;
+ uint64_t uint64;
- memcpy(&packet->magic[0], p, sizeof(packet->magic));
- p += sizeof(packet->magic);
+ memcpy(&packet->fcep_magic[0], p, sizeof(packet->fcep_magic));
+ p += sizeof(packet->fcep_magic);
- packet->version = *p;
- p++;
+ packet->fcep_version = *p++;
- packet->mode = *p;
- p++;
+ if (packet->fcep_version > 1)
+ packet->fcep_options = *p++;
- memcpy(&packet->event_id, p, sizeof(packet->event_id));
- p += sizeof(packet->event_id);
- packet->event_id = ntohl(packet->event_id);
+ packet->fcep_event = *p++;
- memcpy(&packet->datalen, p, sizeof(packet->datalen));
- p += sizeof(packet->datalen);
- packet->datalen = ntohs(packet->datalen);
+ if (packet->fcep_version > 1)
+ /* padding */
+ p++;
- memcpy(&packet->data[0], p, packet->datalen);
- packet->data[packet->datalen] = 0; /* 0 terminate strings */
- p += packet->datalen;
+ if (packet->fcep_version > 1)
+ /* reserved */
+ p += 8;
+
+ memcpy(&packet->fcep_event_id, p, sizeof(packet->fcep_event_id));
+ p += sizeof(packet->fcep_event_id);
+ packet->fcep_event_id = ntohl(packet->fcep_event_id);
+
+ if (packet->fcep_options & FCE_EV_INFO_PID) {
+ memcpy(&packet->fcep_pid, p, sizeof(packet->fcep_pid));
+ packet->fcep_pid = hton64(packet->fcep_pid);
+ p += sizeof(packet->fcep_pid);
+ }
+
+ if (packet->fcep_options & FCE_EV_INFO_USER) {
+ memcpy(&packet->fcep_userlen, p, sizeof(packet->fcep_userlen));
+ packet->fcep_userlen = ntohs(packet->fcep_userlen);
+ p += sizeof(packet->fcep_userlen);
+
+ memcpy(&packet->fcep_user[0], p, packet->fcep_userlen);
+ packet->fcep_user[packet->fcep_userlen] = 0; /* 0 terminate strings */
+ p += packet->fcep_userlen;
+ }
+
+ /* path */
+ memcpy(&packet->fcep_pathlen1, p, sizeof(packet->fcep_pathlen1));
+ p += sizeof(packet->fcep_pathlen1);
+ packet->fcep_pathlen1 = ntohs(packet->fcep_pathlen1);
+
+ memcpy(&packet->fcep_path1[0], p, packet->fcep_pathlen1);
+ packet->fcep_path1[packet->fcep_pathlen1] = 0; /* 0 terminate strings */
+ p += packet->fcep_pathlen1;
+
+ if (packet->fcep_options & FCE_EV_INFO_SRCPATH) {
+ memcpy(&packet->fcep_pathlen2, p, sizeof(packet->fcep_pathlen2));
+ p += sizeof(packet->fcep_pathlen2);
+ packet->fcep_pathlen2 = ntohs(packet->fcep_pathlen2);
+ memcpy(&packet->fcep_path2[0], p, packet->fcep_pathlen2);
+ packet->fcep_path2[packet->fcep_pathlen2] = 0; /* 0 terminate strings */
+ p += packet->fcep_pathlen2;
+ }
return 0;
}
-int main(void)
+int main(int argc, char **argv)
{
- int sockfd;
+ int sockfd, rv, c;
struct addrinfo hints, *servinfo, *p;
- int rv;
int numbytes;
struct sockaddr_storage their_addr;
char buf[MAXBUFLEN];
socklen_t addr_len;
+ char s[INET6_ADDRSTRLEN];
+ char *host = "localhost";
+
+ while ((c = getopt(argc, argv, "h:")) != -1) {
+ switch(c) {
+ case 'h':
+ host = strdup(optarg);
+ break;
+ }
+ }
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC; // set to AF_INET to force IPv4
hints.ai_socktype = SOCK_DGRAM;
- if ((rv = getaddrinfo(NULL, FCE_DEFAULT_PORT_STRING, &hints, &servinfo)) != 0) {
+ if ((rv = getaddrinfo(host, FCE_DEFAULT_PORT_STRING, &hints, &servinfo)) != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
return 1;
}
unpack_fce_packet((unsigned char *)buf, &packet);
- if (memcmp(packet.magic, FCE_PACKET_MAGIC, sizeof(packet.magic)) == 0) {
+ if (memcmp(packet.fcep_magic, FCE_PACKET_MAGIC, sizeof(packet.fcep_magic)) == 0) {
- switch (packet.mode) {
+ switch (packet.fcep_event) {
case FCE_CONN_START:
printf("FCE Start\n");
break;
break;
default:
- printf("ID: %" PRIu32 ", Event: %s, Path: %s\n",
- packet.event_id, fce_ev_names[packet.mode], packet.data);
+ printf("ID: %" PRIu32 ", Event: %s", packet.fcep_event_id, fce_ev_names[packet.fcep_event]);
+ if (packet.fcep_options & FCE_EV_INFO_PID)
+ printf(", pid: %" PRId64, packet.fcep_pid);
+ if (packet.fcep_options & FCE_EV_INFO_USER)
+ printf(", user: %s", packet.fcep_user);
+
+ if (packet.fcep_options & FCE_EV_INFO_SRCPATH)
+ printf(", source: %s", packet.fcep_path2);
+
+ printf(", Path: %s\n", packet.fcep_path1);
break;
}
}
bin_SCRIPTS = $(PERLSCRIPTS) $(GENERATED_FILES) afpstats
-EXTRA_DIST = $(TEMPLATE_FILES) make-casetable.pl make-precompose.h.pl afpstats
+EXTRA_DIST = $(TEMPLATE_FILES) make-casetable.pl make-precompose.h.pl afpstats fce_ev_script.sh
--- /dev/null
+#!/bin/sh
+
+usage="$(basename $0) [-h] [-v version] [-e event] [-P path] [-S source path] -- FCE sample script
+
+where:
+ -h show this help text
+ -v version
+ -e event
+ -P path
+ -S source path for events like rename/move
+ -u username
+ -p pid
+ -i event ID
+"
+
+while getopts ':hs:v:e:P:S:u:p:i:' option; do
+ case "$option" in
+ h) echo "$usage"
+ exit
+ ;;
+ v) version=$OPTARG
+ ;;
+ e) event=$OPTARG
+ ;;
+ P) path=$OPTARG
+ ;;
+ S) srcpath=$OPTARG
+ ;;
+ u) user=$OPTARG
+ ;;
+ p) pid=$OPTARG
+ ;;
+ i) evid=$OPTARG
+ ;;
+ ?) printf "illegal option: '%s'\n" "$OPTARG" >&2
+ echo "$usage" >&2
+ exit 1
+ ;;
+ esac
+done
+shift $((OPTIND - 1))
+
+printf "FCE Event: $event" >> /tmp/fce.log
+if [ -n "$version" ] ; then
+ printf ", protocol: $version" >> /tmp/fce.log
+fi
+if [ -n "$evid" ] ; then
+ printf ", ID: $evid" >> /tmp/fce.log
+fi
+if [ -n "$pid" ] ; then
+ printf ", pid: $pid" >> /tmp/fce.log
+fi
+if [ -n "$user" ] ; then
+ echo -n ", user: $user" >> /tmp/fce.log
+fi
+if [ -n "$srcpath" ] ; then
+ echo -n ", source: $srcpath" >> /tmp/fce.log
+fi
+if [ -n "$path" ] ; then
+ echo -n ", path: $path" >> /tmp/fce.log
+fi
+printf "\n" >> /tmp/fce.log
afpd processes notify interested listeners about certain filesystem
event by UDP network datagrams.</para>
+ <para>The following FCE events are defined:</para>
+
+ <itemizedlist>
+ <listitem><para>file modification (<option>fmod</option>)</para></listitem>
+ <listitem><para>file deletion (<option>fdel</option>)</para></listitem>
+ <listitem><para>directory deletion (<option>ddel</option>)</para></listitem>
+ <listitem><para>file creation (<option>fcre</option>)</para></listitem>
+ <listitem><para>directory creation (<option>dcre</option>)</para></listitem>
+ <listitem><para>file move or rename (<option>fmov</option>)</para></listitem>
+ <listitem><para>directory move or rename (<option>dmov</option>)</para></listitem>
+ <listitem><para>login (<option>login</option>)</para></listitem>
+ <listitem><para>logout (<option>logout</option>)</para></listitem>
+ </itemizedlist>
+
<variablelist>
<varlistentry>
<term>fce listener = <replaceable>host[:port]</replaceable>
<varlistentry>
<term>fce events =
- <replaceable>fmod,fdel,ddel,fcre,dcre,tmsz</replaceable>
+ <replaceable>fmod,fdel,ddel,fcre,dcre,fmov,dmov,login,logout</replaceable>
<type>(G)</type></term>
<listitem>
seconds.</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term>fce ignore names = <replaceable>NAME[/NAME2/...]</replaceable>
+ <type>(G)</type></term>
+
+ <listitem>
+ <para>Slash delimited list of filenames for which FCE
+ events shall not be generated. Default: .DS_Store.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>fce notify script = <replaceable>PATH</replaceable>
+ <type>(G)</type></term>
+
+ <listitem>
+ <para>Script which will be executed for every FCE event,
+ see contrib/shell_utils/fce_ev_script.shfrom the Netatalk
+ sources for an example script.</para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</refsect2>
DISTCLEANFILES =
sbin_PROGRAMS = afpd
-noinst_PROGRAMS = hash fce spot
+noinst_PROGRAMS = hash spot
afpd_SOURCES = \
afp_avahi.c \
hash_SOURCES = hash.c
hash_CFLAGS = -DKAZLIB_TEST_MAIN -I$(top_srcdir)/include
-fce_SOURCES = fce_api.c fce_util.c
-fce_CFLAGS = -DFCE_TEST_MAIN -I$(top_srcdir)/include
-fce_LDADD = $(top_builddir)/libatalk/libatalk.la
-
spot_SOURCES = spotlight.c spotlight_marshalling.c
spot_CFLAGS = -DSPOT_TEST_MAIN
spot_LDADD = $(top_builddir)/libatalk/libatalk.la
LOG(log_note, logtype_afpd, "Fce events: %s", r);
fce_set_events(r);
}
+ r = atalk_iniparser_getstring(obj->iniconfig, INISEC_GLOBAL, "fce version", "1");
+ LOG(log_debug, logtype_afpd, "Fce version: %s", r);
+ obj->fce_version = atoi(r);
+
+ if ((r = atalk_iniparser_getstring(obj->iniconfig, INISEC_GLOBAL, "fce ignore names", ".DS_Store"))) {
+ obj->fce_ign_names = strdup(r);
+ }
+
+ if ((r = atalk_iniparser_getstring(obj->iniconfig, INISEC_GLOBAL, "fce notify script", NULL))) {
+ obj->fce_notify_script = strdup(r);
+ }
+
+
EC_CLEANUP:
if (q)
}
}
+static void child_handler(void)
+{
+ wait(NULL);
+}
+
/* -----------------
if dsi->in_write is set attention, tickle (and close?) msg
aren't sent. We don't care about tickle
afp_dsi_die(EXITERR_SYS);
}
#endif /* DEBUGGING */
+
+ /* SIGCLD */
+ action.sa_handler = child_handler;
+#ifdef SA_NOCLDWAIT
+/* this enhancement simplifies things for Solaris, it also improves performance */
+ action.sa_flags |= SA_NOCLDWAIT;
+#endif
+ if (sigaction(SIGCLD, &action, NULL) < 0 ) {
+ LOG(log_error, logtype_afpd, "afp_over_dsi: sigaction: %s", strerror(errno) );
+ afp_dsi_die(EXITERR_SYS);
+ }
}
/* -------------------------------------------
exit(82);
return 1;
}
+
+/*
+ * Run a command in the background without waiting,
+ * being careful about uid/gid handling
+ */
+int afprun_bg(int root, char *cmd)
+{
+ pid_t pid;
+ uid_t uid = geteuid();
+ gid_t gid = getegid();
+ int fd, fdlimit = sysconf(_SC_OPEN_MAX);
+
+ LOG(log_debug, logtype_afpd, "running %s as user %d", cmd, root ? 0 : uid);
+
+ /* in this method we will exec /bin/sh with the correct
+ arguments, after first setting stdout to point at the file */
+
+ if ((pid = fork()) < 0) {
+ LOG(log_error, logtype_afpd, "afprun: fork failed with error %s", strerror(errno) );
+ return errno;
+ }
+
+ if (pid)
+ /* parent, just return */
+ return 0;
+
+ /* we are in the child. we exec /bin/sh to do the work for us. we
+ don't directly exec the command we want because it may be a
+ pipeline or anything else the config file specifies */
+
+ if (chdir("/") < 0) {
+ LOG(log_error, logtype_afpd, "afprun: can't change directory to \"/\" %s", strerror(errno) );
+ exit(83);
+ }
+
+ /* now completely lose our privileges. This is a fairly paranoid
+ way of doing it, but it does work on all systems that I know of */
+ if (root) {
+ become_user_permanently(0, 0);
+ uid = gid = 0;
+ } else {
+ become_user_permanently(uid, gid);
+ }
+
+ if (getuid() != uid || geteuid() != uid || getgid() != gid || getegid() != gid) {
+ /* we failed to lose our privileges - do not execute the command */
+ exit(81);
+ }
+
+ fd = 3;
+ while (fd < fdlimit)
+ close(fd++);
+
+ execl("/bin/sh","sh","-c", cmd, NULL);
+
+ /* not reached */
+ exit(82);
+ return 1;
+}
#include <atalk/server_ipc.h>
#include <atalk/uuid.h>
#include <atalk/globals.h>
+#include <atalk/fce_api.h>
#include <atalk/spotlight.h>
#include <atalk/unix.h>
/* Some PAM module might have reset our signal handlers and timer, so we need to reestablish them */
afp_over_dsi_sighandlers(obj);
+ /* Send FCE login event */
+ fce_register(obj, FCE_LOGIN, "", NULL);
+
return( AFP_OK );
}
close_all_vol(obj);
dsi->flags = DSI_AFP_LOGGED_OUT;
*rbuflen = 0;
+
+ /* Send FCE login event */
+ fce_register(obj, FCE_LOGOUT, "", NULL);
+
return AFP_OK;
}
ad_setname(&ad, s_path->m_name);
ad_setid( &ad, s_path->st.st_dev, s_path->st.st_ino, dir->d_did, did, vol->v_stamp);
- fce_register(FCE_DIR_CREATE, bdata(curdir->d_fullpath), NULL, fce_dir);
+ fce_register(obj, FCE_DIR_CREATE, bdata(curdir->d_fullpath), NULL);
ad_flush(&ad);
ad_close(&ad, ADFLAGS_HF);
// ONLY USED IN THIS FILE
#include "fce_api_internal.h"
+extern int afprun_bg(int root, char *cmd);
+
/* We store our connection data here */
static struct udp_entry udp_socket_list[FCE_MAX_UDP_SOCKS];
static int udp_sockets = 0;
(1 << FCE_FILE_DELETE) |
(1 << FCE_DIR_DELETE) |
(1 << FCE_FILE_CREATE) |
- (1 << FCE_DIR_CREATE);
+ (1 << FCE_DIR_CREATE) |
+ (1 << FCE_FILE_MOVE) |
+ (1 << FCE_DIR_MOVE) |
+ (1 << FCE_LOGIN) |
+ (1 << FCE_LOGOUT);
+
+static uint8_t fce_ev_info; /* flags of additional info to send in events */
-#define MAXIOBUF 1024
+#define MAXIOBUF 4096
static unsigned char iobuf[MAXIOBUF];
-static const char *skip_files[] =
-{
- ".DS_Store",
- NULL
-};
+static const char **skip_files;
static struct fce_close_event last_close_event;
static char *fce_event_names[] = {
"FCE_FILE_DELETE",
"FCE_DIR_DELETE",
"FCE_FILE_CREATE",
- "FCE_DIR_CREATE"
+ "FCE_DIR_CREATE",
+ "FCE_FILE_MOVE",
+ "FCE_DIR_MOVE",
+ "FCE_LOGIN",
+ "FCE_LOGOUT"
};
/*
/*
* Construct a UDP packet for our listeners and return packet size
* */
-static ssize_t build_fce_packet( struct fce_packet *packet, const char *path, int event, uint32_t event_id )
+static ssize_t build_fce_packet(const AFPObj *obj,
+ char *iobuf,
+ fce_ev_t event,
+ const char *path,
+ const char *oldpath,
+ pid_t pid,
+ const char *user,
+ uint32_t event_id)
{
- size_t pathlen = 0;
- ssize_t data_len = 0;
-
- /* Set content of packet */
- memcpy(packet->magic, FCE_PACKET_MAGIC, sizeof(packet->magic) );
- packet->version = FCE_PACKET_VERSION;
- packet->mode = event;
-
- packet->event_id = event_id;
-
- pathlen = strlen(path); /* exclude string terminator */
-
- /* This should never happen, but before we bust this server, we send nonsense, fce listener has to cope */
- if (pathlen >= MAXPATHLEN)
- pathlen = MAXPATHLEN - 1;
-
- packet->datalen = pathlen;
-
- /* This is the payload len. Means: the packet has len bytes more until packet is finished */
- data_len = FCE_PACKET_HEADER_SIZE + pathlen;
-
- memcpy(packet->data, path, pathlen);
+ char *p = iobuf;
+ size_t pathlen;
+ ssize_t datalen = 0;
+ uint16_t uint16;
+ uint32_t uint32;
+ uint64_t uint64;
+ uint8_t packet_info = fce_ev_info;
+
+ /* FCE magic */
+ memcpy(p, FCE_PACKET_MAGIC, 8);
+ p += 8;
+ datalen += 8;
+
+ /* version */
+ *p = FCE_PACKET_VERSION;
+ p += 1;
+ datalen += 1;
+
+ /* optional: options */
+ if (FCE_PACKET_VERSION > 1) {
+ if (oldpath)
+ packet_info |= FCE_EV_INFO_SRCPATH;
+ *p = packet_info;
+ p += 1;
+ datalen += 1;
+ }
- /* return the packet len */
- return data_len;
-}
+ /* event */
+ *p = event;
+ p += 1;
+ datalen += 1;
-/*
- * Handle Endianess and write into buffer w/o padding
- **/
-static void pack_fce_packet(struct fce_packet *packet, unsigned char *buf, int maxlen)
-{
- unsigned char *p = buf;
+ /* optional: padding */
+ if (FCE_PACKET_VERSION > 1) {
+ p += 1;
+ datalen += 1;
+ }
- memcpy(p, &packet->magic[0], sizeof(packet->magic));
- p += sizeof(packet->magic);
+ /* optional: reserved */
+ if (FCE_PACKET_VERSION > 1) {
+ p += 8;
+ datalen += 8;
+ }
- *p = packet->version;
- p++;
-
- *p = packet->mode;
- p++;
-
- uint32_t *id = (uint32_t*)p;
- *id = htonl(packet->event_id);
- p += sizeof(packet->event_id);
+ /* event ID */
+ uint32 = htonl(event_id);
+ memcpy(p, &uint32, sizeof(uint32));
+ p += sizeof(uint32);
+ datalen += sizeof(uint32);
+
+ /* optional: pid */
+ if (packet_info & FCE_EV_INFO_PID) {
+ uint64 = pid;
+ uint64 = hton64(uint64);
+ memcpy(p, &uint64, sizeof(uint64));
+ p += sizeof(uint64);
+ datalen += sizeof(uint64);
+ }
- uint16_t *l = ( uint16_t *)p;
- *l = htons(packet->datalen);
- p += sizeof(packet->datalen);
+ /* optional: username */
+ if (packet_info & FCE_EV_INFO_USER) {
+ uint16 = strlen(user);
+ uint16 = htons(uint16);
+ memcpy(p, &uint16, sizeof(uint16));
+ p += sizeof(uint16);
+ datalen += sizeof(uint16);
+ memcpy(p, user, strlen(user));
+ p += strlen(user);
+ datalen += strlen(user);
+ }
- if (((p - buf) + packet->datalen) < maxlen) {
- memcpy(p, &packet->data[0], packet->datalen);
+ /* path */
+ if ((pathlen = strlen(path)) >= MAXPATHLEN)
+ pathlen = MAXPATHLEN - 1;
+ uint16 = pathlen;
+ uint16 = htons(uint16);
+ memcpy(p, &uint16, sizeof(uint16));
+ p += sizeof(uint16);
+ datalen += sizeof(uint16);
+ memcpy(p, path, pathlen);
+ p += pathlen;
+ datalen += pathlen;
+
+ /* optional: source path */
+ if (packet_info & FCE_EV_INFO_SRCPATH) {
+ if ((pathlen = strlen(oldpath)) >= MAXPATHLEN)
+ pathlen = MAXPATHLEN - 1;
+ uint16 = pathlen;
+ uint16 = htons(uint16);
+ memcpy(p, &uint16, sizeof(uint16));
+ p += sizeof(uint16);
+ datalen += sizeof(uint16);
+ memcpy(p, oldpath, pathlen);
+ p += pathlen;
+ datalen += pathlen;
}
+
+ /* return the packet len */
+ return datalen;
}
/*
* Send the fce information to all (connected) listeners
* We dont give return code because all errors are handled internally (I hope..)
* */
-static void send_fce_event(const char *path, int event)
+static void send_fce_event(const AFPObj *obj, int event, const char *path, const char *oldpath)
{
static bool first_event = true;
-
- struct fce_packet packet;
static uint32_t event_id = 0; /* the unique packet couter to detect packet/data loss. Going from 0xFFFFFFFF to 0x0 is a valid increment */
+ static char *user;
time_t now = time(NULL);
-
- LOG(log_debug, logtype_fce, "send_fce_event: start");
+ ssize_t data_len;
/* initialized ? */
if (first_event == true) {
first_event = false;
+
+ struct passwd *pwd = getpwuid(obj->uid);
+ user = strdup(pwd->pw_name);
+
+ switch (obj->fce_version) {
+ case 1:
+ /* fce_ev_info unused */
+ break;
+ case 2:
+ fce_ev_info = FCE_EV_INFO_PID | FCE_EV_INFO_USER;
+ break;
+ default:
+ fce_ev_info = 0;
+ LOG(log_error, logtype_fce, "Unsupported FCE protocol version %d", obj->fce_version);
+ break;
+ }
+
fce_init_udp();
/* Notify listeners the we start from the beginning */
- send_fce_event( "", FCE_CONN_START );
+ send_fce_event(obj, FCE_CONN_START, "", NULL);
}
- /* build our data packet */
- ssize_t data_len = build_fce_packet( &packet, path, event, ++event_id );
- pack_fce_packet(&packet, iobuf, MAXIOBUF);
+ /* run script */
+ if (obj->fce_notify_script) {
+ static bstring quote = NULL;
+ static bstring quoterep = NULL;
+ static bstring slash = NULL;
+ static bstring slashrep = NULL;
+
+ if (!quote) {
+ quote = bfromcstr("'");
+ quoterep = bfromcstr("'\\''");
+ slash = bfromcstr("\\");
+ slashrep = bfromcstr("\\\\");
+ }
- for (int i = 0; i < udp_sockets; i++)
- {
+ bstring cmd = bformat("%s -v %d -e %s -i %" PRIu32 "",
+ obj->fce_notify_script,
+ FCE_PACKET_VERSION,
+ fce_event_names[event],
+ event_id);
+
+ if (path[0]) {
+ bstring bpath = bfromcstr(path);
+ bfindreplace(bpath, slash, slashrep, 0);
+ bfindreplace(bpath, quote, quoterep, 0);
+ bformata(cmd, " -P '%s'", bdata(bpath));
+ bdestroy(bpath);
+ }
+ if (fce_ev_info | FCE_EV_INFO_PID)
+ bformata(cmd, " -p %" PRIu64 "", (uint64_t)getpid());
+ if (fce_ev_info | FCE_EV_INFO_USER)
+ bformata(cmd, " -u %s", user);
+ if (oldpath) {
+ bstring boldpath = bfromcstr(oldpath);
+ bfindreplace(boldpath, slash, slashrep, 0);
+ bfindreplace(boldpath, quote, quoterep, 0);
+ bformata(cmd, " -S '%s'", bdata(boldpath));
+ bdestroy(boldpath);
+ }
+ (void)afprun_bg(1, bdata(cmd));
+ bdestroy(cmd);
+ }
+
+ for (int i = 0; i < udp_sockets; i++) {
int sent_data = 0;
struct udp_entry *udp_entry = udp_socket_list + i;
/* we had a problem earlier ? */
- if (udp_entry->sock == -1)
- {
+ if (udp_entry->sock == -1) {
/* We still have to wait ?*/
if (now < udp_entry->next_try_on_error)
continue;
udp_entry->next_try_on_error = 0;
/* Okay, we have a running socket again, send server that we had a problem on our side*/
- data_len = build_fce_packet( &packet, "", FCE_CONN_BROKEN, 0 );
- pack_fce_packet(&packet, iobuf, MAXIOBUF);
+ data_len = build_fce_packet(obj, iobuf, FCE_CONN_BROKEN, "", NULL, getpid(), user, 0);
sendto(udp_entry->sock,
iobuf,
0,
(struct sockaddr *)&udp_entry->sockaddr,
udp_entry->addrinfo.ai_addrlen);
-
- /* Rebuild our original data packet */
- data_len = build_fce_packet(&packet, path, event, event_id);
- pack_fce_packet(&packet, iobuf, MAXIOBUF);
}
+ /* build our data packet */
+ data_len = build_fce_packet(obj, iobuf, event, path, oldpath, getpid(), user, ++event_id);
+
sent_data = sendto(udp_entry->sock,
iobuf,
data_len,
return AFP_OK;
}
-static void save_close_event(const char *path)
+static void save_close_event(const AFPObj *obj, const char *path)
{
time_t now = time(NULL);
if (last_close_event.time /* is there any saved event ? */
&& (strcmp(path, last_close_event.path) != 0)) {
/* no, so send the saved event out now */
- send_fce_event(last_close_event.path, FCE_FILE_MODIFY);
+ send_fce_event(obj, FCE_FILE_MODIFY,last_close_event.path, NULL);
}
LOG(log_debug, logtype_fce, "save_close_event: %s", path);
strncpy(last_close_event.path, path, MAXPATHLEN);
}
+static void fce_init_ign_names(const char *ignores)
+{
+ int count = 0;
+ char *names = strdup(ignores);
+ char *p;
+ int i = 0;
+
+ while (names[i]) {
+ count++;
+ for (; names[i] && names[i] != '/'; i++)
+ ;
+ if (!names[i])
+ break;
+ i++;
+ }
+
+ skip_files = calloc(count + 1, sizeof(char *));
+
+ for (i = 0, p = strtok(names, "/"); p ; p = strtok(NULL, "/"))
+ skip_files[i++] = strdup(p);
+
+ free(names);
+}
+
/*
*
* Dispatcher for all incoming file change events
*
* */
-int fce_register(fce_ev_t event, const char *path, const char *oldpath, fce_obj_t type)
+int fce_register(const AFPObj *obj, fce_ev_t event, const char *path, const char *oldpath)
{
static bool first_event = true;
const char *bname;
AFP_ASSERT(event >= FCE_FIRST_EVENT && event <= FCE_LAST_EVENT);
AFP_ASSERT(path);
- LOG(log_debug, logtype_fce, "register_fce(path: %s, type: %s, event: %s",
- path, type == fce_dir ? "dir" : "file", fce_event_names[event]);
+ LOG(log_debug, logtype_fce, "register_fce(path: %s, event: %s)",
+ path, fce_event_names[event]);
bname = basename_safe(path);
- if (udp_sockets == 0)
+ if ((udp_sockets == 0) && (obj->fce_notify_script == NULL)) {
/* No listeners configured */
return AFP_OK;
-
+ }
/* do some initialization on the fly the first time */
if (first_event) {
fce_initialize_history();
+ fce_init_ign_names(obj->fce_ign_names);
first_event = false;
}
/* handle files which should not cause events (.DS_Store atc. ) */
- for (int i = 0; skip_files[i] != NULL; i++) {
- if (strcmp(bname, skip_files[i]) == 0)
+ for (int i = 0; skip_files[i] != NULL; i++) {
+ if (strcmp(bname, skip_files[i]) == 0)
return AFP_OK;
}
/* Can we ignore this event based on type or history? */
- if (fce_handle_coalescation(event, path, type)) {
+ if (fce_handle_coalescation(event, path)) {
LOG(log_debug9, logtype_fce, "Coalesced fc event <%d> for <%s>", event, path);
return AFP_OK;
}
switch (event) {
case FCE_FILE_MODIFY:
- save_close_event(path);
+ save_close_event(obj, path);
break;
default:
- send_fce_event(path, event);
+ send_fce_event(obj, event, path, oldpath);
break;
}
return AFP_OK;
}
-static void check_saved_close_events(int fmodwait)
+static void check_saved_close_events(const AFPObj *obj)
{
time_t now = time(NULL);
/* check if configured holdclose time has passed */
- if (last_close_event.time && ((last_close_event.time + fmodwait) < now)) {
+ if (last_close_event.time && ((last_close_event.time + obj->options.fce_fmodwait) < now)) {
LOG(log_debug, logtype_fce, "check_saved_close_events: sending event: %s", last_close_event.path);
/* yes, send event */
- send_fce_event(&last_close_event.path[0], FCE_FILE_MODIFY);
+ send_fce_event(obj, FCE_FILE_MODIFY, &last_close_event.path[0], NULL);
last_close_event.path[0] = 0;
last_close_event.time = 0;
}
/*
* API-Calls for file change api, called form outside (file.c directory.c ofork.c filedir.c)
* */
-void fce_pending_events(AFPObj *obj)
+void fce_pending_events(const AFPObj *obj)
{
if (!udp_sockets)
return;
- check_saved_close_events(obj->options.fce_fmodwait);
+ check_saved_close_events(obj);
}
/*
fce_ev_enabled |= (1 << FCE_FILE_CREATE);
} else if (strcmp(p, "dcre") == 0) {
fce_ev_enabled |= (1 << FCE_DIR_CREATE);
+ } else if (strcmp(p, "fmov") == 0) {
+ fce_ev_enabled |= (1 << FCE_FILE_MOVE);
+ } else if (strcmp(p, "dmov") == 0) {
+ fce_ev_enabled |= (1 << FCE_DIR_MOVE);
+ } else if (strcmp(p, "login") == 0) {
+ fce_ev_enabled |= (1 << FCE_LOGIN);
+ } else if (strcmp(p, "logout") == 0) {
+ fce_ev_enabled |= (1 << FCE_LOGOUT);
}
}
return AFP_OK;
}
-
-#ifdef FCE_TEST_MAIN
-
-
-void shortsleep( unsigned int us )
-{
- usleep( us );
-}
-int main( int argc, char*argv[] )
-{
- int c;
-
- char *port = FCE_DEFAULT_PORT_STRING;
- char *host = "localhost";
- int delay_between_events = 1000;
- int event_code = FCE_FILE_MODIFY;
- char pathbuff[1024];
- int duration_in_seconds = 0; // TILL ETERNITY
- char target[256];
- char *path = getcwd( pathbuff, sizeof(pathbuff) );
-
- // FULLSPEED TEST IS "-s 1001" -> delay is 0 -> send packets without pause
-
- while ((c = getopt(argc, argv, "d:e:h:p:P:s:")) != -1) {
- switch(c) {
- case '?':
- fprintf(stdout, "%s: [ -p Port -h Listener1 [ -h Listener2 ...] -P path -s Delay_between_events_in_us -e event_code -d Duration ]\n", argv[0]);
- exit(1);
- break;
- case 'd':
- duration_in_seconds = atoi(optarg);
- break;
- case 'e':
- event_code = atoi(optarg);
- break;
- case 'h':
- host = strdup(optarg);
- break;
- case 'p':
- port = strdup(optarg);
- break;
- case 'P':
- path = strdup(optarg);
- break;
- case 's':
- delay_between_events = atoi(optarg);
- break;
- }
- }
-
- sprintf(target, "%s:%s", host, port);
- if (fce_add_udp_socket(target) != 0)
- return 1;
-
- int ev_cnt = 0;
- time_t start_time = time(NULL);
- time_t end_time = 0;
-
- if (duration_in_seconds)
- end_time = start_time + duration_in_seconds;
-
- while (1)
- {
- time_t now = time(NULL);
- if (now > start_time)
- {
- start_time = now;
- fprintf( stdout, "%d events/s\n", ev_cnt );
- ev_cnt = 0;
- }
- if (end_time && now >= end_time)
- break;
-
- fce_register(event_code, path, NULL, 0);
- ev_cnt++;
-
-
- shortsleep( delay_between_events );
- }
-}
-#endif /* TESTMAIN*/
#define FCE_MAX_UDP_SOCKS 5 /* Allow a maximum of udp listeners for file change events */
#define FCE_SOCKET_RETRY_DELAY_S 600 /* Pause this time in s after socket was broken */
-#define FCE_PACKET_VERSION 1
#define FCE_HISTORY_LEN 10 /* This is used to coalesce events */
#define MAX_COALESCE_TIME_MS 1000 /* Events oldeer than this are not coalesced */
struct fce_history {
fce_ev_t fce_h_event;
- fce_obj_t fce_h_type;
char fce_h_path[MAXPATHLEN + 1];
struct timeval fce_h_tv;
};
#define PACKET_HDR_LEN (sizeof(struct fce_packet) - FCE_MAX_PATH_LEN)
-bool fce_handle_coalescation(int event, const char *path, fce_obj_t type);
+bool fce_handle_coalescation(int event, const char *path);
void fce_initialize_history();
}
}
-bool fce_handle_coalescation(int event, const char *path, fce_obj_t type)
+bool fce_handle_coalescation(int event, const char *path)
{
/* These two are used to eval our next index in history */
/* the history is unsorted, speed should not be a problem, length is 10 */
/* If we find a parent dir we should be DELETED we are done */
if ((coalesce & FCE_COALESCE_DELETE)
- && fh->fce_h_type
&& (event == FCE_FILE_DELETE || event == FCE_DIR_DELETE)) {
/* Parent dir ? */
if (!strncmp(fh->fce_h_path, path, strlen(fh->fce_h_path)))
/* We have a new entry for the history, register it */
fce_history_list[oldest_entry_idx].fce_h_tv = tv;
fce_history_list[oldest_entry_idx].fce_h_event = event;
- fce_history_list[oldest_entry_idx].fce_h_type = type;
- strncpy(fce_history_list[oldest_entry_idx].fce_h_path, path, MAXPATHLEN);
+ strncpy(fce_history_list[oldest_entry_idx].fce_h_path, path, MAXPATHLEN);
/* we have to handle this event */
return false;
createfile_iderr:
ad_flush(&ad);
ad_close(&ad, ADFLAGS_DF|ADFLAGS_HF );
- fce_register(FCE_FILE_CREATE, fullpathname(upath), NULL, fce_file);
+ fce_register(obj, FCE_FILE_CREATE, fullpathname(upath), NULL);
sl_index_file(path);
curdir->d_offcnt++;
move and rename sdir:oldname to curdir:newname in volume vol
special care is needed for lock
*/
-static int moveandrename(struct vol *vol,
+static int moveandrename(const AFPObj *obj,
+ struct vol *vol,
struct dir *sdir,
int sdir_fd,
char *oldname,
cnid_t id;
int cwd_fd = -1;
- LOG(log_debug, logtype_afpd,
- "moveandrename: [\"%s\"/\"%s\"] -> \"%s\"",
- cfrombstr(sdir->d_u_name), oldname, newname);
-
ad_init(&ad, vol);
adp = &ad;
adflags = 0;
goto exit;
}
+ if (isdir)
+ LOG(log_debug, logtype_afpd,
+ "moveandrename(\"%s\" -> \"%s/%s\")",
+ oldunixname, bdata(curdir->d_fullpath), upath);
+ else
+ LOG(log_debug, logtype_afpd,
+ "moveandrename(\"%s/%s\" -> \"%s/%s\")",
+ bdata(sdir->d_fullpath), oldunixname, bdata(curdir->d_fullpath), upath);
+
/* source == destination. we just silently accept this. */
if ((!isdir && curdir == sdir) || (isdir && curdir->d_did == sdir->d_pdid)) {
if (strcmp(oldname, newname) == 0) {
AFP_CNID_START("cnid_update");
cnid_update(vol->v_cdb, id, st, curdir->d_did, upath, strlen(upath));
AFP_CNID_DONE();
+
+ /* Send FCE event */
+ if (isdir) {
+ fce_register(obj, FCE_DIR_MOVE, fullpathname(upath), oldunixname);
+ } else {
+ bstring srcpath = bformat("%s/%s", bdata(sdir->d_fullpath), oldunixname);
+ fce_register(obj, FCE_FILE_MOVE, fullpathname(upath), bdata(srcpath));
+ bdestroy(srcpath);
+ }
}
exit:
return AFP_OK; /* newname == oldname same dir */
}
- rc = moveandrename(vol, sdir, -1, oldname, newname, isdir);
+ rc = moveandrename(obj, vol, sdir, -1, oldname, newname, isdir);
if ( rc == AFP_OK ) {
setvoltime(obj, vol );
}
cnid_delete(vol->v_cdb, delcnid);
AFP_CNID_DONE();
}
- fce_register(FCE_DIR_DELETE, fullpathname(upath), NULL, fce_dir);
+ fce_register(obj, FCE_DIR_DELETE, fullpathname(upath), NULL);
} else {
/* we have to cache this, the structs are lost in deletcurdir*/
/* but we need the positive returncode to send our event */
if ((dname = bstrcpy(curdir->d_u_name)) == NULL)
return AFPERR_MISC;
if ((rc = deletecurdir(vol)) == AFP_OK)
- fce_register(FCE_DIR_DELETE, fullpathname(cfrombstr(dname)), NULL, fce_dir);
+ fce_register(obj, FCE_DIR_DELETE, fullpathname(cfrombstr(dname)), NULL);
bdestroy(dname);
}
} else if (of_findname(vol, s_path)) {
rc = AFPERR_NOOBJ;
} else {
if ((rc = deletefile(vol, -1, upath, 1)) == AFP_OK) {
- fce_register(FCE_FILE_DELETE, fullpathname(upath), NULL, fce_file);
+ fce_register(obj, FCE_FILE_DELETE, fullpathname(upath), NULL);
if (vol->v_tm_used < s_path->st.st_size)
vol->v_tm_used = 0;
else
/* This does the work */
LOG(log_debug, logtype_afpd, "afp_move(oldname:'%s', newname:'%s', isdir:%u)",
oldname, newname, isdir);
- rc = moveandrename(vol, sdir, sdir_fd, oldname, newname, isdir);
+ rc = moveandrename(obj, vol, sdir, sdir_fd, oldname, newname, isdir);
if ( rc == AFP_OK ) {
char *upath = mtoupath(vol, newname, pdid, utf8_encoding(obj));
/* Somone has used write_fork, we assume file was changed, register it to file change event api */
if ((ofork->of_flags & AFPFORK_MODIFIED) && (forkpath)) {
- fce_register(FCE_FILE_MODIFY, bdata(forkpath), NULL, fce_file);
+ fce_register(obj, FCE_FILE_MODIFY, bdata(forkpath), NULL);
}
ad_unlock(ofork->of_ad, ofork->of_refnum, ofork->of_flags & AFPFORK_ERROR ? 0 : 1);
struct vol *vol = talloc_zero(ssp_slq, struct vol);
vol->v_path = "/Volumes/test";
ssp_slq->slq_vol = vol;
- ssp_slq->slq_allow_expr = false;
+ ssp_slq->slq_allow_expr = true;
sparqlvar = 'a';
s = yy_scan_string(argv[1]);
#include <atalk/globals.h>
+#define FCE_PACKET_VERSION 2
+
/* fce_packet.mode */
#define FCE_FILE_MODIFY 1
#define FCE_FILE_DELETE 2
#define FCE_DIR_DELETE 3
#define FCE_FILE_CREATE 4
#define FCE_DIR_CREATE 5
+#define FCE_FILE_MOVE 6
+#define FCE_DIR_MOVE 7
+#define FCE_LOGIN 8
+#define FCE_LOGOUT 9
#define FCE_CONN_START 42
#define FCE_CONN_BROKEN 99
/* fce_packet.fce_magic */
#define FCE_PACKET_MAGIC "at_fcapi"
-/* This packet goes over the network, so we want to
- * be shure about datastructs and type sizes between platforms.
- * Format is network byte order.
+/* flags for "fce_ev_info" of additional info to send in events */
+#define FCE_EV_INFO_PID (1 << 0)
+#define FCE_EV_INFO_USER (1 << 1)
+#define FCE_EV_INFO_SRCPATH (1 << 2)
+
+/*
+ * Network payload of an FCE packet, version 1
+ *
+ * 1 2 3 4 5 6 7 8
+ * +---------+---------+---------+---------+---------+---------+----------+----------+
+ * | FCE magic |
+ * +---------+---------+---------+---------+---------+---------+----------+----------+
+ * | version |
+ * +---------+
+ * | event |
+ * +---------+-----------------------------+
+ * | event ID |
+ * +-------------------+-------------------+ . . . .
+ * | pathlen | path
+ * +-------------------+------ . . . . . . . . . . .
+ *
+ *
+ * Network payload of an FCE packet, version 2
+ *
+ * 1 2 3 4 5 6 7 8
+ * +---------+---------+---------+---------+---------+---------+----------+----------+
+ * | FCE magic |
+ * +---------+---------+---------+---------+---------+---------+----------+----------+
+ * | version |
+ * +---------+
+ * | options |
+ * +---------+
+ * | event |
+ * +---------+
+ * | padding |
+ * +---------+---------+---------+---------+---------+---------+----------+----------+
+ * | reserved |
+ * +---------+---------+---------+---------+---------+---------+----------+----------+
+ * | event ID |
+ * +---------+---------+---------+---------+
+ * ... optional:
+ * +---------+---------+---------+---------+---------+---------+----------+----------+
+ * | pid |
+ * +---------+---------+---------+---------+---------+---------+----------+----------+
+ * ...
+ * ... optional:
+ * +-------------------+---------- . . . .
+ * | username length | username
+ * +-------------------+---------- . . . .
+ * ...
+ * +-------------------+------ . . . . . .
+ * | pathlen | path
+ * +-------------------+------ . . . . . .
+ * ... optional:
+ * +-------------------+------------- . . .
+ * | pathlen | source path
+ * +-------------------+------------- . . .
+ *
+ * version = 2
+ * options = bitfield:
+ * 0: pid present
+ * 1: username present
+ * 2: source path present
+ * pid = optional pid
+ * username = optional username
+ * source path = optional source path
*/
-#define FCE_PACKET_HEADER_SIZE 8+1+1+4+2
-
-struct fce_packet
-{
- char magic[8];
- unsigned char version;
- unsigned char mode;
- uint32_t event_id;
- uint16_t datalen;
- char data[MAXPATHLEN];
+
+struct fce_packet {
+ char fcep_magic[8];
+ unsigned char fcep_version;
+ unsigned char fcep_options;
+ unsigned char fcep_event;
+ uint32_t fcep_event_id;
+ uint64_t fcep_pid;
+ uint16_t fcep_userlen;
+ char fcep_user[MAXPATHLEN];
+ uint16_t fcep_pathlen1;
+ char fcep_path1[MAXPATHLEN];
+ uint16_t fcep_pathlen2;
+ char fcep_path2[MAXPATHLEN];
};
typedef uint32_t fce_ev_t;
-typedef enum { fce_file, fce_dir } fce_obj_t;
struct path;
struct ofork;
-void fce_pending_events(AFPObj *obj);
-int fce_register(fce_ev_t event, const char *path, const char *oldpath, fce_obj_t type);
+void fce_pending_events(const AFPObj *obj);
+int fce_register(const AFPObj *obj, fce_ev_t event, const char *path, const char *oldpath);
int fce_add_udp_socket(const char *target ); // IP or IP:Port
int fce_set_coalesce(const char *coalesce_opt ); // all|delete|create
int fce_set_events(const char *events); /* fmod,fdel,ddel,fcre,dcre */
void (*exit)(int);
int (*reply)(void *, int);
int (*attention)(void *, AFPUserBytes);
+ int fce_version;
+ char *fce_ign_names;
+ char *fce_notify_script;
} AFPObj;
/* typedef for AFP functions handlers */
.PP
Netatalk includes a nifty filesystem change event mechanism where afpd processes notify interested listeners about certain filesystem event by UDP network datagrams\&.
.PP
+The following FCE events are defined:
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+.sp -1
+.IP \(bu 2.3
+.\}
+file modification (\fBfmod\fR)
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+.sp -1
+.IP \(bu 2.3
+.\}
+file deletion (\fBfdel\fR)
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+.sp -1
+.IP \(bu 2.3
+.\}
+directory deletion (\fBddel\fR)
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+.sp -1
+.IP \(bu 2.3
+.\}
+file creation (\fBfcre\fR)
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+.sp -1
+.IP \(bu 2.3
+.\}
+directory creation (\fBdcre\fR)
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+.sp -1
+.IP \(bu 2.3
+.\}
+file move or rename (\fBfmov\fR)
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+.sp -1
+.IP \(bu 2.3
+.\}
+directory move or rename (\fBdmov\fR)
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+.sp -1
+.IP \(bu 2.3
+.\}
+login (\fBlogin\fR)
+.RE
+.sp
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+.sp -1
+.IP \(bu 2.3
+.\}
+logout (\fBlogout\fR)
+.RE
+.PP
fce listener = \fIhost[:port]\fR \fB(G)\fR
.RS 4
Enables sending FCE events to the specified
is 12250 if not specified\&. Specifying multiple listeners is done by having this option once for each of them\&.
.RE
.PP
-fce events = \fIfmod,fdel,ddel,fcre,dcre,tmsz\fR \fB(G)\fR
+fce events = \fIfmod,fdel,ddel,fcre,dcre,fmov,dmov,login,logout\fR \fB(G)\fR
.RS 4
Specifies which FCE events are active, default is
\fIfmod,fdel,ddel,fcre,dcre\fR\&.
.RS 4
This determines the time delay in seconds which is always waited if another file modification for the same file is done by a client before sending an FCE file modification event (fmod)\&. For example saving a file in Photoshop would generate multiple events by itself because the application is opening, modifying and closing a file multiple times for every "save"\&. Default: 60 seconds\&.
.RE
+.PP
+fce ignore names = \fINAME[/NAME2/\&.\&.\&.]\fR \fB(G)\fR
+.RS 4
+Slash delimited list of filenames for which FCE events shall not be generated\&. Default: \&.DS_Store\&.
+.RE
+.PP
+fce notify script = \fIPATH\fR \fB(G)\fR
+.RS 4
+Script which will be executed for every FCE event, see contrib/shell_utils/fce_ev_script\&.shfrom the Netatalk sources for an example script\&.
+.RE
.SS "Debug Parameters"
.PP
These options are useful for debugging only\&.