Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions cmdline/cmdline_afp.c
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ static struct afp_url url;
static int cmdline_log_min_rank = 2; /* Default rank: notice */
static int verbose_mode = 0;
static char connect_servername[AFP_SERVER_NAME_UTF8_LEN];
static int cmdline_dsi_timeout = 0;

int full_url = 0;

Expand Down Expand Up @@ -156,7 +157,7 @@ static int reconnect_session(int restore_volume, int restore_dir)
}

if (afp_sl_connect(&reconnect_url, uam_mask, &new_server_id, mesg,
&error) != 0) {
&error, cmdline_dsi_timeout) != 0) {
return -1;
}

Expand Down Expand Up @@ -2125,6 +2126,11 @@ void cmdline_set_verbose(int verbose)
verbose_mode = verbose;
}

void cmdline_set_dsi_timeout(int timeout)
{
cmdline_dsi_timeout = timeout;
}

static void cmdline_log_for_client(__attribute__((unused)) void * priv,
__attribute__((unused)) enum logtypes logtype,
int loglevel, const char *message)
Expand Down Expand Up @@ -2165,7 +2171,8 @@ static int cmdline_server_startup(int batch_mode)
strlcpy(connect_servername, url.servername, sizeof(connect_servername));
}

if (afp_sl_connect(&url, uam_mask, &server_id, mesg, &error)) {
if (afp_sl_connect(&url, uam_mask, &server_id, mesg, &error,
cmdline_dsi_timeout)) {
printf("Could not connect to server\n");
return -1;
}
Expand Down
1 change: 1 addition & 0 deletions cmdline/cmdline_afp.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ int cmdline_afp_setup(int recursive, int batch_mode, char * url_string);
void cmdline_afp_setup_client(void);
void cmdline_set_log_level(int loglevel);
void cmdline_set_verbose(int verbose);
void cmdline_set_dsi_timeout(int timeout);
int cmdline_batch_transfer(char * local_path, int direction, int recursive);
char *afp_remote_file_generator(const char *text, int state);

Expand Down
9 changes: 8 additions & 1 deletion cmdline/cmdline_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -477,11 +477,13 @@
int verbose = 0;
int show_usage = 0;
int log_level = LOG_NOTICE;
int dsi_timeout = 0;
struct option long_options[] = {
{"help", 0, 0, 'h'},
{"recursive", 0, 0, 'r'},
{"verbose", 0, 0, 'V'},
{"loglevel", 1, 0, 'v'},
{"timeout", 1, 0, 't'},

@augmentcode augmentcode Bot Apr 18, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

usage() output here still doesn’t mention the new -t/--timeout option, so -h help is now incomplete compared to the manpage and the actual supported flags.

Severity: low

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

{NULL, 0, NULL, 0},
};
char *url = NULL;
Expand All @@ -490,7 +492,7 @@
int direction = 0; /* 0 = GET (remote->local), 1 = PUT (local->remote) */

while (1) {
c = getopt_long(argc, argv, "hrVv:",
c = getopt_long(argc, argv, "hrVv:t:",
long_options, &option_index);

if (c == -1) {
Expand All @@ -510,6 +512,10 @@
verbose = 1;
break;

case 't':
dsi_timeout = strtol(optarg, NULL, 10);

Check warning on line 516 in cmdline/cmdline_main.c

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

implicit conversion loses integer precision: 'long' to 'int'

See more on https://sonarcloud.io/project/issues?id=Netatalk_afpfs-ng&issues=AZ2gRJkIIF1tYctof0BL&open=AZ2gRJkIIF1tYctof0BL&pullRequest=204
break;

case 'v': {
int parsed_loglevel;

Expand Down Expand Up @@ -538,6 +544,7 @@
cmdline_afp_setup_client();
cmdline_set_log_level(log_level);
cmdline_set_verbose(verbose);
cmdline_set_dsi_timeout(dsi_timeout);

/* Check arguments for batch mode */
if (argc - optind == 2) {
Expand Down
4 changes: 4 additions & 0 deletions daemon/commands.c
Original file line number Diff line number Diff line change
Expand Up @@ -1862,6 +1862,10 @@ static int process_connect(struct daemon_client * c)
goto error;
}

if (req->dsi_timeout > 0) {
s->dsi_default_timeout = req->dsi_timeout;
}

/* Immediately copy data from server before it can be freed asynchronously */
server_copy = s;

Expand Down
3 changes: 2 additions & 1 deletion daemon/stateless.c
Original file line number Diff line number Diff line change
Expand Up @@ -1192,7 +1192,7 @@ int afp_sl_getvols(struct afp_url * url, unsigned int start,
*/

int afp_sl_connect(struct afp_url * url, unsigned int uam_mask,
serverid_t *id, char *loginmesg, int *error)
serverid_t *id, char *loginmesg, int *error, int dsi_timeout)
{
struct afp_server_connect_request req;
const struct afp_server_connect_response *resp;
Expand All @@ -1208,6 +1208,7 @@ int afp_sl_connect(struct afp_url * url, unsigned int uam_mask,
req.header.command = AFP_SERVER_COMMAND_CONNECT;
memcpy(&req.url, url, sizeof(struct afp_url));
req.uam_mask = uam_mask;
req.dsi_timeout = dsi_timeout;

if (send_command(sizeof(req), (char *)&req, AFP_SERVER_COMMAND_CONNECT) < 0) {
return AFP_SERVER_RESULT_DAEMON_ERROR;
Expand Down
7 changes: 6 additions & 1 deletion docs/manpages/afp_client.1
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.Dd January 25, 2026
.Dd April 18, 2026
.Dt AFP_CLIENT 1
.Os afpfs-ng
.Sh NAME
Expand Down Expand Up @@ -143,6 +143,11 @@ as the volume password, you will be prompted for it.
.It Fl u , Fl -user Ar username
Authenticate with the AFP server as
.Ar username .
.It Fl t , Fl -timeout Ar seconds
Override the default DSI request timeout.
If not specified, the timeout is selected automatically based on the
server type: Time Capsule servers default to 30 seconds to accommodate
spinning disk wake-up latency; all other server types default to 5 seconds.
.It Fl v , Fl -afpversion Ar afp_version
Specify the AFP protocol version that will be used for a mount.
By default afpfs-ng will choose the highest AFP version shared between
Expand Down
7 changes: 6 additions & 1 deletion docs/manpages/afpcmd.1
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.Dd January 26, 2026
.Dd April 18, 2026
.Dt AFPCMD 1
.Os afpfs-ng
.Sh NAME
Expand Down Expand Up @@ -50,6 +50,11 @@ Enables verbose mode for file transfers.
When enabled, displays detailed messages during upload and download operations,
including per-file transfer statistics.
By default, only a summary message is shown after each transfer completes.
.It Fl t , Fl -timeout Ar seconds
Override the default DSI request timeout.
If not specified, the timeout is selected automatically based on the
server type: Time Capsule servers default to 30 seconds to accommodate
spinning disk wake-up latency; all other server types default to 5 seconds.
.It Fl v , Fl -loglevel Ar level
Sets the log verbosity level.
Accepted values are
Expand Down
8 changes: 7 additions & 1 deletion fuse/client.c
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,7 @@
" -m, --map <mapname> : use this uid/gid mapping method, one of:\n"
" common, loginids\n"
" -O, --options <flags> : FUSE mount options; see the fuse man page\n"
" -t, --timeout <seconds> : DSI request timeout (overrides server-type default)\n"
"\n"
" unmount <mountpoint> : unmount the specified mountpoint\n"
" status [mountpoint] : get status of the AFP daemon;\n"
Expand Down Expand Up @@ -577,6 +578,7 @@
{"uam", 1, 0, 'a'},
{"map", 1, 0, 'm'},
{"options", 1, 0, 'O'},
{"timeout", 1, 0, 't'},
{0, 0, 0, 0},
};

Expand All @@ -593,7 +595,7 @@

while (1) {
optnum++;
c = getopt_long(argc, argv, "a:m:O:o:P:p:u:v:", long_options, &option_index);
c = getopt_long(argc, argv, "a:m:O:o:P:p:t:u:v:", long_options, &option_index);

if (c == -1) {
break;
Expand Down Expand Up @@ -635,6 +637,10 @@
snprintf(request.url.password, AFP_MAX_PASSWORD_LEN, "%s", optarg);
break;

case 't':
request.dsi_timeout = strtol(optarg, NULL, 10);

Check warning on line 641 in fuse/client.c

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

implicit conversion loses integer precision: 'long' to 'int'

See more on https://sonarcloud.io/project/issues?id=Netatalk_afpfs-ng&issues=AZ2gRJkdIF1tYctof0BM&open=AZ2gRJkdIF1tYctof0BM&pullRequest=204

@augmentcode augmentcode Bot Apr 18, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--timeout is parsed via strtol(optarg, NULL, 10) without checking endptr/errno/range, so invalid or overflow inputs can silently turn into 0/garbage and be ignored or misapplied. This is user-facing and can make it hard to tell whether the override was accepted.

Severity: low

Other Locations
  • cmdline/cmdline_main.c:516

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

break;

case 'u':
snprintf(request.url.username, AFP_MAX_USERNAME_LEN, "%s", optarg);
break;
Expand Down
4 changes: 4 additions & 0 deletions fuse/commands.c
Original file line number Diff line number Diff line change
Expand Up @@ -642,6 +642,10 @@ static int process_mount(struct fuse_client * c)
goto error;
}

if (req.dsi_timeout > 0) {
s->dsi_default_timeout = req.dsi_timeout;
}

if ((volume = mount_volume(c, s, req.url.volumename,
req.url.volpassword)) == NULL) {
goto error;
Expand Down
1 change: 1 addition & 0 deletions fuse/fuse_ipc.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ struct afp_server_mount_request {
unsigned int map;
int changeuid;
char fuse_options[256];
int dsi_timeout;
};

struct afp_server_status_request {
Expand Down
3 changes: 3 additions & 0 deletions include/afp.h
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,9 @@ struct afp_server {

unsigned int tx_delay;

/* DSI request timeout in seconds; set by server type detection or user override */
int dsi_default_timeout;

@augmentcode augmentcode Bot Apr 18, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding dsi_default_timeout to the public struct afp_server changes the struct layout/size, which is an ABI break for any external consumers that access this struct from libafpclient (not just source-level API changes). Can you confirm the project intends this ABI change (e.g., via SONAME bump / documented ABI versioning)?

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.


/* Connection information */
//the linked list returned by getaddrinfo
struct addrinfo *address;
Expand Down
1 change: 1 addition & 0 deletions include/afp_server.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ struct afp_server_connect_request {
struct afp_server_request_header header;
struct afp_url url;
unsigned int uam_mask;
int dsi_timeout;
};

struct afp_server_connect_response {
Expand Down
2 changes: 1 addition & 1 deletion include/afpsl.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ int afp_sl_exit(void);
int afp_sl_status(const char * volumename, const char * servername,
char *text, unsigned int *remaining);
int afp_sl_connect(struct afp_url * url, unsigned int uam_mask,
serverid_t *id, char *loginmesg, int *error);
serverid_t *id, char *loginmesg, int *error, int dsi_timeout);
int afp_sl_disconnect(serverid_t *id);
int afp_sl_getvolid(struct afp_url * url, volumeid_t *volid);
int afp_sl_attach(struct afp_url * url, unsigned int volume_options,
Expand Down
5 changes: 3 additions & 2 deletions include/dsi.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,11 @@ int dsi_recv(struct afp_server * server);
#define DSI_BLOCK_TIMEOUT -1
#define DSI_DONT_WAIT 0
#define DSI_DEFAULT_TIMEOUT 5
//a spun down time capsule can take up to 20 secs to
//wake up and reply to a mount request
/* A spun down time capsule can take up to 20 secs to
* wake up and reply to a mount request */
#define DSI_OPENVOLUME_TIMEOUT 20
#define DSI_LOGIN_TIMEOUT 20
#define DSI_TIMECAPSULE_DEFAULT_TIMEOUT 30

#define GETSTATUS_BUF_SIZE 1024

Expand Down
1 change: 1 addition & 0 deletions lib/afp.c
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,7 @@ struct afp_server *afp_server_init(struct addrinfo * address)
s->attention_quantum = AFP_DEFAULT_ATTENTION_QUANTUM;
s->attention_buffer = malloc(s->attention_quantum);
s->attention_len = 0;
s->dsi_default_timeout = DSI_DEFAULT_TIMEOUT;
s->connect_state = SERVER_STATE_DISCONNECTED;
s->address = address;
/* Initialize mutexes */
Expand Down
2 changes: 1 addition & 1 deletion lib/dsi.c
Original file line number Diff line number Diff line change
Expand Up @@ -761,7 +761,7 @@
if (checkmessage) {
afp_getsrvrmsg(server, AFPMESG_SERVER,
((server->using_version && server->using_version->av_number >= 30) ? 1 : 0),
DSI_DEFAULT_TIMEOUT, mesg);
server->dsi_default_timeout, mesg);

Check warning on line 764 in lib/dsi.c

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

implicit conversion loses integer precision: 'int' to 'unsigned char'

See more on https://sonarcloud.io/project/issues?id=Netatalk_afpfs-ng&issues=AZ2gRJgmIF1tYctof0BH&open=AZ2gRJgmIF1tYctof0BH&pullRequest=204

if (bcmp(mesg, "The server is going down for maintenance.", 41) == 0) {
shutdown = 1;
Expand Down
5 changes: 5 additions & 0 deletions lib/identify.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#include <string.h>
#include "afp.h"
#include "dsi.h"

/*
* afp_server_identify()
Expand Down Expand Up @@ -43,6 +44,10 @@ void afp_server_identify(struct afp_server * s)
"Identified server %s as Time Capsule",
s->server_name_printable);
s->server_type = AFPFS_SERVER_TYPE_TIMECAPSULE;

if (s->dsi_default_timeout == DSI_DEFAULT_TIMEOUT) {

@augmentcode augmentcode Bot Apr 18, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using s->dsi_default_timeout == DSI_DEFAULT_TIMEOUT as the “not user overridden” sentinel can’t distinguish between the default 5s and an explicit user choice of 5s, so an explicit 5s set before afp_server_identify() could still be overridden to 30s for Time Capsule.

Severity: low

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

s->dsi_default_timeout = DSI_TIMECAPSULE_DEFAULT_TIMEOUT;
}
} else {
log_for_client(NULL, AFPFSD, LOG_DEBUG,
"Could not identify server %s (machine type %s)",
Expand Down
12 changes: 8 additions & 4 deletions lib/proto_attr.c
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ int afp_listextattr(struct afp_volume * volume,
request_packet->maxreplysize = htonl(info->maxsize);
copy_path(server, pathptr, pathname, strlen(pathname));
unixpath_to_afppath(server, pathptr);
ret = dsi_send(server, (char *) request_packet, len, DSI_DEFAULT_TIMEOUT,
ret = dsi_send(server, (char *) request_packet, len,
server->dsi_default_timeout,
afpListExtAttrs, (void *) info);
free(msg);
}
Expand Down Expand Up @@ -189,7 +190,8 @@ int afp_getextattr(struct afp_volume * volume, unsigned int dirid,
/* EA name: length-prefixed (2 bytes length + name) */
*((uint16_t *)p2) = htons(namelen);
memcpy(p2 + 2, name, namelen);
ret = dsi_send(server, (char *) request_packet, len, DSI_DEFAULT_TIMEOUT,
ret = dsi_send(server, (char *) request_packet, len,
server->dsi_default_timeout,
afpGetExtAttr, (void *) i);
free(msg);
}
Expand Down Expand Up @@ -265,7 +267,8 @@ int afp_setextattr(struct afp_volume * volume, unsigned int dirid,
memcpy(p2, attribdata, attribdatalen);
}

ret = dsi_send(server, (char *) request_packet, len, DSI_DEFAULT_TIMEOUT,
ret = dsi_send(server, (char *) request_packet, len,
server->dsi_default_timeout,
afpSetExtAttr, NULL);
free(msg);
}
Expand Down Expand Up @@ -324,7 +327,8 @@ int afp_removeextattr(struct afp_volume * volume, unsigned int dirid,
/* EA name: length-prefixed (2 bytes length + name) */
*((uint16_t *)p2) = htons(namelen);
memcpy(p2 + 2, name, namelen);
ret = dsi_send(server, (char *) request_packet, len, DSI_DEFAULT_TIMEOUT,
ret = dsi_send(server, (char *) request_packet, len,
server->dsi_default_timeout,
afpRemoveExtAttr, NULL);
free(msg);
}
Expand Down
12 changes: 7 additions & 5 deletions lib/proto_desktop.c
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
request_packet.pad2 = 0;
request_packet.length = htons(length);
return dsi_send(volume->server, (char *)&request_packet,
sizeof(request_packet), DSI_DEFAULT_TIMEOUT,
sizeof(request_packet), volume->server->dsi_default_timeout,
afpGetIcon, (void *) icon);
}

Expand Down Expand Up @@ -108,7 +108,8 @@

copy_to_pascal(p, comment);
*size = strlen(comment);
rc = dsi_send(volume->server, (char *)msg, len, DSI_DEFAULT_TIMEOUT,
rc = dsi_send(volume->server, (char *)msg, len,
volume->server->dsi_default_timeout,
afpAddComment, (void *) comment);
free(msg);
return rc;
Expand Down Expand Up @@ -140,7 +141,8 @@
request_packet->dirid = htonl(did);
copy_path(volume->server, path, pathname, strlen(pathname));
unixpath_to_afppath(volume->server, path);
rc = dsi_send(volume->server, (char *)msg, len, DSI_DEFAULT_TIMEOUT,
rc = dsi_send(volume->server, (char *)msg, len,

Check warning on line 144 in lib/proto_desktop.c

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this redundant cast.

See more on https://sonarcloud.io/project/issues?id=Netatalk_afpfs-ng&issues=AZ2gRJi6IF1tYctof0BJ&open=AZ2gRJi6IF1tYctof0BJ&pullRequest=204
volume->server->dsi_default_timeout,
afpGetComment, (void *) comment);
free(msg);
return rc;
Expand Down Expand Up @@ -184,7 +186,7 @@
request_packet.pad = 0;
request_packet.refnum = htons(refnum);
return dsi_send(server, (char *) &request_packet,
sizeof(request_packet), DSI_DEFAULT_TIMEOUT, afpCloseDT, NULL);
sizeof(request_packet), server->dsi_default_timeout, afpCloseDT, NULL);
}


Expand All @@ -204,7 +206,7 @@
request_packet.pad = 0;
request_packet.volid = htons(volume->volid);
return dsi_send(volume->server, (char *) &request_packet,
sizeof(request_packet), DSI_DEFAULT_TIMEOUT, afpOpenDT,
sizeof(request_packet), volume->server->dsi_default_timeout, afpOpenDT,
(void *) refnum);
}

Expand Down
Loading