nwconn: route AFP access timestamps through atime
All checks were successful
Source release / source-package (push) Successful in 48s

This commit is contained in:
Mario Fetka
2026-05-30 17:25:15 +00:00
parent b442500ef9
commit 31a9234c8b
5 changed files with 134 additions and 24 deletions

View File

@@ -840,6 +840,24 @@ The report showed `bitmap=0x2000`, `backup=0x576eb9aa`, and the backing xattr
`1700000000`.
### AFP Access Date/Time smoke
`afp_set_file_info_smoke` supports the WebSDK Access Date/Time request bitmap
`0x0400` via:
```sh
./afp_set_file_info_smoke \
-S MARS -U SUPERVISOR -P secret \
--access-time-only --access-time-epoch 1700000000 \
SYS:PUBLIC/pmdflts.ini
```
The server stores Access Date/Time through the existing POSIX `st_atime` path,
preserving `st_mtime` with `utime()` and enforcing trustee Modify rights before
changing the timestamp. The AFP file-information record exposes the Access
Date at offset 22; no AFP-specific xattr is added for this NetWare-semantic
timestamp.
### AFP Create Date/Time smoke
`afp_set_file_info_smoke` supports the WebSDK Create Date/Time request bitmap

View File

@@ -24,6 +24,7 @@
#define AFP_SET_FILE_INFORMATION 0x09
#define AFP20_SET_FILE_INFORMATION 0x10
#define AFP_ATTRIBUTES_MASK 0x0100
#define AFP_ACCESS_DATE_MASK 0x0400
#define AFP_CREATE_DATE_MASK 0x0800
#define AFP_MODIFY_DATE_MASK 0x1000
#define AFP_BACKUP_DATE_MASK 0x2000
@@ -43,7 +44,7 @@ static void usage(const char *prog)
"Usage: %s [--afp09|--afp20] [--expect-completion CODE] [--allow-invalid-namespace] [--allow-invalid-path] "
"[--volume N] [--entry-id ID] [--type FOUR] [--creator FOUR] "
"[--hidden|--clear-hidden|--invisible|--clear-invisible|--system|--clear-system|--archive|--clear-archive] "
"[--create-time-epoch SECONDS] [--mtime-epoch SECONDS] [--backup-time-epoch SECONDS] [--finder-info-only|--attributes-only|--create-time-only|--timestamp-only|--backup-time-only] "
"[--access-time-epoch SECONDS] [--create-time-epoch SECONDS] [--mtime-epoch SECONDS] [--backup-time-epoch SECONDS] [--finder-info-only|--attributes-only|--access-time-only|--create-time-only|--timestamp-only|--backup-time-only] "
"[ncpfs options] PATH\n"
"\n"
"ncpfs options are parsed by ncp_initialize(), for example:\n"
@@ -53,12 +54,13 @@ static void usage(const char *prog)
" %s -S MARS -U SUPERVISOR -P secret --type TEXT --creator MARS SYS:PUBLIC/pmdflts.ini\n"
" %s -S MARS -U SUPERVISOR -P secret --hidden --attributes-only SYS:PUBLIC/pmdflts.ini\n"
" %s -S MARS -U SUPERVISOR -P secret --archive --attributes-only SYS:PUBLIC/pmdflts.ini\n"
" %s -S MARS -U SUPERVISOR -P secret --access-time-epoch 1700000000 --access-time-only SYS:PUBLIC/pmdflts.ini\n"
" %s -S MARS -U SUPERVISOR -P secret --create-time-epoch 1700000000 --create-time-only SYS:PUBLIC/pmdflts.ini\n"
" %s -S MARS -U SUPERVISOR -P secret --mtime-epoch 1700000000 --timestamp-only SYS:PUBLIC/pmdflts.ini\n"
" %s -S MARS -U SUPERVISOR -P secret --backup-time-epoch 1700000000 --backup-time-only SYS:PUBLIC/pmdflts.ini\n"
" %s --allow-invalid-namespace -S MARS SYS:PUBLIC/pmdflts.ini\n"
" %s --expect-completion 0x8c -S MARS -U NOPASSUSER -n --finder-info-only SYS:PUBLIC/pmdflts.ini\n",
prog, prog, prog, prog, prog, prog, prog, prog, prog);
prog, prog, prog, prog, prog, prog, prog, prog, prog, prog);
}
static int parse_u32(const char *text, uint32_t *value)
@@ -174,6 +176,8 @@ int main(int argc, char **argv)
uint16_t attr_request = 0;
uint16_t attr_verify_mask = 0;
uint16_t attr_expected = 0;
uint8_t access_time[4];
int have_access_time = 0;
uint8_t create_time[4];
int have_create_time = 0;
uint8_t modify_time[4];
@@ -181,7 +185,7 @@ int main(int argc, char **argv)
uint8_t backup_time[4];
int have_backup_time = 0;
uint8_t verify_buf[AFP_REPLY_LEN];
uint8_t request[1 + 4 + 2 + 1 + 255 + 1 + 2 + 4 + 4 + 4 + 1 + 32];
uint8_t request[1 + 4 + 2 + 1 + 255 + 1 + 2 + 4 + 4 + 4 + 4 + 1 + 32];
size_t path_len;
size_t afp_data_off;
size_t data_off;
@@ -189,6 +193,7 @@ int main(int argc, char **argv)
NWCCODE err;
memset(finder_info, 0, sizeof(finder_info));
memset(access_time, 0, sizeof(access_time));
memset(create_time, 0, sizeof(create_time));
memset(modify_time, 0, sizeof(modify_time));
memset(backup_time, 0, sizeof(backup_time));
@@ -266,6 +271,15 @@ int main(int argc, char **argv)
attr_request = AFP_ATTR_ARCHIVE;
attr_verify_mask = AFP_ATTR_ARCHIVE;
attr_expected = 0;
} else if (!strcmp(argv[i], "--access-time-epoch")) {
uint32_t epoch;
if (++i >= argc || parse_u32(argv[i], &epoch) || epoch_to_nw_time(epoch, access_time)) {
fprintf(stderr, "invalid --access-time-epoch value\n");
ncp_close(conn);
return 2;
}
request_mask |= AFP_ACCESS_DATE_MASK;
have_access_time = 1;
} else if (!strcmp(argv[i], "--create-time-epoch")) {
uint32_t epoch;
if (++i >= argc || parse_u32(argv[i], &epoch) || epoch_to_nw_time(epoch, create_time)) {
@@ -294,17 +308,20 @@ int main(int argc, char **argv)
request_mask |= AFP_BACKUP_DATE_MASK;
have_backup_time = 1;
} else if (!strcmp(argv[i], "--attributes-only")) {
request_mask &= ~(AFP_CREATE_DATE_MASK | AFP_FINDER_INFO_MASK | AFP_MODIFY_DATE_MASK | AFP_BACKUP_DATE_MASK);
request_mask &= ~(AFP_ACCESS_DATE_MASK | AFP_CREATE_DATE_MASK | AFP_FINDER_INFO_MASK | AFP_MODIFY_DATE_MASK | AFP_BACKUP_DATE_MASK);
} else if (!strcmp(argv[i], "--finder-info-only")) {
request_mask &= ~(AFP_ATTRIBUTES_MASK | AFP_CREATE_DATE_MASK | AFP_MODIFY_DATE_MASK | AFP_BACKUP_DATE_MASK);
request_mask &= ~(AFP_ATTRIBUTES_MASK | AFP_ACCESS_DATE_MASK | AFP_CREATE_DATE_MASK | AFP_MODIFY_DATE_MASK | AFP_BACKUP_DATE_MASK);
} else if (!strcmp(argv[i], "--timestamp-only")) {
request_mask &= ~(AFP_ATTRIBUTES_MASK | AFP_CREATE_DATE_MASK | AFP_FINDER_INFO_MASK | AFP_BACKUP_DATE_MASK);
request_mask &= ~(AFP_ATTRIBUTES_MASK | AFP_ACCESS_DATE_MASK | AFP_CREATE_DATE_MASK | AFP_FINDER_INFO_MASK | AFP_BACKUP_DATE_MASK);
request_mask |= AFP_MODIFY_DATE_MASK;
} else if (!strcmp(argv[i], "--access-time-only")) {
request_mask &= ~(AFP_ATTRIBUTES_MASK | AFP_CREATE_DATE_MASK | AFP_FINDER_INFO_MASK | AFP_MODIFY_DATE_MASK | AFP_BACKUP_DATE_MASK);
request_mask |= AFP_ACCESS_DATE_MASK;
} else if (!strcmp(argv[i], "--create-time-only")) {
request_mask &= ~(AFP_ATTRIBUTES_MASK | AFP_FINDER_INFO_MASK | AFP_MODIFY_DATE_MASK | AFP_BACKUP_DATE_MASK);
request_mask &= ~(AFP_ATTRIBUTES_MASK | AFP_ACCESS_DATE_MASK | AFP_FINDER_INFO_MASK | AFP_MODIFY_DATE_MASK | AFP_BACKUP_DATE_MASK);
request_mask |= AFP_CREATE_DATE_MASK;
} else if (!strcmp(argv[i], "--backup-time-only")) {
request_mask &= ~(AFP_ATTRIBUTES_MASK | AFP_CREATE_DATE_MASK | AFP_FINDER_INFO_MASK | AFP_MODIFY_DATE_MASK);
request_mask &= ~(AFP_ATTRIBUTES_MASK | AFP_ACCESS_DATE_MASK | AFP_CREATE_DATE_MASK | AFP_FINDER_INFO_MASK | AFP_MODIFY_DATE_MASK);
request_mask |= AFP_BACKUP_DATE_MASK;
} else if (!strcmp(argv[i], "--type")) {
if (++i >= argc || copy_fourcc(argv[i], finder_info + 0)) {
@@ -368,6 +385,15 @@ int main(int argc, char **argv)
cpu_to_be16(attr_request, request + data_off);
data_off += 2;
}
if (request_mask & AFP_ACCESS_DATE_MASK) {
if (!have_access_time) {
fprintf(stderr, "--access-time-only requires --access-time-epoch\n");
ncp_close(conn);
return 2;
}
memcpy(request + data_off, access_time, sizeof(access_time));
data_off += sizeof(access_time);
}
if (request_mask & AFP_CREATE_DATE_MASK) {
if (!have_create_time) {
fprintf(stderr, "--create-time-only requires --create-time-epoch\n");
@@ -396,7 +422,7 @@ int main(int argc, char **argv)
data_off += sizeof(backup_time);
}
if ((request_mask & AFP_FINDER_INFO_MASK) &&
(request_mask & (AFP_ATTRIBUTES_MASK | AFP_CREATE_DATE_MASK | AFP_MODIFY_DATE_MASK | AFP_BACKUP_DATE_MASK)) &&
(request_mask & (AFP_ATTRIBUTES_MASK | AFP_ACCESS_DATE_MASK | AFP_CREATE_DATE_MASK | AFP_MODIFY_DATE_MASK | AFP_BACKUP_DATE_MASK)) &&
((data_off + 1) & 1))
data_off++;
if (request_mask & AFP_FINDER_INFO_MASK) {
@@ -459,6 +485,16 @@ int main(int argc, char **argv)
return 1;
}
if ((request_mask & AFP_ACCESS_DATE_MASK) &&
memcmp(verify_buf + 22, access_time, 2)) {
fprintf(stderr,
"AFP Set File Information access timestamp verify mismatch: path=%s got=0x%04x expected_date=0x%04x expected_time=0x%04x\n",
path, be16_to_cpu(verify_buf + 22),
be16_to_cpu(access_time + 0), be16_to_cpu(access_time + 2));
ncp_close(conn);
return 1;
}
if ((request_mask & AFP_CREATE_DATE_MASK) &&
memcmp(verify_buf + 20, create_time, 2)) {
fprintf(stderr,
@@ -498,9 +534,9 @@ int main(int argc, char **argv)
return 1;
}
printf("AFP Set File Info subfunction=0x%02x path=%s bitmap=0x%04x attrs=0x%04x create=0x%04x modify=0x%04x%04x backup=0x%04x%04x finder_type=%.4s finder_creator=%.4s entry_id=0x%08x verified\n",
printf("AFP Set File Info subfunction=0x%02x path=%s bitmap=0x%04x attrs=0x%04x create=0x%04x access=0x%04x modify=0x%04x%04x backup=0x%04x%04x finder_type=%.4s finder_creator=%.4s entry_id=0x%08x verified\n",
set_subfunction, path, request_mask, be16_to_cpu(verify_buf + 8),
be16_to_cpu(verify_buf + 20),
be16_to_cpu(verify_buf + 20), be16_to_cpu(verify_buf + 22),
be16_to_cpu(verify_buf + 24), be16_to_cpu(verify_buf + 26),
be16_to_cpu(verify_buf + 28), be16_to_cpu(verify_buf + 30),
finder_info + 0, finder_info + 4, be32_to_cpu(verify_buf + 0));

View File

@@ -362,6 +362,12 @@ run_cmd \
"$SCRIPT_DIR/afp_set_file_info_smoke" -S "$SERVER" -U "$USER_NAME" -P "$PASSWORD" \
--afp09 --attributes-only --clear-hidden "$NETWARE_PATH"
run_cmd \
"AFP Set File Information Access Timestamp" \
"./afp_set_file_info_smoke $COMMON_PRINT --access-time-only --access-time-epoch '$TIMESTAMP_EPOCH' '$NETWARE_PATH'" \
"$SCRIPT_DIR/afp_set_file_info_smoke" -S "$SERVER" -U "$USER_NAME" -P "$PASSWORD" \
--access-time-only --access-time-epoch "$TIMESTAMP_EPOCH" "$NETWARE_PATH"
run_cmd \
"AFP Set File Information Create Timestamp" \
"./afp_set_file_info_smoke $COMMON_PRINT --create-time-only --create-time-epoch '$TIMESTAMP_EPOCH' '$NETWARE_PATH'" \