-
Notifications
You must be signed in to change notification settings - Fork 100
Dev Docs Netatalk Controller Daemon
The netatalk daemon is the master service controller for Netatalk's AFP file-sharing stack. It orchestrates the lifecycle of child processes — afpd (AFP protocol daemon), cnid_metad (CNID metadata daemon), and optionally a D-Bus session daemon for Spotlight — through a libevent2-based event loop. The controller handles configuration parsing, service startup/restart, signal-driven reload and shutdown, and Zeroconf service registration.
| Component | File |
|---|---|
| Master daemon | etc/netatalk/netatalk.c |
| Zeroconf facade |
etc/netatalk/afp_zeroconf.c, etc/netatalk/afp_zeroconf.h
|
| Avahi integration |
etc/netatalk/afp_avahi.c, etc/netatalk/afp_avahi.h
|
| mDNSResponder integration |
etc/netatalk/afp_mdns.c, etc/netatalk/afp_mdns.h
|
| Build definition | etc/netatalk/meson.build |
| AFPObj / afp_options | include/atalk/globals.h |
| Config parsing API | include/atalk/netatalk_conf.h |
| Config parsing impl | libatalk/util/netatalk_conf.c |
The netatalk daemon manages exactly three child processes. AppleTalk-era daemons (atalkd, papd, timelord, a2boot, macipgw) are not managed by netatalk — they have their own independent init scripts.
%%{ init: { 'themeVariables': { 'fontSize': '14px' }, 'flowchart': { 'nodeSpacing': 15, 'rankSpacing': 40 } } }%%
graph TB
subgraph "Init System"
INIT["systemd / launchd<br/>rc.d / OpenRC / SMF"]:::grey
end
subgraph "netatalk Master Daemon"
NM["netatalk<br/>libevent2 event loop<br/>netatalk.c: main()"]:::orange
end
subgraph "Managed Child Processes"
AFPD["afpd<br/>AFP file server<br/>etc/afpd/main.c"]:::blue
CNID["cnid_metad<br/>CNID metadata daemon<br/>etc/cnid_dbd/cnid_metad.c"]:::purple
DBUS["dbus-daemon<br/>D-Bus session bus<br/>Spotlight only"]:::grey
end
subgraph "Zeroconf Registration"
ZC["afp_zeroconf.c<br/>Avahi or mDNSResponder"]:::green
end
subgraph "Spawned by cnid_metad"
CNIDDBD["cnid_dbd<br/>per-volume CNID<br/>database daemon"]:::salmon
end
INIT -->|"start/stop"| NM
NM -->|"fork+exec"| AFPD
NM -->|"fork+exec<br/>if CNID_BACKEND_DBD"| CNID
NM -->|"fork+exec<br/>if WITH_SPOTLIGHT"| DBUS
NM --> ZC
CNID -->|"spawns per volume"| CNIDDBD
classDef blue fill:#74b9ff,stroke:#333,rx:10,ry:10
classDef purple fill:#a29bfe,stroke:#333,rx:10,ry:10
classDef green fill:#55efc4,stroke:#333,rx:10,ry:10
classDef yellow fill:#ffeaa7,stroke:#333,rx:10,ry:10
classDef grey fill:#dfe6e9,stroke:#333,rx:10,ry:10
classDef salmon fill:#fab1a0,stroke:#333,rx:10,ry:10
classDef cyan fill:#81ecec,stroke:#333,rx:10,ry:10
classDef orange fill:#ff9f43,stroke:#333,rx:10,ry:10
Three sentinel constants defined in netatalk.c are used as PID values to track service state:
#define NETATALK_SRV_NEEDED -1 // Service must be (re)started
#define NETATALK_SRV_OPTIONAL 0 // Service not required
#define NETATALK_SRV_ERROR NETATALK_SRV_NEEDED // Error → needs restartDefined as file-scope variables in netatalk.c:
| Variable | Storage | Type | Purpose |
|---|---|---|---|
obj |
static |
AFPObj |
Parsed configuration object |
afpd_pid |
static |
pid_t |
AFP daemon PID or sentinel |
cnid_metad_pid |
static |
pid_t |
CNID metadata daemon PID or sentinel |
dbus_pid |
static |
pid_t |
D-Bus session daemon PID or sentinel |
afpd_restarts |
static |
uint |
AFP daemon restart counter |
cnid_metad_restarts |
static |
uint |
CNID daemon restart counter |
dbus_restarts |
static |
uint |
D-Bus daemon restart counter |
base |
static |
struct event_base * |
libevent2 event base |
sigterm_ev, sigquit_ev, sigchld_ev, sighup_ev, timer_ev
|
global | struct event * |
libevent2 signal/timer events |
in_shutdown |
static |
int |
Shutdown state flag |
dbus_path |
static |
const char * |
Configured dbus-daemon path |
Initial PID values control which services are managed:
-
afpd_pid=NETATALK_SRV_NEEDED— always started -
cnid_metad_pid=NETATALK_SRV_NEEDEDifCNID_BACKEND_DBDis compiled in, otherwiseNETATALK_SRV_OPTIONAL -
dbus_pid=NETATALK_SRV_OPTIONAL— only started ifWITH_SPOTLIGHTandOPTION_SPOTLIGHT
The service_running() helper checks whether a PID represents an active process:
static bool service_running(pid_t pid)
{
if ((pid != NETATALK_SRV_NEEDED) && (pid != NETATALK_SRV_OPTIONAL))
return true;
return false;
}The main() function in netatalk.c implements a carefully ordered startup:
%%{ init: { 'themeVariables': { 'fontSize': '14px' }, 'flowchart': { 'nodeSpacing': 15, 'rankSpacing': 40 } } }%%
sequenceDiagram
participant Init as Init System
participant NM as netatalk main()
participant AFP as afpd
participant CNID as cnid_metad
participant DBUS as dbus-daemon
participant ZC as Zeroconf
Init->>NM: Start netatalk
NM->>NM: Parse -d / -F flags
NM->>NM: check_lockfile()
NM->>NM: daemonize() unless -d
NM->>NM: create_lockfile()
NM->>NM: Block all signals
NM->>NM: afp_config_parse(&obj, "netatalk")
NM->>NM: load_volumes(&obj, LV_ALL)
NM->>AFP: run_process(_PATH_AFPD, "-d", "-F", configfile)
AFP-->>NM: afpd_pid
alt CNID_BACKEND_DBD compiled in
NM->>CNID: run_process(_PATH_CNID_METAD, "-d", "-F", configfile)
CNID-->>NM: cnid_metad_pid
end
NM->>NM: Setup libevent2 base + signal events
NM->>NM: Start 1-second periodic timer_ev
alt WITH_SPOTLIGHT && OPTION_SPOTLIGHT
NM->>NM: Set DBUS_SESSION_BUS_ADDRESS env
NM->>DBUS: run_process(dbus_path, "--config-file=...")
DBUS-->>NM: dbus_pid
NM->>NM: sleep(1) — let D-Bus start
NM->>NM: set_sl_volumes() via gsettings
NM->>NM: system(INDEXER_COMMAND " -s")
end
NM->>ZC: zeroconf_register(&obj)
NM->>NM: event_base_dispatch(base)
Note over NM: Enter libevent event loop
usage: netatalk [-F configfile]
netatalk -d # debug mode (foreground)
netatalk -v|-V # show version and paths
The show_netatalk_version() function displays compiled-in feature support (Zeroconf, Spotlight). The show_netatalk_paths() function displays key file paths:
| Path | Macro | Build Source |
|---|---|---|
afp.conf |
_PATH_CONFDIR "afp.conf" |
pkgconfdir in meson.build
|
afpd |
_PATH_AFPD |
sbindir/afpd |
cnid_metad |
_PATH_CNID_METAD |
sbindir/cnid_metad |
dbus-daemon |
DBUS_DAEMON_PATH |
Detected at build time |
dbus-session.conf |
_PATH_CONFDIR "dbus-session.conf" |
pkgconfdir |
indexer command |
INDEXER_COMMAND |
LocalSearch3 or Tracker3 |
lock file |
PATH_NETATALK_LOCK |
lockfile_path/netatalk |
The run_process() function uses fork() + execv() to launch child processes. It accepts a variable argument list of command-line parameters:
static pid_t run_process(const char *path, ...)
{
int i = 0;
#define MYARVSIZE 64
char *myargv[MYARVSIZE];
va_list args;
pid_t pid;
if ((pid = fork()) < 0) {
LOG(log_error, logtype_cnid, "error in fork: %s", strerror(errno));
return -1;
}
if (pid == 0) {
myargv[i++] = (char *)path;
va_start(args, path);
while (i < MYARVSIZE) {
if ((myargv[i++] = va_arg(args, char *)) == NULL)
break;
}
va_end(args);
(void)execv(path, myargv);
LOG(log_error, logtype_cnid, "Fatal error in exec: %s", strerror(errno));
exit(1);
}
return pid;
}All child processes are launched with -d (debug/foreground mode) and -F configfile so they inherit the same configuration file path and run as foreground daemons under netatalk's supervision.
The kill_childs() function sends a signal to a variable-length list of child PID pointers, skipping any that hold sentinel values:
static void kill_childs(int sig, ...)
{
va_list args;
pid_t *pid;
va_start(args, sig);
while ((pid = va_arg(args, pid_t *)) != NULL) {
if (*pid == NETATALK_SRV_ERROR || *pid == NETATALK_SRV_OPTIONAL)
continue;
kill(*pid, sig);
}
va_end(args);
}The timer_cb() callback fires every 1 second and restarts any service whose PID holds the NETATALK_SRV_NEEDED sentinel:
static void timer_cb(evutil_socket_t fd, short what, void *arg)
{
if (in_shutdown)
return;
if (afpd_pid == NETATALK_SRV_NEEDED) {
afpd_restarts++;
LOG(log_note, logtype_afpd, "Restarting 'afpd' (restarts: %u)", afpd_restarts);
if ((afpd_pid = run_process(_PATH_AFPD, "-d", "-F", obj.options.configfile,
NULL)) == -1) {
LOG(log_error, logtype_default, "Error starting 'afpd'");
}
}
if (cnid_metad_pid == NETATALK_SRV_NEEDED) {
cnid_metad_restarts++;
LOG(log_note, logtype_afpd, "Restarting 'cnid_metad' (restarts: %u)",
cnid_metad_restarts);
if ((cnid_metad_pid = run_process(_PATH_CNID_METAD, "-d", "-F",
obj.options.configfile, NULL)) == -1) {
LOG(log_error, logtype_default, "Error starting 'cnid_metad'");
}
}
#ifdef WITH_SPOTLIGHT
if (dbus_pid == NETATALK_SRV_NEEDED) {
dbus_restarts++;
LOG(log_note, logtype_afpd, "Restarting 'dbus' (restarts: %u)", dbus_restarts);
if ((dbus_pid = run_process(dbus_path,
"--config-file=" _PATH_CONFDIR "dbus-session.conf",
NULL)) == -1) {
LOG(log_error, logtype_default, "Error starting '%s'", dbus_path);
}
}
#endif
}There is no restart limit — services are restarted indefinitely as long as in_shutdown is not set. The restart counter is logged but not used to cap retries.
The netatalk daemon uses libevent2 signal events for all signal handling, set up in main():
sigterm_ev = event_new(base, SIGTERM, EV_SIGNAL, sigterm_cb, NULL);
sigquit_ev = event_new(base, SIGQUIT, EV_SIGNAL | EV_PERSIST, sigquit_cb, NULL);
sighup_ev = event_new(base, SIGHUP, EV_SIGNAL | EV_PERSIST, sighup_cb, NULL);
sigchld_ev = event_new(base, SIGCHLD, EV_SIGNAL | EV_PERSIST, sigchld_cb, NULL);
timer_ev = event_new(base, -1, EV_PERSIST, timer_cb, NULL);Only SIGTERM, SIGQUIT, SIGCHLD, and SIGHUP are unblocked; all other signals are blocked via sigprocmask().
%%{ init: { 'themeVariables': { 'fontSize': '14px' }, 'flowchart': { 'nodeSpacing': 15, 'rankSpacing': 40 } } }%%
graph LR
subgraph "Signals"
SIGHUP["SIGHUP<br/>Reload"]:::green
SIGTERM["SIGTERM<br/>Graceful stop"]:::salmon
SIGQUIT["SIGQUIT<br/>Immediate stop"]:::orange
SIGCHLD["SIGCHLD<br/>Child exited"]:::blue
end
subgraph "Callbacks"
HUP["sighup_cb()"]:::green
TERM["sigterm_cb()"]:::salmon
QUIT["sigquit_cb()"]:::orange
CHLD["sigchld_cb()"]:::blue
end
subgraph "Actions"
RELOAD["load_volumes()<br/>Re-register Zeroconf<br/>Forward SIGHUP to<br/>afpd + cnid_metad"]:::green
GRACE["in_shutdown = 1<br/>Block signals (except SIGCHLD)<br/>event_base_loopexit(5s)<br/>Stop Spotlight indexer<br/>SIGTERM to children"]:::salmon
IMMEDIATE["Stop Spotlight indexer<br/>SIGQUIT to all children"]:::orange
REAP["waitpid() loop<br/>Set PID to SRV_ERROR<br/>If all exited during shutdown:<br/>event_base_loopbreak()"]:::blue
end
SIGHUP --> HUP --> RELOAD
SIGTERM --> TERM --> GRACE
SIGQUIT --> QUIT --> IMMEDIATE
SIGCHLD --> CHLD --> REAP
classDef blue fill:#74b9ff,stroke:#333,rx:10,ry:10
classDef green fill:#55efc4,stroke:#333,rx:10,ry:10
classDef salmon fill:#fab1a0,stroke:#333,rx:10,ry:10
classDef orange fill:#ff9f43,stroke:#333,rx:10,ry:10
The sighup_cb() handler reloads volumes, re-registers with Zeroconf, and forwards SIGHUP to afpd and cnid_metad:
static void sighup_cb(evutil_socket_t fd, short what, void *arg)
{
LOG(log_note, logtype_afpd,
"Received SIGHUP, sending all processes signal to reload config");
load_volumes(&obj, LV_ALL);
if (!(obj.options.flags & OPTION_NOZEROCONF)) {
zeroconf_deregister();
zeroconf_register(&obj);
LOG(log_note, logtype_default, "Re-registered with Zeroconf");
}
kill_childs(SIGHUP, &afpd_pid, &cnid_metad_pid, NULL);
}When afpd receives SIGHUP, its own afp_goaway() handler in etc/afpd/main.c sets reloadconfig = 1, which triggers a full re-parse of afp.conf in the parent's main poll loop.
The sigterm_cb() handler initiates a timed graceful shutdown with a KILL_GRACETIME of 5 seconds:
static void sigterm_cb(evutil_socket_t fd, short what, void *arg)
{
sigset_t sigs;
struct timeval tv;
LOG(log_info, logtype_afpd, "Exiting on SIGTERM");
if (in_shutdown)
return;
in_shutdown = 1;
sigfillset(&sigs);
sigdelset(&sigs, SIGCHLD);
sigprocmask(SIG_SETMASK, &sigs, NULL);
tv.tv_sec = KILL_GRACETIME;
tv.tv_usec = 0;
event_base_loopexit(base, &tv);
event_del(sigterm_ev);
event_del(sigquit_ev);
event_del(sighup_ev);
event_del(timer_ev);
#ifdef WITH_SPOTLIGHT
system(INDEXER_COMMAND " -t");
#endif
kill_childs(SIGTERM, &afpd_pid, &cnid_metad_pid, &dbus_pid, NULL);
}After the event loop exits (either all children exited or timeout), main() checks for stragglers and sends SIGKILL:
if (service_running(afpd_pid) || service_running(cnid_metad_pid)
|| service_running(dbus_pid)) {
// Log which services didn't shut down...
kill_childs(SIGKILL, &afpd_pid, &cnid_metad_pid, &dbus_pid, NULL);
}The sigquit_cb() handler sends SIGQUIT directly to all children without the graceful shutdown sequence:
static void sigquit_cb(evutil_socket_t fd, short what, void *arg)
{
LOG(log_note, logtype_afpd, "Exiting on SIGQUIT");
#ifdef WITH_SPOTLIGHT
system(INDEXER_COMMAND " -t");
#endif
kill_childs(SIGQUIT, &afpd_pid, &cnid_metad_pid, &dbus_pid, NULL);
}The sigchld_cb() handler uses a waitpid() loop to reap all terminated children, identifies which service exited, and sets its PID to NETATALK_SRV_ERROR (which equals NETATALK_SRV_NEEDED, triggering restart by timer_cb()):
static void sigchld_cb(evutil_socket_t fd, short what, void *arg)
{
int status;
pid_t pid;
while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
if (WIFEXITED(status)) {
if (WEXITSTATUS(status))
LOG(log_info, logtype_default, "child[%d]: exited %d",
pid, WEXITSTATUS(status));
else
LOG(log_info, logtype_default, "child[%d]: done", pid);
} else {
if (WIFSIGNALED(status))
LOG(log_info, logtype_default, "child[%d]: killed by signal %d",
pid, WTERMSIG(status));
else
LOG(log_info, logtype_default, "child[%d]: died", pid);
}
if (pid == afpd_pid)
afpd_pid = NETATALK_SRV_ERROR;
else if (pid == cnid_metad_pid)
cnid_metad_pid = NETATALK_SRV_ERROR;
else if (pid == dbus_pid)
dbus_pid = NETATALK_SRV_ERROR;
else
LOG(log_error, logtype_afpd, "Bad pid: %d", pid);
}
if (in_shutdown
&& !service_running(afpd_pid)
&& !service_running(cnid_metad_pid)
&& !service_running(dbus_pid)) {
event_base_loopbreak(base);
}
}During shutdown, once all three services have exited, event_base_loopbreak() immediately terminates the event loop, bypassing the KILL_GRACETIME timeout.
%%{ init: { 'themeVariables': { 'fontSize': '14px' }, 'flowchart': { 'nodeSpacing': 15, 'rankSpacing': 40 } } }%%
sequenceDiagram
participant SYS as System / Init
participant NM as netatalk
participant AFP as afpd
participant CNID as cnid_metad
participant DBUS as dbus-daemon
SYS->>NM: SIGTERM
NM->>NM: in_shutdown = 1
NM->>NM: Block all signals except SIGCHLD
NM->>NM: event_base_loopexit(KILL_GRACETIME=5s)
NM->>NM: Stop Spotlight indexer (if enabled)
NM->>AFP: SIGTERM
NM->>CNID: SIGTERM
NM->>DBUS: SIGTERM
alt All children exit within 5s
AFP-->>NM: SIGCHLD (exit)
CNID-->>NM: SIGCHLD (exit)
DBUS-->>NM: SIGCHLD (exit)
NM->>NM: event_base_loopbreak()
else Timeout after 5s
NM->>NM: Event loop exits
NM->>AFP: SIGKILL (if still running)
NM->>CNID: SIGKILL (if still running)
NM->>DBUS: SIGKILL (if still running)
end
NM->>NM: server_unlock(PATH_NETATALK_LOCK)
NM->>NM: exit()
At startup, main() calls afp_config_parse() (declared in include/atalk/netatalk_conf.h, implemented in libatalk/util/netatalk_conf.c) with "netatalk" as the process name, then load_volumes() with LV_ALL to load all volume definitions:
if (afp_config_parse(&obj, "netatalk") != 0)
netatalk_exit(EXITERR_CONF);
load_volumes(&obj, LV_ALL);The global AFPObj obj (defined in include/atalk/globals.h) holds parsed configuration. Key fields used by the controller:
| Field | Type | Purpose |
|---|---|---|
obj.options.configfile |
char * |
Path to afp.conf
|
obj.options.flags |
int |
Bitmask: OPTION_NOZEROCONF, OPTION_SPOTLIGHT, etc. |
obj.iniconfig |
dictionary * |
Parsed INI file for INIPARSER_GETSTR() lookups |
The configuration file path is passed to child processes via the -F flag, so each child parses afp.conf independently with afp_config_parse() using its own process name.
When WITH_SPOTLIGHT is compiled in and OPTION_SPOTLIGHT is set in obj.options.flags, main() configures the D-Bus session environment and Tracker/LocalSearch indexer:
if (obj.options.flags & OPTION_SPOTLIGHT) {
setenv("DBUS_SESSION_BUS_ADDRESS", "unix:path=" _PATH_STATEDIR "spotlight.ipc", 1);
setenv("XDG_DATA_HOME", _PATH_STATEDIR, 0);
setenv("XDG_CACHE_HOME", _PATH_STATEDIR, 0);
setenv("TRACKER_USE_LOG_FILES", "1", 0);
dbus_path = INIPARSER_GETSTR(obj.iniconfig, INISEC_GLOBAL, "dbus daemon",
DBUS_DAEMON_PATH);
// ... launch dbus, sleep(1), set_sl_volumes(), start indexer
}The set_sl_volumes() function iterates all loaded volumes. For each volume with the AFPVOL_SPOTLIGHT flag, it builds a comma-separated path list and configures the indexer via gsettings:
cmd = bformat("gsettings set " INDEXER_DBUS_NAME
" index-recursive-directories \"[%s]\"",
bdata(volnamelist) ? bdata(volnamelist) : "");The INDEXER_DBUS_NAME and INDEXER_COMMAND macros are set in meson.build depending on whether LocalSearch3 or Tracker3 is available.
The Zeroconf subsystem uses a facade pattern. afp_zeroconf.c delegates to either afp_avahi.c or afp_mdns.c:
void zeroconf_register(const AFPObj *configs)
{
#if defined (HAVE_MDNS)
md_zeroconf_register(configs);
#elif defined (HAVE_AVAHI)
av_zeroconf_register(configs);
#endif
}Three DNS-SD service types are defined in afp_zeroconf.h:
| Constant | Value | Purpose |
|---|---|---|
AFP_DNS_SERVICE_TYPE |
"_afpovertcp._tcp" |
AFP file sharing discovery |
ADISK_SERVICE_TYPE |
"_adisk._tcp" |
Time Machine discovery |
DEV_INFO_SERVICE_TYPE |
"_device-info._tcp" |
Device model advertisement |
Registration occurs after all services are started but before entering the event loop. On SIGHUP, services are deregistered and re-registered to reflect configuration changes.
The netatalk controller and afpd are separate processes with distinct responsibilities:
| Aspect | netatalk | afpd |
|---|---|---|
| Role | Service controller / supervisor | AFP protocol server |
| Source | etc/netatalk/netatalk.c |
etc/afpd/main.c |
| Event loop | libevent2 event_base_dispatch()
|
poll() loop in main()
|
| Child management | Manages afpd, cnid_metad, dbus | Manages per-session fork children via server_child_alloc()
|
| Config parsing | afp_config_parse(&obj, "netatalk") |
afp_config_parse(&dsi_obj, "afpd") |
| SIGHUP handling | Reloads volumes, re-registers Zeroconf, forwards SIGHUP | Sets reloadconfig=1, re-parses afp.conf, reinitializes listening sockets, forwards SIGHUP to child workers |
When netatalk launches afpd:
-
netatalkcallsrun_process(_PATH_AFPD, "-d", "-F", configfile) -
afpdruns in foreground mode (-d), parses its own copy ofafp.conf -
afpdenters its ownpoll()loop, accepting DSI/ASP connections and forking session children - If
afpdcrashes,netatalk'ssigchld_cb()reaps it and setsafpd_pid = NETATALK_SRV_ERROR - On the next 1-second timer tick,
timer_cb()restartsafpd
cnid_metad is launched by netatalk only when CNID_BACKEND_DBD is compiled in. It acts as a connection broker between afpd workers and per-volume cnid_dbd processes, as documented in the cnid_metad.c file header:
via TCP socket
1. afpd -------> cnid_metad
via UNIX domain socket
2. cnid_metad -------> cnid_dbd
passes afpd client fd
3. cnid_metad -------> cnid_dbd
Result:
via TCP socket
4. afpd -------> cnid_dbd
This means cnid_metad only brokers the initial connection — subsequent CNID operations go directly between afpd and cnid_dbd.
The netatalk daemon integrates with every major init system via platform-specific scripts in distrib/initscripts/. All init scripts start the same netatalk binary, which then manages child processes internally.
distrib/initscripts/systemd.netatalk.service.in:
[Unit]
Description=Netatalk AFP fileserver
Documentation=man:afp.conf(5) man:netatalk(8) man:afpd(8) man:cnid_metad(8) man:cnid_dbd(8)
After=network-online.target avahi-daemon.service atalkd.service
[Service]
Type=forking
GuessMainPID=no
ExecStart=@sbindir@/netatalk
PIDFile=@lockfile_path@/netatalk
ExecReload=/bin/kill -HUP $MAINPID
Restart=always
RestartSec=1
[Install]
WantedBy=multi-user.targetKey details: Type=forking because netatalk calls daemonize() (unless -d). GuessMainPID=no with explicit PIDFile. ExecReload sends SIGHUP for live config reload. Restart=always provides systemd-level restart on top of netatalk's internal child restart.
distrib/initscripts/openrc.netatalk.in:
#!/sbin/openrc-run
extra_started_commands="reload"
name=$RC_SVCNAME
command="@sbindir@/${RC_SVCNAME}"
pidfile=@lockfile_path@
depend() {
need net
use logger dns
after atalkd
after avahi-daemon
after firewall
}
reload() {
ebegin "Reloading $name"
start-stop-daemon --signal SIGHUP --name $command
eend $?
}distrib/initscripts/debian.netatalk.in provides LSB-compliant init with start, stop, restart, force-reload, and status commands using start-stop-daemon.
distrib/initscripts/freebsd.netatalk.in uses rc.subr with a custom reload command that sends SIGHUP:
netatalk_reload()
{
echo 'Reloading netatalk.'
kill -HUP $rc_pid
}distrib/initscripts/netbsd.netatalk.in: minimal rc.subr script with required_files="$etcdir/afp.conf".
distrib/initscripts/openbsd.netatalk.in: minimal rc.subr script.
distrib/initscripts/macos.netatalk.plist.in:
<key>Label</key>
<string>io.netatalk.daemon</string>
<key>ProgramArguments</key>
<array>
<string>@sbindir@/netatalk</string>
<string>-d</string>
</array>
<key>RunAtLoad</key>
<true/>Note: macOS uses -d (foreground mode) since launchd manages the process lifecycle. The companion startup script macos.netatalk.in provides StartService, StopService, and RestartService functions, waiting for network availability before launch.
distrib/initscripts/solaris.netatalk.xml.in: SMF manifest with network/netatalk service name, dependencies on network and filesystem, and optional mdns dependency. Stop method uses :kill (SIGTERM).
| Init System | Script | Reload Method | Service Type |
|---|---|---|---|
| systemd | systemd.netatalk.service.in |
kill -HUP $MAINPID |
Type=forking |
| OpenRC | openrc.netatalk.in |
start-stop-daemon --signal SIGHUP |
Standard daemon |
| SysV | debian.netatalk.in |
force-reload (full restart) |
start-stop-daemon |
| FreeBSD rc.d | freebsd.netatalk.in |
kill -HUP $rc_pid |
rc.subr |
| NetBSD rc.d | netbsd.netatalk.in |
— | rc.subr |
| OpenBSD rc.d | openbsd.netatalk.in |
— | rc.subr |
| macOS launchd | macos.netatalk.plist.in |
— |
RunAtLoad, foreground -d
|
| Solaris SMF | solaris.netatalk.xml.in |
— | duration=contract |
The netatalk binary is built from etc/netatalk/meson.build:
netatalk_sources = ['afp_avahi.c', 'afp_mdns.c', 'afp_zeroconf.c', 'netatalk.c']
netatalk_deps = [bstring, iniparser, libevent]
executable(
'netatalk',
netatalk_sources,
include_directories: root_includes,
link_with: libatalk,
dependencies: netatalk_deps,
c_args: [confdir, statedir, afpd, cnid_metad, dversion],
install: true,
install_dir: sbindir,
)The c_args inject compile-time path defines from the top-level meson.build:
| Define | Value | Used For |
|---|---|---|
_PATH_AFPD |
sbindir/afpd |
Launching afpd |
_PATH_CNID_METAD |
sbindir/cnid_metad |
Launching cnid_metad |
_PATH_CONFDIR |
pkgconfdir/ |
Finding afp.conf, dbus-session.conf |
_PATH_STATEDIR |
localstatedir/netatalk/ |
D-Bus socket, Tracker state |
VERSION |
netatalk_version |
Version display |
PATH_NETATALK_LOCK |
lockfile_path/netatalk |
PID file / lock file |
| Flag | Effect | Build Source |
|---|---|---|
CNID_BACKEND_DBD |
Enables cnid_metad management | BDB dependency in meson.build
|
WITH_SPOTLIGHT |
Enables D-Bus + indexer management | Spotlight option in meson.build
|
HAVE_AVAHI |
Selects Avahi for Zeroconf | Avahi dependency check |
HAVE_MDNS |
Selects mDNSResponder for Zeroconf | DNS-SD dependency check |
DBUS_DAEMON_PATH |
Path to dbus-daemon binary | Auto-detected in meson.build
|
INDEXER_COMMAND |
Tracker/LocalSearch daemon command | Auto-detected in meson.build
|
INDEXER_DBUS_NAME |
D-Bus name for indexer service | Auto-detected in meson.build
|
Resources
- Getting Started
- FAQ
- Troubleshooting
- Connect to AFP Server
- Webmin Module
- Benchmarks
- Interoperability with Samba
OS Specific Guides
- Installing Netatalk on Alpine Linux
- Installing Netatalk on Debian Linux
- Installing Netatalk on Fedora Linux
- Installing Netatalk on FreeBSD
- Installing Netatalk on macOS
- Installing Netatalk on NetBSD
- Installing Netatalk on OmniOS
- Installing Netatalk on OpenBSD
- Installing Netatalk on OpenIndiana
- Installing Netatalk on openSUSE
- Installing Netatalk on Raspberry Pi OS
- Installing Netatalk on Solaris
- Installing Netatalk on Ubuntu
Tech Notes
- Capturing AFP network traffic
- Kerberos
- Special Files and Folders
- Spotlight
- MySQL CNID Backend
- Slow AFP read performance
- Limiting Time Machine volumes
- Netatalk and ZFS nbmand property
Retro AFP
Development