#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
+#include <inttypes.h>
+#include <time.h>
#include <atalk/asp.h>
#include <atalk/dsi.h>
#include <atalk/uuid.h>
#include <atalk/bstrlib.h>
#include <atalk/bstradd.h>
-
+#include <atalk/ftw.h>
+#include <atalk/globals.h>
+#include <atalk/fce_api.h>
+#include <atalk/errchk.h>
#ifdef CNID_DB
#include <atalk/cnid.h>
#endif /* CNID_DB*/
-#include "globals.h"
#include "directory.h"
#include "file.h"
#include "volume.h"
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#endif /* ! MIN */
-#ifndef NO_LARGE_VOL_SUPPORT
-#if BYTE_ORDER == BIG_ENDIAN
-#define hton64(x) (x)
-#define ntoh64(x) (x)
-#else /* BYTE_ORDER == BIG_ENDIAN */
-#define hton64(x) ((u_int64_t) (htonl(((x) >> 32) & 0xffffffffLL)) | \
- (u_int64_t) ((htonl(x) & 0xffffffffLL) << 32))
-#define ntoh64(x) (hton64(x))
-#endif /* BYTE_ORDER == BIG_ENDIAN */
-#endif /* ! NO_LARGE_VOL_SUPPORT */
-
#ifndef UUID_PRINTABLE_STRING_LENGTH
#define UUID_PRINTABLE_STRING_LENGTH 37
#endif
} _special_folder;
static const _special_folder special_folders[] = {
+#if 0
{"Network Trash Folder", 1, 0777, 1},
{"Temporary Items", 1, 0777, 1},
{".AppleDesktop", 1, 0777, 0},
-#if 0
{"TheFindByContentFolder", 0, 0, 1},
{"TheVolumeSettingsFolder", 0, 0, 1},
#endif
} else if (is_var(p, "$c")) {
if (afpmaster && xlatevolname)
return NULL;
+#ifndef NO_DDP
if (obj->proto == AFPPROTO_ASP) {
ASP asp = obj->handle;
dest += len;
destlen -= len;
- } else if (obj->proto == AFPPROTO_DSI) {
+ }
+#endif
+ if (obj->proto == AFPPROTO_DSI) {
DSI *dsi = obj->handle;
len = sprintf(dest, "%s:%u",
getip_string((struct sockaddr *)&dsi->client),
} else if (is_var(p, "$i")) {
if (afpmaster && xlatevolname)
return NULL;
+#ifndef NO_DDP
if (obj->proto == AFPPROTO_ASP) {
ASP asp = obj->handle;
-
len = sprintf(dest, "%u", ntohs(asp->asp_sat.sat_addr.s_net));
dest += len;
destlen -= len;
- } else if (obj->proto == AFPPROTO_DSI) {
+ }
+#endif
+ if (obj->proto == AFPPROTO_DSI) {
DSI *dsi = obj->handle;
q = getip_string((struct sockaddr *)&dsi->client);
}
setoption(options, save, VOLOPT_ROLIST, val);
} else if (optionok(tmp, "codepage:", val)) {
- LOG (log_error, logtype_afpd, "The old codepage system has been removed. Please make sure to read the documentation !!!!");
+ LOG (log_error, logtype_afpd, "The old codepage system has been removed. Please make sure to read the documentation!");
/* Make sure we don't screw anything */
exit (EXITERR_CONF);
} else if (optionok(tmp, "volcharset:", val)) {
options[VOLOPT_FLAGS].i_value |= AFPVOL_TM;
else if (strcasecmp(p, "searchdb") == 0)
options[VOLOPT_FLAGS].i_value |= AFPVOL_SEARCHDB;
-/* Found this in branch dir-rewrite, maybe we want to use it sometimes */
-#if 0
- else if (strcasecmp(p, "cdrom") == 0)
- options[VOLOPT_FLAGS].i_value |= AFPVOL_CDROM | AFPVOL_RO;
-#endif
+ else if (strcasecmp(p, "nonetids") == 0)
+ options[VOLOPT_FLAGS].i_value |= AFPVOL_NONETIDS;
+ else if (strcasecmp(p, "noacls") == 0)
+ options[VOLOPT_FLAGS].i_value &= ~AFPVOL_ACLS;
p = strtok(NULL, ",");
}
if ( (flags & CONV_REQMANGLE) || (tmpvlen > AFPVOL_MACNAMELEN)) {
if (tmpvlen + suffixlen > AFPVOL_MACNAMELEN) {
flags = CONV_FORCE;
- tmpvlen = convert_charset(obj->options.unixcharset, obj->options.maccharset, 0, name, vlen, tmpname, AFPVOL_MACNAMELEN - suffixlen, &flags);
+ tmpvlen = convert_charset(obj->options.unixcharset,
+ obj->options.maccharset,
+ 0,
+ name,
+ vlen,
+ tmpname,
+ AFPVOL_MACNAMELEN - suffixlen,
+ &flags);
tmpname[tmpvlen >= 0 ? tmpvlen : 0] = 0;
}
strcat(tmpname, suffix);
}
/* Secondly convert name from maccharset to UCS2 */
- if ( 0 >= ( macvlen = convert_string(obj->options.maccharset, CH_UCS2, tmpname, tmpvlen, mactmpname, AFPVOL_U8MNAMELEN*2)) )
+ if ( 0 >= ( macvlen = convert_string(obj->options.maccharset,
+ CH_UCS2,
+ tmpname,
+ tmpvlen,
+ mactmpname,
+ AFPVOL_U8MNAMELEN*2)) )
return -1;
LOG(log_maxdebug, logtype_afpd, "createvol: Volume '%s' -> Longname: '%s'", name, tmpname);
/* check duplicate */
for ( volume = Volumes; volume; volume = volume->v_next ) {
- if (( strcasecmp_w( volume->v_u8mname, u8mtmpname ) == 0 ) || ( strcasecmp_w( volume->v_macname, mactmpname ) == 0 )){
- LOG (log_error, logtype_afpd, "ERROR: Volume name is duplicated. Check AppleVolumes files.");
+ if ((utf8_encoding() && (strcasecmp_w(volume->v_u8mname, u8mtmpname) == 0))
+ ||
+ (!utf8_encoding() && (strcasecmp_w(volume->v_macname, mactmpname) == 0))) {
+ LOG (log_error, logtype_afpd,
+ "Duplicate volume name, check AppleVolumes files: previous: \"%s\", new: \"%s\"",
+ volume->v_localname, name);
if (volume->v_deleted) {
volume->v_new = hide = 1;
}
volume->v_vid = ++lastvid;
volume->v_vid = htons(volume->v_vid);
#ifdef HAVE_ACLS
- if (check_vol_acl_support(volume))
- volume->v_flags |= AFPVOL_ACLS
-;
+ if (!check_vol_acl_support(volume)) {
+ LOG(log_debug, logtype_afpd, "creatvol(\"%s\"): disabling ACL support", volume->v_path);
+ options[VOLOPT_FLAGS].i_value &= ~AFPVOL_ACLS;
+ }
#endif
/* handle options */
check_ea_sys_support(volume);
initvol_vfs(volume);
- /* get/store uuid from file */
- if (volume->v_flags & AFPVOL_TM) {
+ /* get/store uuid from file in afpd master*/
+ if ((parent_or_child == 0) && (volume->v_flags & AFPVOL_TM)) {
char *uuid = get_vol_uuid(obj, volume->v_localname);
if (!uuid) {
LOG(log_error, logtype_afpd, "Volume '%s': couldn't get UUID",
int i;
struct passwd *pw;
struct vol_option save_options[VOLOPT_NUM];
+ struct vol_option default_options[VOLOPT_NUM];
struct vol_option options[VOLOPT_NUM];
struct stat st;
p1->mtime = st.st_mtime;
}
- if ((read_lock(fd, 0, SEEK_SET, 0)) != 0) {
- LOG(log_error, logtype_afpd, "readvolfile: can't lock volume file \"%s\"", path);
- if ( fclose( fp ) != 0 ) {
- LOG(log_error, logtype_afpd, "readvolfile: fclose: %s", strerror(errno) );
+ /* try putting a read lock on the volume file twice, sleep 1 second if first attempt fails */
+ int retries = 2;
+ while (1) {
+ if ((read_lock(fd, 0, SEEK_SET, 0)) != 0) {
+ retries--;
+ if (!retries) {
+ LOG(log_error, logtype_afpd, "readvolfile: can't lock volume file \"%s\"", path);
+ if ( fclose( fp ) != 0 ) {
+ LOG(log_error, logtype_afpd, "readvolfile: fclose: %s", strerror(errno) );
+ }
+ return -1;
+ }
+ sleep(1);
+ continue;
}
- return -1;
+ break;
}
- memset(save_options, 0, sizeof(save_options));
+ memset(default_options, 0, sizeof(default_options));
/* Enable some default options for all volumes */
- save_options[VOLOPT_FLAGS].i_value |= AFPVOL_CACHE;
- save_options[VOLOPT_EA_VFS].i_value = AFPVOL_EA_AUTO;
+ default_options[VOLOPT_FLAGS].i_value |= AFPVOL_CACHE;
+#ifdef HAVE_ACLS
+ default_options[VOLOPT_FLAGS].i_value |= AFPVOL_ACLS;
+#endif
+ default_options[VOLOPT_EA_VFS].i_value = AFPVOL_EA_AUTO;
LOG(log_maxdebug, logtype_afpd, "readvolfile: seeding default umask: %04o",
obj->options.umask);
- save_options[VOLOPT_UMASK].i_value = obj->options.umask;
+ default_options[VOLOPT_UMASK].i_value = obj->options.umask;
+ memcpy(save_options, default_options, sizeof(options));
LOG(log_debug, logtype_afpd, "readvolfile: \"%s\"", path);
case ':':
/* change the default options for this file */
if (strncmp(path, VOLOPT_DEFAULT, VOLOPT_DEFAULT_LEN) == 0) {
+ volfree(default_options, save_options);
+ memcpy(default_options, save_options, sizeof(options));
*tmp = '\0';
for (i = 0; i < VOLOPT_NUM; i++) {
if (parseline( sizeof( path ) - VOLOPT_DEFAULT_LEN - 1,
path + VOLOPT_DEFAULT_LEN) < 0)
break;
- volset(save_options, NULL, tmp, sizeof(tmp) - 1,
+ volset(default_options, NULL, tmp, sizeof(tmp) - 1,
path + VOLOPT_DEFAULT_LEN);
}
}
* able to specify things in any order, but i don't want to
* re-write everything. */
- memcpy(options, save_options, sizeof(options));
+ memcpy(options, default_options, sizeof(options));
*volname = '\0';
/* read in up to VOLOP_NUM possible options */
if (parseline( sizeof( tmp ) - 1, tmp ) < 0)
break;
- volset(options, save_options, volname, sizeof(volname) - 1, tmp);
+ volset(options, default_options, volname, sizeof(volname) - 1, tmp);
}
/* check allow/deny lists (if not afpd master loading volumes for Zeroconf reg.):
creatvol(obj, pwent, path, tmp, options, p2 != NULL);
}
- volfree(options, save_options);
+ volfree(options, default_options);
break;
case '.' :
}
}
}
+/*!
+ * Read band-size info from Info.plist XML file of an TM sparsebundle
+ *
+ * @param path (r) path to Info.plist file
+ * @return band-size in bytes, -1 on error
+ */
+static long long int get_tm_bandsize(const char *path)
+{
+ EC_INIT;
+ FILE *file = NULL;
+ char buf[512];
+ long long int bandsize = -1;
+
+ EC_NULL_LOGSTR( file = fopen(path, "r"),
+ "get_tm_bandsize(\"%s\"): %s",
+ path, strerror(errno) );
+
+ while (fgets(buf, sizeof(buf), file) != NULL) {
+ if (strstr(buf, "band-size") == NULL)
+ continue;
+
+ if (fscanf(file, " <integer>%lld</integer>", &bandsize) != 1) {
+ LOG(log_error, logtype_afpd, "get_tm_bandsize(\"%s\"): can't parse band-size", path);
+ EC_FAIL;
+ }
+ break;
+ }
+
+EC_CLEANUP:
+ if (file)
+ fclose(file);
+ LOG(log_debug, logtype_afpd, "get_tm_bandsize(\"%s\"): bandsize: %lld", path, bandsize);
+ return bandsize;
+}
+
+/*!
+ * Return number on entries in a directory
+ *
+ * @param path (r) path to dir
+ * @return number of entries, -1 on error
+ */
+static long long int get_tm_bands(const char *path)
+{
+ EC_INIT;
+ long long int count = 0;
+ DIR *dir = NULL;
+ const struct dirent *entry;
+
+ EC_NULL( dir = opendir(path) );
+
+ while ((entry = readdir(dir)) != NULL)
+ count++;
+ count -= 2; /* All OSens I'm aware of return "." and "..", so just substract them, avoiding string comparison in loop */
+
+EC_CLEANUP:
+ if (dir)
+ closedir(dir);
+ if (ret != 0)
+ return -1;
+ return count;
+}
+
+/*!
+ * Calculate used size of a TimeMachine volume
+ *
+ * This assumes that the volume is used only for TimeMachine.
+ *
+ * 1) readdir(path of volume)
+ * 2) for every element that matches regex "\(.*\)\.sparsebundle$" :
+ * 3) parse "\1.sparsebundle/Info.plist" and read the band-size XML key integer value
+ * 4) readdir "\1.sparsebundle/bands/" counting files
+ * 5) calculate used size as: (file_count - 1) * band-size
+ *
+ * The result of the calculation is returned in "volume->v_tm_used".
+ * "volume->v_appended" gets reset to 0.
+ * "volume->v_tm_cachetime" is updated with the current time from time(NULL).
+ *
+ * "volume->v_tm_used" is cached for TM_USED_CACHETIME seconds and updated by
+ * "volume->v_appended". The latter is increased by X every time the client
+ * appends X bytes to a file (in fork.c).
+ *
+ * @param vol (rw) volume to calculate
+ * @return 0 on success, -1 on error
+ */
+#define TM_USED_CACHETIME 60 /* cache for 60 seconds */
+static int get_tm_used(struct vol * restrict vol)
+{
+ EC_INIT;
+ long long int bandsize;
+ VolSpace used = 0;
+ bstring infoplist = NULL;
+ bstring bandsdir = NULL;
+ DIR *dir = NULL;
+ const struct dirent *entry;
+ const char *p;
+ struct stat st;
+ long int links;
+ time_t now = time(NULL);
+
+ if (vol->v_tm_cachetime
+ && ((vol->v_tm_cachetime + TM_USED_CACHETIME) >= now)) {
+ if (vol->v_tm_used == -1)
+ EC_FAIL;
+ vol->v_tm_used += vol->v_appended;
+ vol->v_appended = 0;
+ LOG(log_debug, logtype_afpd, "getused(\"%s\"): cached: %" PRIu64 " bytes",
+ vol->v_path, vol->v_tm_used);
+ return 0;
+ }
+
+ vol->v_tm_cachetime = now;
+
+ EC_NULL( dir = opendir(vol->v_path) );
+
+ while ((entry = readdir(dir)) != NULL) {
+ if (((p = strstr(entry->d_name, "sparsebundle")) != NULL)
+ && (strlen(entry->d_name) == (p + strlen("sparsebundle") - entry->d_name))) {
+
+ EC_NULL_LOG( infoplist = bformat("%s/%s/%s", vol->v_path, entry->d_name, "Info.plist") );
+
+ if ((bandsize = get_tm_bandsize(cfrombstr(infoplist))) == -1)
+ continue;
+
+ EC_NULL_LOG( bandsdir = bformat("%s/%s/%s/", vol->v_path, entry->d_name, "bands") );
+
+ if ((links = get_tm_bands(cfrombstr(bandsdir))) == -1)
+ continue;
+
+ used += (links - 1) * bandsize;
+ LOG(log_debug, logtype_afpd, "getused(\"%s\"): bands: %" PRIu64 " bytes",
+ cfrombstr(bandsdir), used);
+ }
+ }
+
+ vol->v_tm_used = used;
+
+EC_CLEANUP:
+ if (infoplist)
+ bdestroy(infoplist);
+ if (bandsdir)
+ bdestroy(bandsdir);
+ if (dir)
+ closedir(dir);
+
+ LOG(log_debug, logtype_afpd, "getused(\"%s\"): %" PRIu64 " bytes", vol->v_path, vol->v_tm_used);
+
+ EC_EXIT;
+}
static int getvolspace(struct vol *vol,
u_int32_t *bfree, u_int32_t *btotal,
{
int spaceflag, rc;
u_int32_t maxsize;
+ VolSpace used;
#ifndef NO_QUOTA_SUPPORT
VolSpace qfree, qtotal;
#endif
return( rc );
}
-#define min(a,b) ((a)<(b)?(a):(b))
#ifndef NO_QUOTA_SUPPORT
if ( spaceflag == AFPVOL_NONE || spaceflag == AFPVOL_UQUOTA ) {
if ( uquota_getvolspace( vol, &qfree, &qtotal, *bsize ) == AFP_OK ) {
vol->v_flags = ( ~AFPVOL_GVSMASK & vol->v_flags ) | AFPVOL_UQUOTA;
- *xbfree = min(*xbfree, qfree);
- *xbtotal = min( *xbtotal, qtotal);
+ *xbfree = MIN(*xbfree, qfree);
+ *xbtotal = MIN(*xbtotal, qtotal);
goto getvolspace_done;
}
}
getvolspace_done:
if (vol->v_limitsize) {
- bstring cmdstr;
- if ((cmdstr = bformat("du -sh \"%s\" 2> /dev/null | cut -f1", vol->v_path)) == NULL)
- return AFPERR_MISC;
-
- FILE *cmd = popen(cfrombstr(cmdstr), "r");
- bdestroy(cmdstr);
- if (cmd == NULL)
+ if (get_tm_used(vol) != 0)
return AFPERR_MISC;
- char buf[100];
- fgets(buf, 100, cmd);
+ *xbtotal = MIN(*xbtotal, (vol->v_limitsize * 1024 * 1024));
+ *xbfree = MIN(*xbfree, *xbtotal < vol->v_tm_used ? 0 : *xbtotal - vol->v_tm_used);
- if (pclose(cmd) == -1)
- return AFPERR_MISC;
+ LOG(log_debug, logtype_afpd,
+ "volparams: total: %" PRIu64 ", used: %" PRIu64 ", free: %" PRIu64 " bytes",
+ *xbtotal, vol->v_tm_used, *xbfree);
+ }
- size_t multi = 0;
- if (buf[strlen(buf) - 2] == 'G' || buf[strlen(buf) - 2] == 'g')
- /* GB */
- multi = 1024 * 1024 * 1024;
- else if (buf[strlen(buf) - 2] == 'M' || buf[strlen(buf) - 2] == 'm')
- /* MB */
- multi = 1024 * 1024;
- else if (buf[strlen(buf) - 2] == 'K' || buf[strlen(buf) - 2] == 'k')
- /* MB */
- multi = 1024;
+ *bfree = MIN(*xbfree, maxsize);
+ *btotal = MIN(*xbtotal, maxsize);
+ return( AFP_OK );
+}
- char *p;
- if (p = strchr(buf, ','))
- /* ignore fraction */
- *p = 0;
- else
- /* remove G|M|K char */
- buf[strlen(buf) - 2] = 0;
- /* now buf contains only digits */
- long long used = atoll(buf) * multi;
- LOG(log_debug, logtype_afpd, "volparams: used on volume: %llu bytes", used);
+#define FCE_TM_DELTA 10 /* send notification every 10 seconds */
+void vol_fce_tm_event(void)
+{
+ static time_t last;
+ time_t now = time(NULL);
+ struct vol *vol = Volumes;
- *xbtotal = min(*xbtotal, (vol->v_limitsize * 1024 * 1024));
- *xbfree = min(*xbfree, *xbtotal < used ? 0 : *xbtotal - used);
+ if ((last + FCE_TM_DELTA) < now) {
+ last = now;
+ for ( ; vol; vol = vol->v_next ) {
+ if (vol->v_flags & AFPVOL_TM)
+ (void)fce_register_tm_size(vol->v_path, vol->v_tm_used + vol->v_appended);
+ }
}
-
- *bfree = min( *xbfree, maxsize);
- *btotal = min( *xbtotal, maxsize);
- return( AFP_OK );
}
/* -----------------------
ashort |= VOLPBIT_ATTR_UNIXPRIV;
if (vol->v_flags & AFPVOL_TM)
ashort |= VOLPBIT_ATTR_TM;
-
+ if (vol->v_flags & AFPVOL_NONETIDS)
+ ashort |= VOLPBIT_ATTR_NONETIDS;
if (afp_version >= 32) {
if (vol->v_vfs_ea)
ashort |= VOLPBIT_ATTR_EXT_ATTRS;
if (!S_ISDIR(st.st_mode)) {
continue; /* not a dir */
}
- accessmode(volume->v_path, &ma, NULL, &st);
+ accessmode(volume, volume->v_path, &ma, NULL, &st);
if ((ma.ma_user & (AR_UREAD | AR_USEARCH)) != (AR_UREAD | AR_USEARCH)) {
continue; /* no r-x access */
}
DIRDID_ROOT_PARENT,
DIRDID_ROOT,
bfromcstr(volume->v_path),
- st.st_ctime)
+ &st)
) == NULL) {
free(vol_mname);
LOG(log_error, logtype_afpd, "afp_openvol(%s): malloc: %s", volume->v_path, strerror(errno) );
}
ret = stat_vol(bitmap, volume, rbuf, rbuflen);
+
if (ret == AFP_OK) {
+ handle_special_folders(volume);
+ savevolinfo(volume,
+ volume->v_cnidserver ? volume->v_cnidserver : Cnid_srv,
+ volume->v_cnidport ? volume->v_cnidport : Cnid_port);
- if (!(volume->v_flags & AFPVOL_RO)) {
- handle_special_folders( volume );
- savevolinfo(volume,
- volume->v_cnidserver ? volume->v_cnidserver : Cnid_srv,
- volume->v_cnidport ? volume->v_cnidport : Cnid_port);
- }
/*
* If you mount a volume twice, the second time the trash appears on
{
struct timeval tv;
- /* just looking at vol->v_mtime is broken seriously since updates
- * from other users afpd processes never are seen.
- * This is not the most elegant solution (a shared memory between
- * the afpd processes would come closer)
- * [RS] */
-
if ( gettimeofday( &tv, NULL ) < 0 ) {
LOG(log_error, logtype_afpd, "setvoltime(%s): gettimeofday: %s", vol->v_path, strerror(errno) );
return;
static void handle_special_folders (const struct vol * vol)
{
const _special_folder *p = &special_folders[0];
+ uid_t process_uid;
- if ((vol->v_flags & AFPVOL_RO))
- return;
+ process_uid = geteuid();
+ if (process_uid) {
+ if (seteuid(0) == -1) {
+ process_uid = 0;
+ }
+ }
for (; p->name != NULL; p++) {
create_special_folder (vol, p);
}
+
+ if (process_uid) {
+ if (seteuid(process_uid) == -1) {
+ LOG(log_error, logtype_logger, "can't seteuid back %s", strerror(errno));
+ exit(EXITERR_SYS);
+ }
+ }
}
const struct vol *getvolumes(void)