-
Notifications
You must be signed in to change notification settings - Fork 100
Dev Docs Dev Guidelines
Netatalk Development Guidelines Overview This document outlines the coding standards, best practices, and development procedures for contributing to the Netatalk project. Following these guidelines ensures code consistency, maintainability, and compatibility across the project.
Code Standards Language and Compiler Requirements C Standard: C11 with GNU extensions Compiler: GCC 4.9+ or Clang 3.5+ Target Platforms: Linux, BSD variants, macOS, Solaris Architecture Support: x86, x86_64, ARM, ARM64, SPARC Implementation Files configure.ac - Autotools configuration with compiler and platform detection meson.build - Main Meson build configuration with compiler requirements include/atalk/compat.h - Cross-platform compatibility definitions libatalk/compat/ - Platform-specific compatibility implementations Coding Style Naming Conventions // Function names: lowercase with underscores int afp_login_user(struct AFPObj *obj, char *username);
// Variable names: lowercase with underscores struct vol *current_volume; int connection_count;
// Constants: uppercase with underscores #define AFP_MAX_USERNAME_LEN 256 #define CNID_INVALID 0
// Structure names: lowercase with underscores struct cnid_database { DB_ENV *env; DB *cnid_db; DB *devino_db; };
// Enum values: uppercase with prefix enum afp_result { AFP_OK = 0, AFP_ERROR = -1, AFP_ACCESS_DENIED = -5000 }; Code Formatting // Function definitions static int process_afp_command(struct AFPObj *obj, char *ibuf, size_t ibuflen, char *rbuf, size_t *rbuflen) { EC_INIT; int command; int result = AFP_OK;
// Extract command from buffer
if (ibuflen < 1) {
LOG(log_error, "Invalid AFP command buffer length");
EC_FAIL;
}
command = *ibuf;
// Process command based on type
switch (command) {
case AFP_LOGIN:
EC_ZERO(afp_login(obj, ibuf + 1, ibuflen - 1, rbuf, rbuflen));
break;
case AFP_LOGOUT:
EC_ZERO(afp_logout(obj));
break;
default:
LOG(log_warning, "Unknown AFP command: %d", command);
result = AFP_CALL_NOT_SUPPORTED;
break;
}
EC_STATUS(result);
EC_CLEANUP: EC_EXIT; }
// Control structures if (condition) { // Single statement or block do_something(); } else if (other_condition) { // Multiple statements do_this(); do_that(); } else { // Default case do_default(); }
// Loop structures for (int i = 0; i < count; i++) { process_item(items[i]); }
while (condition) { if (check_exit_condition()) { break; } process_data(); } Header File Organization #ifndef _ATALK_EXAMPLE_H #define _ATALK_EXAMPLE_H 1
#ifdef HAVE_CONFIG_H #include "config.h" #endif
// System includes #include <sys/types.h> #include <sys/stat.h> #include <unistd.h>
// Project includes #include <atalk/adouble.h> #include <atalk/cnid.h> #include <atalk/logger.h>
// Forward declarations struct afp_obj; struct vol;
// Constants #define EXAMPLE_MAX_SIZE 1024 #define EXAMPLE_DEFAULT "default_value"
// Type definitions typedef struct example_data { int ed_id; char *ed_name; size_t ed_size; } example_data_t;
// Function prototypes extern int example_init(struct afp_obj *obj); extern int example_process(struct vol *vol, const char *path); extern void example_cleanup(void);
#endif /* _ATALK_EXAMPLE_H */ Error Handling Netatalk uses a standardized error handling system based on macros defined in <atalk/errchk.h>.
Implementation Files include/atalk/errchk.h - Core error checking macro definitions libatalk/util/fault.c - Error handling utilities and stack trace generation libatalk/util/logger.c - Error logging integration with error checking Error Checking Macros // Basic error checking template int example_function(const char *filename) { EC_INIT; FILE *file = NULL; char *buffer = NULL;
// Check parameters
EC_NULL_LOG(filename);
// Open file with error checking
EC_NULL(file = fopen(filename, "r"));
// Allocate memory with error checking
EC_NULL(buffer = malloc(BUFFER_SIZE));
// Function call with zero return check
EC_ZERO(process_file_data(file, buffer));
// Set successful return status
EC_STATUS(0);
EC_CLEANUP: // Cleanup resources if (file != NULL) { fclose(file); } if (buffer != NULL) { free(buffer); }
EC_EXIT;
} Available Error Checking Macros EC_INIT: Initialize error handling (must be first) EC_NULL(expr): Check for NULL pointer EC_NULL_LOG(expr): Check for NULL with logging EC_ZERO(expr): Check for zero return value EC_ZERO_LOG(expr): Check for zero with logging EC_NEG1(expr): Check for -1 return value EC_FALSE(expr): Check for false return value EC_STATUS(val): Set return status value EC_FAIL: Jump to cleanup with error EC_EXIT: Return with current status (must be last) Logging Use the centralized logging system for all debug and error output.
Implementation Files include/atalk/logger.h - Logging system interface and level definitions libatalk/util/logger.c - Core logging implementation with file/syslog support include/atalk/debug.h - Debug logging macros and conditional compilation etc/afpd/main.c - AFP daemon logging initialization etc/netatalk/netatalk.c - Master daemon logging setup Log Levels // Log level definitions typedef enum { log_none = 0, log_severe, // System errors, crashes log_error, // Serious errors affecting operation log_warning, // Warnings, recoverable errors log_note, // Important information log_info, // General information log_debug, // Debug information log_debug6, // Verbose debug log_debug7, // Very verbose debug log_debug8, // Extremely verbose debug log_debug9, // Maximum verbosity log_maxdebug } log_severity_t; Logging Examples // Error conditions LOG(log_error, "Failed to open database: %s", strerror(errno));
// Warnings LOG(log_warning, "Volume '%s' not found, using default", volume_name);
// Information LOG(log_info, "Client %s connected from %s", username, client_ip);
// Debug information LOG(log_debug, "Processing AFP command %d, length %zu", cmd, len);
// Debug with detailed information LOG(log_debug6, "CNID lookup: did=%u name='%s' -> cnid=%u", did, filename, cnid); Memory Management Dynamic Memory Allocation Implementation Files libatalk/util/talloc.c - Memory allocation wrapper with debugging support include/atalk/talloc.h - Memory management interface definitions libatalk/util/memcheck.c - Memory leak detection and validation utilities // Always check allocation results char *buffer = malloc(size); if (buffer == NULL) { LOG(log_error, "Memory allocation failed"); return -1; }
// Use calloc for zero-initialized memory struct connection *conn = calloc(1, sizeof(struct connection)); if (conn == NULL) { LOG(log_error, "Failed to allocate connection structure"); return NULL; }
// Always free allocated memory free(buffer); buffer = NULL; // Prevent double-free String Handling // Use safe string functions char dest[MAXPATHLEN]; strlcpy(dest, source, sizeof(dest)); strlcat(dest, suffix, sizeof(dest));
// For dynamic strings char *result = NULL; if (asprintf(&result, "User: %s, Volume: %s", username, volname) == -1) { LOG(log_error, "String allocation failed"); return -1; } // Remember to free(result) later
// String length checking if (strnlen(input, MAX_INPUT_LEN) >= MAX_INPUT_LEN) { LOG(log_error, "Input string too long"); return -1; } Build System (Meson) Project Structure Implementation Files meson.build - Root build configuration with project structure meson_options.txt - Build option definitions and defaults subprojects/ - Dependency subproject definitions test/meson.build - Test suite build configuration etc/meson.build - Daemon executables build rules libatalk/meson.build - Core library build configuration include/meson.build - Header installation rules
component_sources = [ 'component.c', 'component_utils.c', 'component_config.c' ]
component_deps = [ atalk_dep, # Core Netatalk library crypto_dep, # Cryptographic functions db_dep # Database dependencies ]
component_lib = static_library( 'component', component_sources, dependencies: component_deps, include_directories: [ include_directories('.'), include_directories('../include') ], c_args: [ '-DCOMPONENT_VERSION="@0@"'.format(meson.project_version()) ] ) Configuration Options
option('with-component', type: 'boolean', value: true, description: 'Enable component functionality' )
if get_option('with-component') component_enabled = true cdata.set('HAVE_COMPONENT', 1) else component_enabled = false endif Testing Integration
test_component = executable( 'test_component', ['test_component.c'], dependencies: [component_dep, unity_dep], include_directories: test_includes )
test('component_tests', test_component) Testing Guidelines Unit Testing Implementation Files test/ - Main test suite directory with unit and integration tests test/testsuite.c - Test framework initialization and runner test/afpd_test.c - AFP daemon unit tests test/cnid_test.c - CNID system unit tests include/atalk/test_framework.h - Test macros and assertion definitions libatalk/util/test_util.c - Testing utility functions test/meson.build - Test build configuration and execution rules // Test framework integration #include <atalk/test_framework.h>
// Test case structure static int test_cnid_operations(void) { struct cnid_db *db = NULL; cnid_t cnid;
// Setup
TEST_ASSERT_NOT_NULL(db = cnid_open("/tmp/test_db"));
// Test CNID creation
cnid = cnid_add(db, &test_stat, DIRDID_ROOT, "testfile", 8, NULL);
TEST_ASSERT_NOT_EQUAL(cnid, CNID_INVALID);
// Test CNID lookup
cnid_t lookup_cnid = cnid_get(db, DIRDID_ROOT, "testfile", 8);
TEST_ASSERT_EQUAL(cnid, lookup_cnid);
// Cleanup
cnid_close(db);
return 0;
}
// Test registration void register_cnid_tests(void) { TEST_REGISTER(test_cnid_operations); } Integration Testing // Integration test example static int test_afp_login_flow(void) { struct AFPObj obj; char login_buf[256]; char reply_buf[256]; size_t reply_len = sizeof(reply_buf); int ret;
// Initialize AFP object
memset(&obj, 0, sizeof(obj));
TEST_ASSERT_ZERO(afp_options_parse(&obj, test_config));
// Simulate login request
build_login_request(login_buf, "testuser", "testpass");
// Process login
ret = afp_login(&obj, login_buf, sizeof(login_buf),
reply_buf, &reply_len);
TEST_ASSERT_EQUAL(ret, AFP_OK);
TEST_ASSERT_NOT_NULL(obj.username);
// Cleanup
afp_options_free(&obj);
return 0;
} Documentation Code Documentation Implementation Files doc/ - Documentation root directory with all user and developer docs doc/developer/ - Developer-focused documentation and guides doc/manpages/ - Manual pages for commands and configuration files include/atalk/ - Header files with inline API documentation CONTRIBUTORS - Contributor guidelines and recognition README.md - Project overview and quick start guide /*!
- @brief Process AFP directory enumeration command
- This function handles the FPEnumerate AFP command, which retrieves
- directory contents for display in Mac Finder windows.
- @param obj AFP session object
- @param ibuf Input buffer containing enumeration parameters
- @param ibuflen Length of input buffer
- @param rbuf Output buffer for directory entries
- @param rbuflen Pointer to output buffer length
- @return AFP error code:
-
- AFP_OK on success
-
- AFPERR_NOOBJ if directory not found
-
- AFPERR_ACCESS if access denied
-
- AFPERR_PARAM if invalid parameters
- @note The function respects volume access permissions and applies
-
appropriate filename encoding conversion.
- @see afp_openvol(), afp_closedir() */ int afp_enumerate(AFPObj *obj, char *ibuf, size_t ibuflen, char *rbuf, size_t *rbuflen) { // Implementation... } Function Documentation Standards Purpose: Brief description of what the function does Parameters: Description of each parameter and its constraints Return Value: Possible return values and their meanings Side Effects: Any global state changes or external effects Thread Safety: Concurrent access considerations Examples: Usage examples for complex functions Git Workflow Branch Management Implementation Files .gitignore - Git ignore patterns for build artifacts and temporary files .github/ - GitHub workflow and issue template configurations CONTRIBUTING.md - Contribution guidelines and pull request process ChangeLog - Historical change log with version history
main # Stable release branch develop # Development integration branch
feature/spotlight-v2 # New feature development bugfix/cnid-corruption # Bug fix branches hotfix/security-patch # Critical fixes for releases Commit Messages component: brief description of change
Longer explanation of what this commit does and why it was necessary. Include any breaking changes, migration notes, or special considerations.
- Specific change 1
- Specific change 2
- Reference to issue #123
Signed-off-by: Developer Name [email protected] Pull Request Process Create Feature Branch: git checkout -b feature/your-feature main Implement Changes: Follow coding guidelines and add tests Update Documentation: Update relevant docs and man pages Test Thoroughly: Run unit tests and integration tests Submit PR: Create pull request with detailed description Code Review: Address reviewer feedback CI Validation: Ensure all CI checks pass Merge: Squash and merge to main or development branches Platform Compatibility Portability Guidelines // Use feature detection instead of platform checks #ifdef HAVE_SENDFILE // Use sendfile for efficient file transfers ret = sendfile(outfd, infd, NULL, count); #else // Fallback to read/write loop ret = manual_copy(outfd, infd, count); #endif
// Handle platform differences in data types #if defined(FreeBSD) || defined(NetBSD) typedef off_t file_offset_t; #elif defined(linux) typedef loff_t file_offset_t; #else typedef off_t file_offset_t; #endif Endianness Handling // Use standard byte order functions #include <sys/types.h> #include <netinet/in.h>
// Network to host byte order uint32_t network_value = ntohl(wire_value); uint16_t host_port = ntohs(wire_port);
// Host to network byte order uint32_t wire_value = htonl(host_value); uint16_t wire_port = htons(host_port); Security Considerations Input Validation Implementation Files libatalk/util/string_check.c - String validation and sanitization functions include/atalk/security.h - Security-related function prototypes etc/afpd/auth.c - Authentication input validation libatalk/unicode/utf8.c - UTF-8 validation and normalization libatalk/util/fault.c - Security-focused error handling // Always validate input parameters static int validate_path_component(const char *name, size_t len) { if (name == NULL || len == 0) { return -1; }
// Check for path traversal attempts
if (strstr(name, "..") != NULL) {
LOG(log_warning, "Path traversal attempt detected: %s", name);
return -1;
}
// Check for invalid characters
if (strchr(name, '/') != NULL || strchr(name, '\\') != NULL) {
LOG(log_warning, "Invalid path characters: %s", name);
return -1;
}
return 0;
} Buffer Management // Use bounded string operations char buffer[MAXPATHLEN]; int ret = snprintf(buffer, sizeof(buffer), "%s/%s", basedir, filename); if (ret >= sizeof(buffer)) { LOG(log_error, "Path too long: %s/%s", basedir, filename); return -1; }
// Validate buffer lengths if (input_len > sizeof(internal_buffer) - 1) { LOG(log_error, "Input buffer too large: %zu", input_len); return AFPERR_PARAM; } Privilege Management Implementation Files etc/netatalk/netatalk.c - Master daemon privilege dropping implementation libatalk/util/server_child.c - Child process privilege management etc/afpd/main.c - AFP daemon privilege initialization include/atalk/server_child.h - Process privilege management interface // Drop privileges when appropriate static int drop_privileges(uid_t uid, gid_t gid) { // Set supplementary groups if (setgroups(0, NULL) != 0) { LOG(log_error, "Failed to clear supplementary groups"); return -1; }
// Set group ID
if (setgid(gid) != 0) {
LOG(log_error, "Failed to set group ID to %d", gid);
return -1;
}
// Set user ID
if (setuid(uid) != 0) {
LOG(log_error, "Failed to set user ID to %d", uid);
return -1;
}
return 0;
} Performance Guidelines Optimization Strategies Minimize System Calls: Batch operations where possible Use Efficient Data Structures: Choose appropriate algorithms Cache Frequently Used Data: Implement intelligent caching Avoid Unnecessary Memory Allocation: Reuse buffers when safe Profile Before Optimizing: Use tools like gprof and valgrind Memory Usage // Prefer stack allocation for small, temporary data char temp_buffer[256]; // Stack allocated
// Use dynamic allocation for large or variable-sized data size_t buffer_size = calculate_needed_size(); char *large_buffer = malloc(buffer_size); Following these guidelines helps maintain code quality, ensures compatibility across platforms, and facilitates collaboration among developers working on the Netatalk project.
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 Solaris
- Installing Netatalk on Ubuntu
Tech Notes
- 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