|
|
|
@ -15,7 +15,7 @@ |
|
|
|
#define STORE_USERS 256 |
|
|
|
#define STORE_USERS 256 |
|
|
|
|
|
|
|
|
|
|
|
struct store_file { char path[MF_PATH_MAX + 80]; int fd; uint64_t touched; }; |
|
|
|
struct store_file { char path[MF_PATH_MAX + 80]; int fd; uint64_t touched; }; |
|
|
|
struct store_user { struct store_user* next; uint64_t owner, used, reserved; int valid; }; |
|
|
|
struct store_user { struct store_user* next; uint64_t owner, used, reserved, files, directories; int valid; }; |
|
|
|
struct store_upload { |
|
|
|
struct store_upload { |
|
|
|
uint64_t group, peer, owner, size, previous, reservation, touched; |
|
|
|
uint64_t group, peer, owner, size, previous, reservation, touched; |
|
|
|
struct store_user* user; |
|
|
|
struct store_user* user; |
|
|
|
@ -58,18 +58,27 @@ static void store_users_free(struct mf_storage* s) { |
|
|
|
s->user_count = 0; |
|
|
|
s->user_count = 0; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/* Один worker изменяет размер и резерв вместе. Повторы квитанций сюда не попадают. */ |
|
|
|
/* Один worker корректирует счётчики вместе. file_delta/dir_delta равны -1, 0 или +1; обхода дерева здесь нет. */ |
|
|
|
static int store_user_update(struct store_user* u, uint64_t previous, uint64_t size, uint64_t release, const char* operation) { |
|
|
|
static int store_user_update(struct store_user* u, uint64_t previous, uint64_t size, uint64_t release, |
|
|
|
if (!u->valid || previous > u->used || release > u->reserved || size > UINT64_MAX - (u->used - previous)) { |
|
|
|
int file_delta, int dir_delta, const char* operation) { |
|
|
|
|
|
|
|
if (!u->valid || previous > u->used || release > u->reserved || size > UINT64_MAX - (u->used - previous) || |
|
|
|
|
|
|
|
(file_delta < 0 && !u->files) || (file_delta > 0 && u->files == UINT64_MAX) || |
|
|
|
|
|
|
|
(dir_delta < 0 && !u->directories) || (dir_delta > 0 && u->directories == UINT64_MAX)) { |
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, |
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, |
|
|
|
"member_files: quota accounting invalid owner=%llu operation=%s used=%llu previous=%llu size=%llu reserved=%llu release=%llu", |
|
|
|
"member_files: quota accounting invalid owner=%llu operation=%s used=%llu previous=%llu size=%llu reserved=%llu release=%llu" |
|
|
|
|
|
|
|
" files=%llu file_delta=%d directories=%llu dir_delta=%d", |
|
|
|
(unsigned long long)u->owner, operation, (unsigned long long)u->used, (unsigned long long)previous, |
|
|
|
(unsigned long long)u->owner, operation, (unsigned long long)u->used, (unsigned long long)previous, |
|
|
|
(unsigned long long)size, (unsigned long long)u->reserved, (unsigned long long)release); |
|
|
|
(unsigned long long)size, (unsigned long long)u->reserved, (unsigned long long)release, |
|
|
|
|
|
|
|
(unsigned long long)u->files, file_delta, (unsigned long long)u->directories, dir_delta); |
|
|
|
u->valid = 0; return MF_IO_ERROR; |
|
|
|
u->valid = 0; return MF_IO_ERROR; |
|
|
|
} |
|
|
|
} |
|
|
|
u->used = u->used - previous + size; u->reserved -= release; |
|
|
|
u->used = u->used - previous + size; u->reserved -= release; |
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "member_files: quota updated owner=%llu operation=%s used=%llu reserved=%llu", |
|
|
|
if (file_delta > 0) u->files++; else if (file_delta < 0) u->files--; |
|
|
|
(unsigned long long)u->owner, operation, (unsigned long long)u->used, (unsigned long long)u->reserved); |
|
|
|
if (dir_delta > 0) u->directories++; else if (dir_delta < 0) u->directories--; |
|
|
|
|
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, |
|
|
|
|
|
|
|
"member_files: quota updated owner=%llu operation=%s used=%llu reserved=%llu files=%llu directories=%llu", |
|
|
|
|
|
|
|
(unsigned long long)u->owner, operation, (unsigned long long)u->used, (unsigned long long)u->reserved, |
|
|
|
|
|
|
|
(unsigned long long)u->files, (unsigned long long)u->directories); |
|
|
|
return MF_OK; |
|
|
|
return MF_OK; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -125,7 +134,7 @@ struct scan_context { |
|
|
|
int cleanup; |
|
|
|
int cleanup; |
|
|
|
}; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
/* Размеры UID суммируются по всем группам. Каталоги и .uploads не занимают пользовательскую квоту. */ |
|
|
|
/* Стартовый обход: содержимое UID по всем группам; технические корни и .uploads не входят в счётчики. */ |
|
|
|
static int store_scan_entry(void* arg, const char* name, const struct mf_disk_info* info) { |
|
|
|
static int store_scan_entry(void* arg, const char* name, const struct mf_disk_info* info) { |
|
|
|
struct scan_context* c = arg; |
|
|
|
struct scan_context* c = arg; |
|
|
|
char child[MF_PATH_MAX + 80]; |
|
|
|
char child[MF_PATH_MAX + 80]; |
|
|
|
@ -147,11 +156,18 @@ static int store_scan_entry(void* arg, const char* name, const struct mf_disk_in |
|
|
|
if (c->depth == 1 && !(user = store_user_get(c->s, id, 1))) return -MF_IO_ERROR; |
|
|
|
if (c->depth == 1 && !(user = store_user_get(c->s, id, 1))) return -MF_IO_ERROR; |
|
|
|
} |
|
|
|
} |
|
|
|
if (info->type == MF_FILE) { |
|
|
|
if (info->type == MF_FILE) { |
|
|
|
if (info->size > UINT64_MAX - user->used) { |
|
|
|
if (info->size > UINT64_MAX - user->used || user->files == UINT64_MAX) { |
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "member_files: scan size overflow owner=%llu", (unsigned long long)user->owner); |
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "member_files: scan size overflow owner=%llu", (unsigned long long)user->owner); |
|
|
|
return -MF_IO_ERROR; |
|
|
|
return -MF_IO_ERROR; |
|
|
|
} |
|
|
|
} |
|
|
|
user->used += info->size; return 0; |
|
|
|
user->used += info->size; user->files++; return 0; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (c->depth >= 2) { |
|
|
|
|
|
|
|
if (user->directories == UINT64_MAX) { |
|
|
|
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "member_files: scan directory count overflow owner=%llu", (unsigned long long)user->owner); |
|
|
|
|
|
|
|
return -MF_IO_ERROR; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
user->directories++; |
|
|
|
} |
|
|
|
} |
|
|
|
if (c->depth >= 64) return -MF_INVALID_PATH; |
|
|
|
if (c->depth >= 64) return -MF_INVALID_PATH; |
|
|
|
struct scan_context next = { .s = c->s, .user = user, .depth = c->depth + 1 }; |
|
|
|
struct scan_context next = { .s = c->s, .user = user, .depth = c->depth + 1 }; |
|
|
|
@ -167,7 +183,7 @@ int mf_storage_initialize(struct mf_storage* s) { |
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_MEDIA, "member_files: quota scan started root=%s user_quota=%llu", s->path, (unsigned long long)s->quota); |
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_MEDIA, "member_files: quota scan started root=%s user_quota=%llu", s->path, (unsigned long long)s->quota); |
|
|
|
int rc = mf_disk_root_open(&s->root, s->path); |
|
|
|
int rc = mf_disk_root_open(&s->root, s->path); |
|
|
|
if (rc) goto failed; |
|
|
|
if (rc) goto failed; |
|
|
|
rc = mf_disk_mkdir(&s->root, ".uploads"); |
|
|
|
rc = mf_disk_mkdir(&s->root, ".uploads", NULL); |
|
|
|
if (rc == MF_EXISTS) rc = MF_OK; |
|
|
|
if (rc == MF_EXISTS) rc = MF_OK; |
|
|
|
struct scan_context c = { .s = s, .cleanup = 1 }; |
|
|
|
struct scan_context c = { .s = s, .cleanup = 1 }; |
|
|
|
snprintf(c.path, sizeof(c.path), ".uploads"); |
|
|
|
snprintf(c.path, sizeof(c.path), ".uploads"); |
|
|
|
@ -177,8 +193,9 @@ int mf_storage_initialize(struct mf_storage* s) { |
|
|
|
if (rc) { mf_disk_root_close(&s->root); goto failed; } |
|
|
|
if (rc) { mf_disk_root_close(&s->root); goto failed; } |
|
|
|
s->ready = 1; s->init_status = MF_OK; |
|
|
|
s->ready = 1; s->init_status = MF_OK; |
|
|
|
for (unsigned i = 0; i < STORE_USERS; i++) for (struct store_user* u = s->users[i]; u; u = u->next) { |
|
|
|
for (unsigned i = 0; i < STORE_USERS; i++) for (struct store_user* u = s->users[i]; u; u = u->next) { |
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "member_files: quota scan owner=%llu used=%llu limit=%llu", |
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "member_files: quota scan owner=%llu used=%llu limit=%llu files=%llu directories=%llu", |
|
|
|
(unsigned long long)u->owner, (unsigned long long)u->used, (unsigned long long)s->quota); |
|
|
|
(unsigned long long)u->owner, (unsigned long long)u->used, (unsigned long long)s->quota, |
|
|
|
|
|
|
|
(unsigned long long)u->files, (unsigned long long)u->directories); |
|
|
|
if (u->used > s->quota) DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "member_files: user exceeds quota owner=%llu used=%llu limit=%llu", |
|
|
|
if (u->used > s->quota) DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "member_files: user exceeds quota owner=%llu used=%llu limit=%llu", |
|
|
|
(unsigned long long)u->owner, (unsigned long long)u->used, (unsigned long long)s->quota); |
|
|
|
(unsigned long long)u->owner, (unsigned long long)u->used, (unsigned long long)s->quota); |
|
|
|
} |
|
|
|
} |
|
|
|
@ -218,7 +235,7 @@ static int store_upload_discard(struct mf_storage* s, struct store_upload* u) { |
|
|
|
struct mf_disk_info after; |
|
|
|
struct mf_disk_info after; |
|
|
|
if (mf_disk_stat(&s->root, u->temp, &after) != MF_NOT_FOUND) return rc; |
|
|
|
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; |
|
|
|
int updated = store_user_update(u->user, 0, 0, u->reservation, 0, 0, "abort"); u->active = 0; |
|
|
|
return rc && rc != MF_NOT_FOUND ? rc : updated; |
|
|
|
return rc && rc != MF_NOT_FOUND ? rc : updated; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -256,11 +273,11 @@ void mf_storage_destroy(struct mf_storage* s) { |
|
|
|
static int store_base(struct mf_storage* s, uint64_t group, uint64_t owner) { |
|
|
|
static int store_base(struct mf_storage* s, uint64_t group, uint64_t owner) { |
|
|
|
char path[64]; struct mf_disk_info info; |
|
|
|
char path[64]; struct mf_disk_info info; |
|
|
|
snprintf(path, sizeof(path), "%llu", (unsigned long long)group); |
|
|
|
snprintf(path, sizeof(path), "%llu", (unsigned long long)group); |
|
|
|
int rc = mf_disk_mkdir(&s->root, path); |
|
|
|
int rc = mf_disk_mkdir(&s->root, path, NULL); |
|
|
|
if (rc == MF_EXISTS) rc = mf_disk_stat(&s->root, path, &info); |
|
|
|
if (rc == MF_EXISTS) rc = mf_disk_stat(&s->root, path, &info); |
|
|
|
if (rc) return rc; |
|
|
|
if (rc) return rc; |
|
|
|
snprintf(path, sizeof(path), "%llu/%llu", (unsigned long long)group, (unsigned long long)owner); |
|
|
|
snprintf(path, sizeof(path), "%llu/%llu", (unsigned long long)group, (unsigned long long)owner); |
|
|
|
rc = mf_disk_mkdir(&s->root, path); |
|
|
|
rc = mf_disk_mkdir(&s->root, path, NULL); |
|
|
|
if (rc == MF_EXISTS) rc = mf_disk_stat(&s->root, path, &info); |
|
|
|
if (rc == MF_EXISTS) rc = mf_disk_stat(&s->root, path, &info); |
|
|
|
return rc; |
|
|
|
return rc; |
|
|
|
} |
|
|
|
} |
|
|
|
@ -376,10 +393,13 @@ static int store_command(struct mf_storage* s, uint64_t group, uint64_t peer, co |
|
|
|
return MF_IO_ERROR; |
|
|
|
return MF_IO_ERROR; |
|
|
|
} |
|
|
|
} |
|
|
|
mf_put64(r->data, user ? user->used : 0); mf_put64(r->data + 8, s->quota); |
|
|
|
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; |
|
|
|
mf_put64(r->data + 16, user ? user->reserved : 0); |
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "member_files: quota response owner=%llu used=%llu reserved=%llu limit=%llu", |
|
|
|
mf_put64(r->data + 24, user ? user->files : 0); mf_put64(r->data + 32, user ? user->directories : 0); |
|
|
|
|
|
|
|
r->data_len = MF_QUOTA_BYTES; |
|
|
|
|
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "member_files: quota response owner=%llu used=%llu reserved=%llu limit=%llu files=%llu directories=%llu", |
|
|
|
(unsigned long long)q->owner, (unsigned long long)(user ? user->used : 0), |
|
|
|
(unsigned long long)q->owner, (unsigned long long)(user ? user->used : 0), |
|
|
|
(unsigned long long)(user ? user->reserved : 0), (unsigned long long)s->quota); |
|
|
|
(unsigned long long)(user ? user->reserved : 0), (unsigned long long)s->quota, |
|
|
|
|
|
|
|
(unsigned long long)(user ? user->files : 0), (unsigned long long)(user ? user->directories : 0)); |
|
|
|
return MF_OK; |
|
|
|
return MF_OK; |
|
|
|
} |
|
|
|
} |
|
|
|
if (q->command == MF_BEGIN_WRITE) return store_begin(s, group, peer, path, q); |
|
|
|
if (q->command == MF_BEGIN_WRITE) return store_begin(s, group, peer, path, q); |
|
|
|
@ -407,6 +427,7 @@ static int store_command(struct mf_storage* s, uint64_t group, uint64_t peer, co |
|
|
|
return MF_IO_ERROR; |
|
|
|
return MF_IO_ERROR; |
|
|
|
} |
|
|
|
} |
|
|
|
uint64_t previous = info.size; |
|
|
|
uint64_t previous = info.size; |
|
|
|
|
|
|
|
int new_file = info.type != MF_FILE; // Нулевой размер существующего файла не означает создание нового.
|
|
|
|
store_invalidate(s); |
|
|
|
store_invalidate(s); |
|
|
|
rc = mf_disk_rename(&s->root, u->temp, path); |
|
|
|
rc = mf_disk_rename(&s->root, u->temp, path); |
|
|
|
/* Даже при ошибке fsync rename уже мог произойти; сверяем существование temp. */ |
|
|
|
/* Даже при ошибке fsync rename уже мог произойти; сверяем существование temp. */ |
|
|
|
@ -420,7 +441,7 @@ static int store_command(struct mf_storage* s, uint64_t group, uint64_t peer, co |
|
|
|
} |
|
|
|
} |
|
|
|
return rc ? rc : temp_status ? temp_status : MF_IO_ERROR; |
|
|
|
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; |
|
|
|
int updated = store_user_update(u->user, previous, u->size, u->reservation, new_file, 0, "commit"); u->active = 0; |
|
|
|
if (rc) return rc; |
|
|
|
if (rc) return rc; |
|
|
|
if (updated) return updated; |
|
|
|
if (updated) return updated; |
|
|
|
rc = mf_disk_stat(&s->root, path, &info); |
|
|
|
rc = mf_disk_stat(&s->root, path, &info); |
|
|
|
@ -434,8 +455,21 @@ static int store_command(struct mf_storage* s, uint64_t group, uint64_t peer, co |
|
|
|
if (q->command == MF_MKDIR) { |
|
|
|
if (q->command == MF_MKDIR) { |
|
|
|
for (unsigned i = 0; i < STORE_UPLOADS; i++) |
|
|
|
for (unsigned i = 0; i < STORE_UPLOADS; i++) |
|
|
|
if (s->uploads[i].active && !strcmp(s->uploads[i].path, path)) return MF_BUSY; |
|
|
|
if (s->uploads[i].active && !strcmp(s->uploads[i].path, path)) return MF_BUSY; |
|
|
|
|
|
|
|
struct store_user* user = store_user_get(s, q->owner, 1); |
|
|
|
|
|
|
|
if (!user) return MF_IO_ERROR; |
|
|
|
|
|
|
|
if (!user->valid) { |
|
|
|
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "member_files: mkdir quota account invalid owner=%llu", (unsigned long long)q->owner); |
|
|
|
|
|
|
|
return MF_IO_ERROR; |
|
|
|
|
|
|
|
} |
|
|
|
rc = store_base(s, group, q->owner); |
|
|
|
rc = store_base(s, group, q->owner); |
|
|
|
if (!rc) { store_invalidate(s); rc = mf_disk_mkdir(&s->root, path); } |
|
|
|
if (!rc) { |
|
|
|
|
|
|
|
int created; |
|
|
|
|
|
|
|
store_invalidate(s); rc = mf_disk_mkdir(&s->root, path, &created); |
|
|
|
|
|
|
|
if (created) { |
|
|
|
|
|
|
|
int updated = store_user_update(user, 0, 0, 0, 0, 1, "mkdir"); |
|
|
|
|
|
|
|
if (!rc) rc = updated; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
return rc; |
|
|
|
return rc; |
|
|
|
} |
|
|
|
} |
|
|
|
rc = mf_disk_stat(&s->root, path, &info); |
|
|
|
rc = mf_disk_stat(&s->root, path, &info); |
|
|
|
@ -477,8 +511,8 @@ static int store_command(struct mf_storage* s, uint64_t group, uint64_t peer, co |
|
|
|
} |
|
|
|
} |
|
|
|
store_invalidate(s); |
|
|
|
store_invalidate(s); |
|
|
|
if (q->command == MF_REMOVE) { |
|
|
|
if (q->command == MF_REMOVE) { |
|
|
|
struct store_user* user = info.type == MF_FILE ? store_user_get(s, q->owner, 1) : NULL; |
|
|
|
struct store_user* user = store_user_get(s, q->owner, 1); |
|
|
|
if (info.type == MF_FILE && !user) return MF_IO_ERROR; |
|
|
|
if (!user) return MF_IO_ERROR; |
|
|
|
rc = mf_disk_remove(&s->root, path, info.type == MF_DIRECTORY); |
|
|
|
rc = mf_disk_remove(&s->root, path, info.type == MF_DIRECTORY); |
|
|
|
int removed = rc == MF_OK; |
|
|
|
int removed = rc == MF_OK; |
|
|
|
if (rc) { |
|
|
|
if (rc) { |
|
|
|
@ -490,7 +524,8 @@ static int store_command(struct mf_storage* s, uint64_t group, uint64_t peer, co |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
if (removed && user) { |
|
|
|
if (removed && user) { |
|
|
|
int updated = store_user_update(user, info.size, 0, 0, "remove"); |
|
|
|
int updated = store_user_update(user, info.size, 0, 0, info.type == MF_FILE ? -1 : 0, |
|
|
|
|
|
|
|
info.type == MF_DIRECTORY ? -1 : 0, "remove"); |
|
|
|
if (!rc) rc = updated; |
|
|
|
if (!rc) rc = updated; |
|
|
|
} |
|
|
|
} |
|
|
|
return rc; |
|
|
|
return rc; |
|
|
|
|