Skip to content

clean up the public include surface #273

Description

@rdmark

Move Internal Headers and Symbols Out of the Public Include Surface

Summary

The project currently installs several headers from include/ that expose implementation details such as concrete struct afp_server, struct afp_volume, DSI request queues, mutexes, file descriptors, reconnect state, and low-level AFP protocol helpers. This makes the public API harder to consume from C/C++, leaks unstable ABI details, and turns internal synchronization choices into public header dependencies.

We should split the public API from internal implementation headers. Public headers should expose stable handles, value structs, constants, and supported functions. Internal headers and symbols should live under lib/ and be included only by in-tree implementation code.

Motivation

  • Avoid exposing implementation details such as pthread_mutex_t, reconnect guards, request queues, fd state, and AFP session internals.
  • Avoid public C++ compatibility issues from internal C constructs or compiler-specific synchronization details.
  • Make ABI and API expectations clearer before the project leaves alpha.
  • Reduce accidental dependency by external consumers on structs and functions we may need to change freely.
  • Make future concurrency fixes easier by keeping threading primitives private.

Current Problem Areas

  • include/afp.h exposes concrete internal structs and many low-level functions.
  • struct afp_server is public even though callers should generally use serverid_t or library functions.
  • struct afp_volume is public even though stateless users generally use volumeid_t.
  • Internal helpers such as server lookup, reconnect, AFP command functions, cache helpers, loop helpers, and protocol reply functions are visible through public headers.
  • Some headers under include/ are effectively internal, including dsi.h, midlevel.h, utils.h, and parts of map_def.h.

Proposed Direction

  1. Keep public headers minimal and stable:

    • include/afp.h
    • include/afpsl.h
    • include/afp_protocol.h
    • include/afp_xattr.h
    • include/libafpclient.h
    • any wire protocol IPC headers that must remain public
  2. Move implementation-only declarations to lib/:

    • lib/afp_internal.h
    • lib/dsi.h
    • lib/midlevel.h
    • lib/utils.h or a narrower internal utility header
    • protocol/private reply headers as needed
  3. Make server and volume opaque to public consumers:

    • Public:
      struct afp_server;
      struct afp_volume;
      typedef void *serverid_t;
      typedef void *volumeid_t;
    • Internal:
      concrete struct afp_server and struct afp_volume definitions.
  4. Keep public value structs public:

    • struct afp_url
    • struct afp_server_basic
    • struct afp_file_info_basic
    • struct afp_volume_summary

Suggested Migration Plan

Phase 1: Inventory

  • List all installed headers in include/meson.build.
  • Classify each header as public, private, or mixed.
  • For mixed headers, list which declarations are actually needed by external users.

Phase 2: Introduce Internal Header

  • Add lib/afp_internal.h.
  • Move concrete internal structs there:
    • struct afp_server
    • struct afp_volume
    • struct afp_file_info
    • struct afp_rx_buffer
    • struct afp_token
    • internal comment/icon structs if not public
  • Move private macros and state constants there:
    • SERVER_STATE_*
    • mount/attach state constants
    • volume_is_readonly
    • reconnect guard helpers

Phase 3: Trim Public afp.h

  • Keep only public declarations in include/afp.h.
  • Remove concrete server/volume structs.
  • Remove low-level AFP command prototypes.
  • Remove internal synchronization, reconnect, queue, and loop details.
  • Ensure afp.h is self-contained and usable from C and C++.

Phase 4: Move Internal Headers

  • Move implementation-only headers from include/ to lib/ where appropriate.
  • Update in-tree includes to reference internal headers directly.
  • Keep compatibility shims only if needed temporarily, and do not install them.

Phase 5: Tighten Installation

  • Update include/meson.build so only public headers are installed.
  • Verify meson install does not install internal headers.
  • Consider adding a simple CI check that installed headers do not include lib/ internals or expose internal-only symbols.

Phase 6: Compile and Consumer Checks

  • Build the full project.
  • Run the test suite.
  • Add lightweight public-header compile checks:
    • C translation unit including public headers only.
    • C++ translation unit including public headers only.
  • Verify consumers can use stateless APIs without AFPCLIENT_INTERNAL.

Acceptance Criteria

  • Public headers no longer expose struct afp_server internals.
  • Public headers no longer expose struct afp_volume internals.
  • Public headers do not expose threading primitives, atomics, DSI queues, fd state, or reconnect state.
  • Internal implementation still builds using headers under lib/.
  • Installed headers are self-contained and compile in both C and C++.
  • Existing Meson tests pass.
  • No external consumer needs AFPCLIENT_INTERNAL for supported public APIs.

Non-Goals

  • Do not redesign the stateless IPC protocol in this issue.
  • Do not remove existing public stateless APIs.
  • Do not change runtime behavior except where needed to preserve build correctness after the header split.
  • Do not make ABI stability guarantees beyond reducing public exposure of unstable internals.

Open Questions

  • Should low-level stateful AFP APIs remain public at all, or should the supported public API be the stateless afpsl surface?
  • Should include/afp_server.h remain public, or is it daemon IPC only?
  • Should public handles remain void *, or should they become typed opaque pointers?
  • Do we want temporary compatibility headers for one release, or can alpha status allow an immediate cleanup?

Notes

This cleanup was prompted by reconnect state synchronization work. Exposing synchronization primitives or atomic storage in public structs creates unnecessary C/C++ compatibility and ABI concerns. Keeping those details internal lets us fix threading issues without making implementation choices part of the public API.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions