|
|
|
|
@ -214,9 +214,12 @@ unsigned mf_storage_open_count(const struct mf_storage* s) {
|
|
|
|
|
static int store_upload_discard(struct mf_storage* s, struct store_upload* u) { |
|
|
|
|
store_invalidate(s); |
|
|
|
|
int rc = mf_disk_remove(&s->root, u->temp, 0); |
|
|
|
|
if (rc && rc != MF_NOT_FOUND) return rc; |
|
|
|
|
if (rc && rc != MF_NOT_FOUND) { |
|
|
|
|
struct mf_disk_info after; |
|
|
|
|
if (mf_disk_stat(&s->root, u->temp, &after) != MF_NOT_FOUND) return rc; |
|
|
|
|
} |
|
|
|
|
int updated = store_user_update(u->user, 0, 0, u->reservation, "abort"); u->active = 0; |
|
|
|
|
return rc ? rc : updated; |
|
|
|
|
return rc && rc != MF_NOT_FOUND ? rc : updated; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void mf_storage_sweep(struct mf_storage* s, uint64_t now) { |
|
|
|
|
@ -316,8 +319,21 @@ static int store_begin(struct mf_storage* s, uint64_t group, uint64_t peer, cons
|
|
|
|
|
int rc = store_expected(s, path, q->version, &existing); |
|
|
|
|
if (rc) return rc; |
|
|
|
|
if (existing.type && existing.type != MF_FILE) return MF_INVALID_PATH; |
|
|
|
|
if (s->quota && (s->used > s->quota || s->reserved > s->quota - s->used || q->size > s->quota - s->used - s->reserved)) |
|
|
|
|
struct store_user* user = store_user_get(s, q->owner, 1); |
|
|
|
|
if (!user) return MF_IO_ERROR; |
|
|
|
|
if (!user->valid || existing.size > user->used) { |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "member_files: quota inconsistent before upload owner=%llu used=%llu old_size=%llu", |
|
|
|
|
(unsigned long long)q->owner, (unsigned long long)user->used, (unsigned long long)existing.size); |
|
|
|
|
user->valid = 0; return MF_IO_ERROR; |
|
|
|
|
} |
|
|
|
|
uint64_t reservation = q->size > existing.size ? q->size - existing.size : 0; |
|
|
|
|
if (reservation && (user->used > s->quota || user->reserved > s->quota - user->used || |
|
|
|
|
reservation > s->quota - user->used - user->reserved)) { |
|
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "member_files: user quota exceeded owner=%llu used=%llu reserved=%llu growth=%llu limit=%llu", |
|
|
|
|
(unsigned long long)q->owner, (unsigned long long)user->used, (unsigned long long)user->reserved, |
|
|
|
|
(unsigned long long)reservation, (unsigned long long)s->quota); |
|
|
|
|
return MF_NO_SPACE; |
|
|
|
|
} |
|
|
|
|
rc = store_base(s, group, q->owner); |
|
|
|
|
if (rc) return rc; |
|
|
|
|
memset(slot, 0, sizeof(*slot)); |
|
|
|
|
@ -333,20 +349,32 @@ static int store_begin(struct mf_storage* s, uint64_t group, uint64_t peer, cons
|
|
|
|
|
if (rc) { mf_disk_close(fd); mf_disk_remove(&s->root, slot->temp, 0); return rc; } |
|
|
|
|
cached->fd = fd; cached->touched = get_time_tb(); snprintf(cached->path, sizeof(cached->path), "%s", slot->temp); |
|
|
|
|
slot->active = 1; slot->group = group; slot->peer = peer; slot->owner = q->owner; slot->size = q->size; |
|
|
|
|
slot->user = user; slot->previous = existing.size; slot->reservation = reservation; |
|
|
|
|
memcpy(slot->id, q->upload, 16); memcpy(slot->hash, q->hash, 32); memcpy(slot->expected, q->version, 16); |
|
|
|
|
snprintf(slot->path, sizeof(slot->path), "%s", path); slot->touched = get_time_tb(); s->reserved += q->size; |
|
|
|
|
snprintf(slot->path, sizeof(slot->path), "%s", path); slot->touched = get_time_tb(); user->reserved += reservation; |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_MEDIA, "member_files: begin group=%llu owner=%llu path=%s size=%llu reserved=%llu", |
|
|
|
|
(unsigned long long)group, (unsigned long long)q->owner, path, (unsigned long long)q->size, (unsigned long long)s->reserved); |
|
|
|
|
(unsigned long long)group, (unsigned long long)q->owner, path, (unsigned long long)q->size, (unsigned long long)user->reserved); |
|
|
|
|
return MF_OK; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static int store_command(struct mf_storage* s, uint64_t group, uint64_t peer, const struct mf_message* q, struct mf_message* r) { |
|
|
|
|
if (!mf_path_valid(q->path, q->command == MF_LIST || q->command == MF_STAT) || |
|
|
|
|
if (!mf_path_valid(q->path, q->command == MF_LIST || q->command == MF_STAT || q->command == MF_QUOTA) || |
|
|
|
|
(q->command == MF_RENAME && !mf_path_valid(q->target, 0))) return MF_INVALID_PATH; |
|
|
|
|
char path[MF_PATH_MAX + 80]; |
|
|
|
|
snprintf(path, sizeof(path), "%llu/%llu%s%s", (unsigned long long)group, (unsigned long long)q->owner, *q->path ? "/" : "", q->path); |
|
|
|
|
struct mf_disk_info info = {0}; |
|
|
|
|
int rc, fd; |
|
|
|
|
if (q->command == MF_QUOTA) { |
|
|
|
|
if (*q->path || *q->target || q->cursor || q->data_len) return MF_BAD_PACKET; |
|
|
|
|
struct store_user* user = store_user_get(s, q->owner, 0); |
|
|
|
|
if (user && !user->valid) return MF_IO_ERROR; |
|
|
|
|
mf_put64(r->data, user ? user->used : 0); mf_put64(r->data + 8, s->quota); |
|
|
|
|
mf_put64(r->data + 16, user ? user->reserved : 0); r->data_len = MF_QUOTA_BYTES; |
|
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "member_files: quota response owner=%llu used=%llu reserved=%llu limit=%llu", |
|
|
|
|
(unsigned long long)q->owner, (unsigned long long)(user ? user->used : 0), |
|
|
|
|
(unsigned long long)(user ? user->reserved : 0), (unsigned long long)s->quota); |
|
|
|
|
return MF_OK; |
|
|
|
|
} |
|
|
|
|
if (q->command == MF_BEGIN_WRITE) return store_begin(s, group, peer, path, q); |
|
|
|
|
if (q->command == MF_WRITE || q->command == MF_COMMIT || q->command == MF_ABORT) { |
|
|
|
|
struct store_upload* u = store_upload_find(s, group, peer, q->upload); |
|
|
|
|
@ -367,21 +395,33 @@ static int store_command(struct mf_storage* s, uint64_t group, uint64_t peer, co
|
|
|
|
|
if (rc) return rc; |
|
|
|
|
rc = store_expected(s, path, u->expected, &info); |
|
|
|
|
if (rc) return rc; |
|
|
|
|
if (!u->user->valid || info.size != u->previous) { |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "member_files: quota/old size changed before commit owner=%llu", (unsigned long long)q->owner); |
|
|
|
|
return MF_IO_ERROR; |
|
|
|
|
} |
|
|
|
|
uint64_t previous = info.size; |
|
|
|
|
store_invalidate(s); |
|
|
|
|
rc = mf_disk_rename(&s->root, u->temp, path); |
|
|
|
|
/* Даже при ошибке fsync rename уже мог произойти; сверяем существование temp. */ |
|
|
|
|
struct mf_disk_info temp; |
|
|
|
|
int temp_status = mf_disk_stat(&s->root, u->temp, &temp); |
|
|
|
|
if (temp_status != MF_NOT_FOUND) return rc ? rc : temp_status ? temp_status : MF_IO_ERROR; |
|
|
|
|
s->used = s->used - previous + u->size; s->reserved -= u->size; u->active = 0; |
|
|
|
|
if (temp_status != MF_NOT_FOUND) { |
|
|
|
|
if (!rc || temp_status) { |
|
|
|
|
u->user->valid = 0; |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "member_files: commit outcome uncertain owner=%llu rename=%s temp_stat=%s", |
|
|
|
|
(unsigned long long)q->owner, mf_status_text(rc), mf_status_text(temp_status)); |
|
|
|
|
} |
|
|
|
|
return rc ? rc : temp_status ? temp_status : MF_IO_ERROR; |
|
|
|
|
} |
|
|
|
|
int updated = store_user_update(u->user, previous, u->size, u->reservation, "commit"); u->active = 0; |
|
|
|
|
if (rc) return rc; |
|
|
|
|
if (updated) return updated; |
|
|
|
|
rc = mf_disk_stat(&s->root, path, &info); |
|
|
|
|
if (rc) return rc; |
|
|
|
|
r->size = info.size; r->mtime = info.mtime; r->type = info.type; |
|
|
|
|
store_version(s, &info, r->version); memcpy(r->hash, hash, 32); |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_MEDIA, "member_files: published path=%s size=%llu used=%llu", path, |
|
|
|
|
(unsigned long long)r->size, (unsigned long long)s->used); |
|
|
|
|
(unsigned long long)r->size, (unsigned long long)u->user->used); |
|
|
|
|
return MF_OK; |
|
|
|
|
} |
|
|
|
|
if (q->command == MF_MKDIR) { |
|
|
|
|
@ -430,9 +470,22 @@ static int store_command(struct mf_storage* s, uint64_t group, uint64_t peer, co
|
|
|
|
|
} |
|
|
|
|
store_invalidate(s); |
|
|
|
|
if (q->command == MF_REMOVE) { |
|
|
|
|
struct store_user* user = info.type == MF_FILE ? store_user_get(s, q->owner, 1) : NULL; |
|
|
|
|
if (info.type == MF_FILE && !user) return MF_IO_ERROR; |
|
|
|
|
rc = mf_disk_remove(&s->root, path, info.type == MF_DIRECTORY); |
|
|
|
|
struct mf_disk_info after; |
|
|
|
|
if (mf_disk_stat(&s->root, path, &after) == MF_NOT_FOUND) s->used -= info.size; |
|
|
|
|
int removed = rc == MF_OK; |
|
|
|
|
if (rc) { |
|
|
|
|
struct mf_disk_info after; int checked = mf_disk_stat(&s->root, path, &after); |
|
|
|
|
removed = checked == MF_NOT_FOUND; |
|
|
|
|
if (user && checked && !removed) { |
|
|
|
|
user->valid = 0; |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "member_files: remove outcome uncertain owner=%llu path=%s", (unsigned long long)q->owner, path); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if (removed && user) { |
|
|
|
|
int updated = store_user_update(user, info.size, 0, 0, "remove"); |
|
|
|
|
if (!rc) rc = updated; |
|
|
|
|
} |
|
|
|
|
return rc; |
|
|
|
|
} |
|
|
|
|
if (q->command == MF_RENAME) { |
|
|
|
|
@ -451,17 +504,24 @@ static int store_command(struct mf_storage* s, uint64_t group, uint64_t peer, co
|
|
|
|
|
|
|
|
|
|
void mf_storage_execute(struct mf_storage* s, uint64_t group, uint64_t peer, const struct mf_message* q, struct mf_message* r) { |
|
|
|
|
*r = *q; r->kind = MF_RESPONSE; r->status = MF_OK; r->data_len = 0; r->more = 0; |
|
|
|
|
if (!group || !peer || !q->owner || (q->command >= MF_BEGIN_WRITE && q->owner != peer)) { |
|
|
|
|
if (!group || !peer || !q->owner || (q->command >= MF_BEGIN_WRITE && q->command != MF_QUOTA && q->owner != peer)) { |
|
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "member_files: storage ACL denied group=%llu peer=%llu owner=%llu command=%u", |
|
|
|
|
(unsigned long long)group, (unsigned long long)peer, (unsigned long long)q->owner, q->command); |
|
|
|
|
r->status = MF_ACCESS_DENIED; return; |
|
|
|
|
} |
|
|
|
|
uint64_t now = get_time_tb(); |
|
|
|
|
if (!s->ready) { |
|
|
|
|
r->status = (uint16_t)s->init_status; |
|
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "member_files: storage not ready root=%s status=%s", s->path, mf_status_text(r->status)); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
struct store_user* user = store_user_get(s, q->owner, 0); |
|
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, |
|
|
|
|
"member_files: storage request id=%llu cmd=%s path=%s offset=%llu size=%llu used=%llu reserved=%llu quota=%llu", |
|
|
|
|
(unsigned long long)q->request_id, mf_command_text(q->command), q->path, (unsigned long long)q->offset, |
|
|
|
|
(unsigned long long)q->size, (unsigned long long)s->used, (unsigned long long)s->reserved, (unsigned long long)s->quota); |
|
|
|
|
int mutates = q->command == MF_COMMIT || q->command >= MF_MKDIR; |
|
|
|
|
(unsigned long long)q->size, (unsigned long long)(user ? user->used : 0), |
|
|
|
|
(unsigned long long)(user ? user->reserved : 0), (unsigned long long)s->quota); |
|
|
|
|
int mutates = q->command == MF_COMMIT || q->command == MF_MKDIR || q->command == MF_RENAME || q->command == MF_REMOVE; |
|
|
|
|
struct store_receipt* slot = NULL; |
|
|
|
|
uint8_t wire[MF_WIRE_MAX], fingerprint[32]; |
|
|
|
|
int n = mf_encode(q, wire, sizeof(wire)); |
|
|
|
|
@ -483,22 +543,19 @@ void mf_storage_execute(struct mf_storage* s, uint64_t group, uint64_t peer, con
|
|
|
|
|
} |
|
|
|
|
if (mutates && !slot) r->status = MF_BUSY; |
|
|
|
|
else { |
|
|
|
|
r->status = (uint16_t)store_initialize(s); |
|
|
|
|
if (!r->status) { |
|
|
|
|
mf_storage_sweep(s, now); r->status = (uint16_t)store_command(s, group, peer, q, r); |
|
|
|
|
/* Idle отсчитывается от завершения I/O, а не от начала медленного read/write. */ |
|
|
|
|
if (!r->status && (q->command == MF_READ || q->command == MF_WRITE || q->command == MF_BEGIN_WRITE)) { |
|
|
|
|
char accessed[MF_PATH_MAX + 80]; |
|
|
|
|
if (q->command == MF_READ) snprintf(accessed, sizeof(accessed), "%llu/%llu/%s", |
|
|
|
|
(unsigned long long)group, (unsigned long long)q->owner, q->path); |
|
|
|
|
else { |
|
|
|
|
struct store_upload* u = store_upload_find(s, group, peer, q->upload); |
|
|
|
|
if (u) { snprintf(accessed, sizeof(accessed), "%s", u->temp); u->touched = get_time_tb(); } |
|
|
|
|
else accessed[0] = 0; |
|
|
|
|
} |
|
|
|
|
for (unsigned i = 0; i < STORE_FILES; i++) if (s->files[i].fd >= 0 && !strcmp(s->files[i].path, accessed)) |
|
|
|
|
s->files[i].touched = get_time_tb(); |
|
|
|
|
mf_storage_sweep(s, now); r->status = (uint16_t)store_command(s, group, peer, q, r); |
|
|
|
|
/* Idle отсчитывается от завершения I/O, а не от начала медленного read/write. */ |
|
|
|
|
if (!r->status && (q->command == MF_READ || q->command == MF_WRITE || q->command == MF_BEGIN_WRITE)) { |
|
|
|
|
char accessed[MF_PATH_MAX + 80]; |
|
|
|
|
if (q->command == MF_READ) snprintf(accessed, sizeof(accessed), "%llu/%llu/%s", |
|
|
|
|
(unsigned long long)group, (unsigned long long)q->owner, q->path); |
|
|
|
|
else { |
|
|
|
|
struct store_upload* u = store_upload_find(s, group, peer, q->upload); |
|
|
|
|
if (u) { snprintf(accessed, sizeof(accessed), "%s", u->temp); u->touched = get_time_tb(); } |
|
|
|
|
else accessed[0] = 0; |
|
|
|
|
} |
|
|
|
|
for (unsigned i = 0; i < STORE_FILES; i++) if (s->files[i].fd >= 0 && !strcmp(s->files[i].path, accessed)) |
|
|
|
|
s->files[i].touched = get_time_tb(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if (!r->status && mutates) { |
|
|
|
|
|