1
0
mirror of https://github.com/haiwen/seafile-server.git synced 2025-05-31 11:05:30 +00:00
seafile-server/server/quota-mgr.c
2020-10-15 15:26:01 +08:00

605 lines
18 KiB
C

/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
#include "common.h"
#define DEBUG_FLAG SEAFILE_DEBUG_OTHER
#include "log.h"
#include "utils.h"
#include "seafile-session.h"
#include "seaf-db.h"
#include "quota-mgr.h"
#include "seaf-utils.h"
#define KB 1000L
#define MB 1000000L
#define GB 1000000000L
#define TB 1000000000000L
static gint64
get_default_quota (SeafCfgManager *mgr)
{
char *quota_str;
char *end;
gint64 quota_int;
gint64 multiplier = GB;
gint64 quota;
quota_str = seaf_cfg_manager_get_config_string (mgr, "quota", "default");
if (!quota_str)
return INFINITE_QUOTA;
quota_int = strtoll (quota_str, &end, 10);
if (quota_int == LLONG_MIN || quota_int == LLONG_MAX) {
seaf_warning ("Default quota value out of range. Use unlimited.\n");
quota = INFINITE_QUOTA;
goto out;
}
if (*end != '\0') {
if (strcasecmp(end, "kb") == 0 || strcasecmp(end, "k") == 0)
multiplier = KB;
else if (strcasecmp(end, "mb") == 0 || strcasecmp(end, "m") == 0)
multiplier = MB;
else if (strcasecmp(end, "gb") == 0 || strcasecmp(end, "g") == 0)
multiplier = GB;
else if (strcasecmp(end, "tb") == 0 || strcasecmp(end, "t") == 0)
multiplier = TB;
else {
seaf_warning ("Invalid default quota format %s. Use unlimited.\n", quota_str);
quota = INFINITE_QUOTA;
goto out;
}
}
quota = quota_int * multiplier;
out:
g_free (quota_str);
return quota;
}
SeafQuotaManager *
seaf_quota_manager_new (struct _SeafileSession *session)
{
SeafQuotaManager *mgr = g_new0 (SeafQuotaManager, 1);
if (!mgr)
return NULL;
mgr->session = session;
mgr->calc_share_usage = g_key_file_get_boolean (session->config,
"quota", "calc_share_usage",
NULL);
return mgr;
}
int
seaf_quota_manager_init (SeafQuotaManager *mgr)
{
if (!mgr->session->create_tables && seaf_db_type (mgr->session->db) != SEAF_DB_TYPE_PGSQL)
return 0;
SeafDB *db = mgr->session->db;
const char *sql;
switch (seaf_db_type(db)) {
case SEAF_DB_TYPE_PGSQL:
sql = "CREATE TABLE IF NOT EXISTS UserQuota (\"user\" VARCHAR(255) PRIMARY KEY,"
"quota BIGINT)";
if (seaf_db_query (db, sql) < 0)
return -1;
sql = "CREATE TABLE IF NOT EXISTS UserShareQuota (\"user\" VARCHAR(255) PRIMARY KEY,"
"quota BIGINT)";
if (seaf_db_query (db, sql) < 0)
return -1;
sql = "CREATE TABLE IF NOT EXISTS OrgQuota (org_id INTEGER PRIMARY KEY,"
"quota BIGINT)";
if (seaf_db_query (db, sql) < 0)
return -1;
sql = "CREATE TABLE IF NOT EXISTS OrgUserQuota (org_id INTEGER,"
"\"user\" VARCHAR(255), quota BIGINT, PRIMARY KEY (org_id, \"user\"))";
if (seaf_db_query (db, sql) < 0)
return -1;
break;
case SEAF_DB_TYPE_SQLITE:
sql = "CREATE TABLE IF NOT EXISTS UserQuota (user VARCHAR(255) PRIMARY KEY,"
"quota BIGINT)";
if (seaf_db_query (db, sql) < 0)
return -1;
sql = "CREATE TABLE IF NOT EXISTS UserShareQuota (user VARCHAR(255) PRIMARY KEY,"
"quota BIGINT)";
if (seaf_db_query (db, sql) < 0)
return -1;
sql = "CREATE TABLE IF NOT EXISTS OrgQuota (org_id INTEGER PRIMARY KEY,"
"quota BIGINT)";
if (seaf_db_query (db, sql) < 0)
return -1;
sql = "CREATE TABLE IF NOT EXISTS OrgUserQuota (org_id INTEGER,"
"user VARCHAR(255), quota BIGINT, PRIMARY KEY (org_id, user))";
if (seaf_db_query (db, sql) < 0)
return -1;
break;
case SEAF_DB_TYPE_MYSQL:
sql = "CREATE TABLE IF NOT EXISTS UserQuota (id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, "
"user VARCHAR(255),"
"quota BIGINT, UNIQUE INDEX(user)) ENGINE=INNODB";
if (seaf_db_query (db, sql) < 0)
return -1;
sql = "CREATE TABLE IF NOT EXISTS UserShareQuota (id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, "
"user VARCHAR(255),"
"quota BIGINT, UNIQUE INDEX(user)) ENGINE=INNODB";
if (seaf_db_query (db, sql) < 0)
return -1;
sql = "CREATE TABLE IF NOT EXISTS OrgQuota (id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, "
"org_id INTEGER,"
"quota BIGINT, UNIQUE INDEX(org_id)) ENGINE=INNODB";
if (seaf_db_query (db, sql) < 0)
return -1;
sql = "CREATE TABLE IF NOT EXISTS OrgUserQuota (id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, "
"org_id INTEGER,"
"user VARCHAR(255), quota BIGINT, UNIQUE INDEX(org_id, user))"
"ENGINE=INNODB";
if (seaf_db_query (db, sql) < 0)
return -1;
break;
}
return 0;
}
int
seaf_quota_manager_set_user_quota (SeafQuotaManager *mgr,
const char *user,
gint64 quota)
{
SeafDB *db = mgr->session->db;
if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL) {
gboolean exists, err;
int rc;
exists = seaf_db_statement_exists (db,
"SELECT 1 FROM UserQuota WHERE \"user\"=?",
&err, 1, "string", user);
if (err)
return -1;
if (exists)
rc = seaf_db_statement_query (db,
"UPDATE UserQuota SET quota=? "
"WHERE \"user\"=?",
2, "int64", quota, "string", user);
else
rc = seaf_db_statement_query (db,
"INSERT INTO UserQuota (\"user\", quota) VALUES "
"(?, ?)",
2, "string", user, "int64", quota);
return rc;
} else {
int rc;
rc = seaf_db_statement_query (db,
"REPLACE INTO UserQuota (user, quota) VALUES (?, ?)",
2, "string", user, "int64", quota);
return rc;
}
}
gint64
seaf_quota_manager_get_user_quota (SeafQuotaManager *mgr,
const char *user)
{
char *sql;
gint64 quota;
if (seaf_db_type(mgr->session->db) != SEAF_DB_TYPE_PGSQL)
sql = "SELECT quota FROM UserQuota WHERE user=?";
else
sql = "SELECT quota FROM UserQuota WHERE \"user\"=?";
quota = seaf_db_statement_get_int64 (mgr->session->db, sql,
1, "string", user);
if (quota <= 0)
quota = get_default_quota (seaf->cfg_mgr);
return quota;
}
int
seaf_quota_manager_set_org_quota (SeafQuotaManager *mgr,
int org_id,
gint64 quota)
{
SeafDB *db = mgr->session->db;
if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL) {
gboolean exists, err;
int rc;
exists = seaf_db_statement_exists (db,
"SELECT 1 FROM OrgQuota WHERE org_id=?",
&err, 1, "int", org_id);
if (err)
return -1;
if (exists)
rc = seaf_db_statement_query (db,
"UPDATE OrgQuota SET quota=? WHERE org_id=?",
2, "int64", quota, "int", org_id);
else
rc = seaf_db_statement_query (db,
"INSERT INTO OrgQuota (org_id, quota) VALUES (?, ?)",
2, "int", org_id, "int64", quota);
return rc;
} else {
int rc = seaf_db_statement_query (db,
"REPLACE INTO OrgQuota (org_id, quota) VALUES (?, ?)",
2, "int", org_id, "int64", quota);
return rc;
}
}
gint64
seaf_quota_manager_get_org_quota (SeafQuotaManager *mgr,
int org_id)
{
char *sql;
gint64 quota;
sql = "SELECT quota FROM OrgQuota WHERE org_id=?";
quota = seaf_db_statement_get_int64 (mgr->session->db, sql, 1, "int", org_id);
if (quota <= 0)
quota = get_default_quota (seaf->cfg_mgr);
return quota;
}
int
seaf_quota_manager_set_org_user_quota (SeafQuotaManager *mgr,
int org_id,
const char *user,
gint64 quota)
{
SeafDB *db = mgr->session->db;
int rc;
if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL) {
gboolean exists, err;
exists = seaf_db_statement_exists (db,
"SELECT 1 FROM OrgUserQuota "
"WHERE org_id=? AND \"user\"=?",
&err, 2, "int", org_id, "string", user);
if (err)
return -1;
if (exists)
rc = seaf_db_statement_query (db,
"UPDATE OrgUserQuota SET quota=?"
" WHERE org_id=? AND \"user\"=?",
3, "int64", quota, "int", org_id,
"string", user);
else
rc = seaf_db_statement_query (db,
"INSERT INTO OrgUserQuota (org_id, \"user\", quota) VALUES "
"(?, ?, ?)",
3, "int", org_id, "string", user,
"int64", quota);
return rc;
} else {
rc = seaf_db_statement_query (db,
"REPLACE INTO OrgUserQuota (org_id, user, quota) VALUES (?, ?, ?)",
3, "int", org_id, "string", user, "int64", quota);
return rc;
}
}
gint64
seaf_quota_manager_get_org_user_quota (SeafQuotaManager *mgr,
int org_id,
const char *user)
{
char *sql;
gint64 quota;
if (seaf_db_type(mgr->session->db) != SEAF_DB_TYPE_PGSQL)
sql = "SELECT quota FROM OrgUserQuota WHERE org_id=? AND user=?";
else
sql = "SELECT quota FROM OrgUserQuota WHERE org_id=? AND \"user\"=?";
quota = seaf_db_statement_get_int64 (mgr->session->db, sql,
2, "int", org_id, "string", user);
/* return org quota if per user quota is not set. */
if (quota <= 0)
quota = seaf_quota_manager_get_org_quota (mgr, org_id);
return quota;
}
static void
count_group_members (GHashTable *user_hash, GList *members)
{
GList *p;
CcnetGroupUser *user;
const char *user_name;
int dummy;
for (p = members; p; p = p->next) {
user = p->data;
user_name = ccnet_group_user_get_user_name (user);
g_hash_table_insert (user_hash, g_strdup(user_name), &dummy);
/* seaf_debug ("Shared to %s.\n", user_name); */
g_object_unref (user);
}
g_list_free (members);
}
static gint
get_num_shared_to (const char *user, const char *repo_id)
{
GHashTable *user_hash;
int dummy;
GList *personal = NULL, *groups = NULL, *members = NULL, *p;
gint n_shared_to = -1;
/* seaf_debug ("Computing share usage for repo %s.\n", repo_id); */
/* If a repo is shared to both a user and a group, and that user is also
* a member of the group, we don't want to count that user twice.
* This also applies to two groups with overlapped members.
* So we have to use a hash table to filter out duplicated users.
*/
user_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
/* First count personal share */
personal = seaf_share_manager_list_shared_to (seaf->share_mgr, user, repo_id);
for (p = personal; p; p = p->next) {
char *email = p->data;
g_hash_table_insert (user_hash, g_strdup(email), &dummy);
/* seaf_debug ("Shared to %s.\n", email); */
}
/* Then groups... */
groups = seaf_repo_manager_get_groups_by_repo (seaf->repo_mgr,
repo_id, NULL);
for (p = groups; p; p = p->next) {
members = ccnet_group_manager_get_group_members (seaf->group_mgr, (int)(long)p->data, -1, -1, NULL);
if (!members) {
seaf_warning ("Cannot get member list for groupd %d.\n", (int)(long)p->data);
goto out;
}
count_group_members (user_hash, members);
}
/* Remove myself if i'm in a group. */
g_hash_table_remove (user_hash, user);
n_shared_to = g_hash_table_size(user_hash);
/* seaf_debug ("n_shared_to = %u.\n", n_shared_to); */
out:
g_hash_table_destroy (user_hash);
string_list_free (personal);
g_list_free (groups);
return n_shared_to;
}
int
seaf_quota_manager_check_quota_with_delta (SeafQuotaManager *mgr,
const char *repo_id,
gint64 delta)
{
SeafVirtRepo *vinfo;
const char *r_repo_id = repo_id;
char *user = NULL;
gint64 quota, usage;
int ret = 0;
/* If it's a virtual repo, check quota to origin repo. */
vinfo = seaf_repo_manager_get_virtual_repo_info (seaf->repo_mgr, repo_id);
if (vinfo)
r_repo_id = vinfo->origin_repo_id;
user = seaf_repo_manager_get_repo_owner (seaf->repo_mgr, r_repo_id);
if (user != NULL) {
if (g_strrstr (user, "dtable@seafile") != NULL)
goto out;
quota = seaf_quota_manager_get_user_quota (mgr, user);
} else {
seaf_warning ("Repo %s has no owner.\n", r_repo_id);
ret = -1;
goto out;
}
if (quota == INFINITE_QUOTA)
goto out;
usage = seaf_quota_manager_get_user_usage (mgr, user);
if (usage < 0) {
ret = -1;
goto out;
}
if (delta != 0) {
usage += delta;
}
if (usage >= quota) {
ret = 1;
}
out:
seaf_virtual_repo_info_free (vinfo);
g_free (user);
return ret;
}
int
seaf_quota_manager_check_quota (SeafQuotaManager *mgr,
const char *repo_id)
{
int ret = seaf_quota_manager_check_quota_with_delta (mgr, repo_id, 0);
if (ret == 1) {
return -1;
}
return ret;
}
gint64
seaf_quota_manager_get_user_usage (SeafQuotaManager *mgr, const char *user)
{
char *sql;
sql = "SELECT SUM(size) FROM "
"RepoOwner o LEFT JOIN VirtualRepo v ON o.repo_id=v.repo_id, "
"RepoSize WHERE "
"owner_id=? AND o.repo_id=RepoSize.repo_id "
"AND v.repo_id IS NULL";
return seaf_db_statement_get_int64 (mgr->session->db, sql,
1, "string", user);
/* Add size of repos in trash. */
/* sql = "SELECT size FROM RepoTrash WHERE owner_id = ?"; */
/* if (seaf_db_statement_foreach_row (mgr->session->db, sql, */
/* get_total_size, &total, */
/* 1, "string", user) < 0) */
/* return -1; */
}
static gint64
repo_share_usage (const char *user, const char *repo_id)
{
gint n_shared_to = get_num_shared_to (user, repo_id);
if (n_shared_to < 0) {
return -1;
} else if (n_shared_to == 0) {
return 0;
}
gint64 size = seaf_repo_manager_get_repo_size (seaf->repo_mgr, repo_id);
if (size < 0) {
seaf_warning ("Cannot get size of repo %s.\n", repo_id);
return -1;
}
/* share_usage = repo_size * n_shared_to */
gint64 usage = size * n_shared_to;
return usage;
}
gint64
seaf_quota_manager_get_user_share_usage (SeafQuotaManager *mgr,
const char *user)
{
GList *repos, *p;
char *repo_id;
gint64 total = 0, per_repo;
repos = seaf_repo_manager_get_repo_ids_by_owner (seaf->repo_mgr, user);
for (p = repos; p != NULL; p = p->next) {
repo_id = p->data;
per_repo = repo_share_usage (user, repo_id);
if (per_repo < 0) {
seaf_warning ("Failed to get repo %s share usage.\n", repo_id);
string_list_free (repos);
return -1;
}
total += per_repo;
}
string_list_free (repos);
return total;
}
gint64
seaf_quota_manager_get_org_usage (SeafQuotaManager *mgr, int org_id)
{
char *sql;
sql = "SELECT SUM(size) FROM OrgRepo, RepoSize WHERE "
"org_id=? AND OrgRepo.repo_id=RepoSize.repo_id";
return seaf_db_statement_get_int64 (mgr->session->db, sql,
1, "int", org_id);
}
gint64
seaf_quota_manager_get_org_user_usage (SeafQuotaManager *mgr,
int org_id,
const char *user)
{
char *sql;
sql = "SELECT SUM(size) FROM OrgRepo, RepoSize WHERE "
"org_id=? AND user = ? AND OrgRepo.repo_id=RepoSize.repo_id";
return seaf_db_statement_get_int64 (mgr->session->db, sql,
2, "int", org_id, "string", user);
}
static gboolean
collect_user_and_usage (SeafDBRow *row, void *data)
{
GList **p = data;
const char *user;
gint64 usage;
user = seaf_db_row_get_column_text (row, 0);
usage = seaf_db_row_get_column_int64 (row, 1);
if (!user)
return TRUE;
SeafileUserQuotaUsage *user_usage= g_object_new (SEAFILE_TYPE_USER_QUOTA_USAGE,
"user", user,
"usage", usage,
NULL);
if (!user_usage)
return FALSE;
*p = g_list_prepend (*p, user_usage);
return TRUE;
}
GList *
seaf_repo_quota_manager_list_user_quota_usage (SeafQuotaManager *mgr)
{
GList *ret = NULL;
char *sql = NULL;
sql = "SELECT owner_id,SUM(size) FROM "
"RepoOwner o LEFT JOIN VirtualRepo v ON o.repo_id=v.repo_id, "
"RepoSize WHERE "
"o.repo_id=RepoSize.repo_id "
"AND v.repo_id IS NULL "
"GROUP BY owner_id";
if (seaf_db_statement_foreach_row (mgr->session->db, sql,
collect_user_and_usage,
&ret, 0) < 0) {
g_list_free_full (ret, g_object_unref);
return NULL;
}
return g_list_reverse (ret);
}