1 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3 #include "common.h"
4 #define DEBUG_FLAG SEAFILE_DEBUG_OTHER
5 #include "log.h"
6 #include "utils.h"
7
8 #include "seafile-session.h"
9 #include "seaf-db.h"
10 #include "quota-mgr.h"
11 #include "seaf-utils.h"
12
13 #define KB 1000L
14 #define MB 1000000L
15 #define GB 1000000000L
16 #define TB 1000000000000L
17
18 static gint64
get_default_quota(SeafCfgManager * mgr)19 get_default_quota (SeafCfgManager *mgr)
20 {
21 char *quota_str;
22 char *end;
23 gint64 quota_int;
24 gint64 multiplier = GB;
25 gint64 quota;
26
27 quota_str = seaf_cfg_manager_get_config_string (mgr, "quota", "default");
28 if (!quota_str)
29 return INFINITE_QUOTA;
30
31 quota_int = strtoll (quota_str, &end, 10);
32 if (quota_int == LLONG_MIN || quota_int == LLONG_MAX) {
33 seaf_warning ("Default quota value out of range. Use unlimited.\n");
34 quota = INFINITE_QUOTA;
35 goto out;
36 }
37
38 if (*end != '\0') {
39 if (strcasecmp(end, "kb") == 0 || strcasecmp(end, "k") == 0)
40 multiplier = KB;
41 else if (strcasecmp(end, "mb") == 0 || strcasecmp(end, "m") == 0)
42 multiplier = MB;
43 else if (strcasecmp(end, "gb") == 0 || strcasecmp(end, "g") == 0)
44 multiplier = GB;
45 else if (strcasecmp(end, "tb") == 0 || strcasecmp(end, "t") == 0)
46 multiplier = TB;
47 else {
48 seaf_warning ("Invalid default quota format %s. Use unlimited.\n", quota_str);
49 quota = INFINITE_QUOTA;
50 goto out;
51 }
52 }
53
54 quota = quota_int * multiplier;
55
56 out:
57 g_free (quota_str);
58 return quota;
59 }
60
61 SeafQuotaManager *
seaf_quota_manager_new(struct _SeafileSession * session)62 seaf_quota_manager_new (struct _SeafileSession *session)
63 {
64 SeafQuotaManager *mgr = g_new0 (SeafQuotaManager, 1);
65 if (!mgr)
66 return NULL;
67 mgr->session = session;
68
69 mgr->calc_share_usage = g_key_file_get_boolean (session->config,
70 "quota", "calc_share_usage",
71 NULL);
72
73 return mgr;
74 }
75
76 int
seaf_quota_manager_init(SeafQuotaManager * mgr)77 seaf_quota_manager_init (SeafQuotaManager *mgr)
78 {
79
80 if (!mgr->session->create_tables && seaf_db_type (mgr->session->db) != SEAF_DB_TYPE_PGSQL)
81 return 0;
82
83 SeafDB *db = mgr->session->db;
84 const char *sql;
85
86 switch (seaf_db_type(db)) {
87 case SEAF_DB_TYPE_PGSQL:
88 sql = "CREATE TABLE IF NOT EXISTS UserQuota (\"user\" VARCHAR(255) PRIMARY KEY,"
89 "quota BIGINT)";
90 if (seaf_db_query (db, sql) < 0)
91 return -1;
92
93 sql = "CREATE TABLE IF NOT EXISTS UserShareQuota (\"user\" VARCHAR(255) PRIMARY KEY,"
94 "quota BIGINT)";
95 if (seaf_db_query (db, sql) < 0)
96 return -1;
97
98 sql = "CREATE TABLE IF NOT EXISTS OrgQuota (org_id INTEGER PRIMARY KEY,"
99 "quota BIGINT)";
100 if (seaf_db_query (db, sql) < 0)
101 return -1;
102
103 sql = "CREATE TABLE IF NOT EXISTS OrgUserQuota (org_id INTEGER,"
104 "\"user\" VARCHAR(255), quota BIGINT, PRIMARY KEY (org_id, \"user\"))";
105 if (seaf_db_query (db, sql) < 0)
106 return -1;
107
108 break;
109 case SEAF_DB_TYPE_SQLITE:
110 sql = "CREATE TABLE IF NOT EXISTS UserQuota (user VARCHAR(255) PRIMARY KEY,"
111 "quota BIGINT)";
112 if (seaf_db_query (db, sql) < 0)
113 return -1;
114
115 sql = "CREATE TABLE IF NOT EXISTS UserShareQuota (user VARCHAR(255) PRIMARY KEY,"
116 "quota BIGINT)";
117 if (seaf_db_query (db, sql) < 0)
118 return -1;
119
120 sql = "CREATE TABLE IF NOT EXISTS OrgQuota (org_id INTEGER PRIMARY KEY,"
121 "quota BIGINT)";
122 if (seaf_db_query (db, sql) < 0)
123 return -1;
124
125 sql = "CREATE TABLE IF NOT EXISTS OrgUserQuota (org_id INTEGER,"
126 "user VARCHAR(255), quota BIGINT, PRIMARY KEY (org_id, user))";
127 if (seaf_db_query (db, sql) < 0)
128 return -1;
129
130 break;
131 case SEAF_DB_TYPE_MYSQL:
132 sql = "CREATE TABLE IF NOT EXISTS UserQuota (id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, "
133 "user VARCHAR(255),"
134 "quota BIGINT, UNIQUE INDEX(user)) ENGINE=INNODB";
135 if (seaf_db_query (db, sql) < 0)
136 return -1;
137
138 sql = "CREATE TABLE IF NOT EXISTS UserShareQuota (id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, "
139 "user VARCHAR(255),"
140 "quota BIGINT, UNIQUE INDEX(user)) ENGINE=INNODB";
141 if (seaf_db_query (db, sql) < 0)
142 return -1;
143
144 sql = "CREATE TABLE IF NOT EXISTS OrgQuota (id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, "
145 "org_id INTEGER,"
146 "quota BIGINT, UNIQUE INDEX(org_id)) ENGINE=INNODB";
147 if (seaf_db_query (db, sql) < 0)
148 return -1;
149
150 sql = "CREATE TABLE IF NOT EXISTS OrgUserQuota (id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, "
151 "org_id INTEGER,"
152 "user VARCHAR(255), quota BIGINT, UNIQUE INDEX(org_id, user))"
153 "ENGINE=INNODB";
154 if (seaf_db_query (db, sql) < 0)
155 return -1;
156
157 break;
158 }
159
160 return 0;
161 }
162
163 int
seaf_quota_manager_set_user_quota(SeafQuotaManager * mgr,const char * user,gint64 quota)164 seaf_quota_manager_set_user_quota (SeafQuotaManager *mgr,
165 const char *user,
166 gint64 quota)
167 {
168 SeafDB *db = mgr->session->db;
169 if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL) {
170 gboolean exists, err;
171 int rc;
172
173 exists = seaf_db_statement_exists (db,
174 "SELECT 1 FROM UserQuota WHERE \"user\"=?",
175 &err, 1, "string", user);
176 if (err)
177 return -1;
178
179 if (exists)
180 rc = seaf_db_statement_query (db,
181 "UPDATE UserQuota SET quota=? "
182 "WHERE \"user\"=?",
183 2, "int64", quota, "string", user);
184 else
185 rc = seaf_db_statement_query (db,
186 "INSERT INTO UserQuota (\"user\", quota) VALUES "
187 "(?, ?)",
188 2, "string", user, "int64", quota);
189 return rc;
190 } else {
191 int rc;
192 rc = seaf_db_statement_query (db,
193 "REPLACE INTO UserQuota (user, quota) VALUES (?, ?)",
194 2, "string", user, "int64", quota);
195 return rc;
196 }
197 }
198
199 gint64
seaf_quota_manager_get_user_quota(SeafQuotaManager * mgr,const char * user)200 seaf_quota_manager_get_user_quota (SeafQuotaManager *mgr,
201 const char *user)
202 {
203 char *sql;
204 gint64 quota;
205
206 if (seaf_db_type(mgr->session->db) != SEAF_DB_TYPE_PGSQL)
207 sql = "SELECT quota FROM UserQuota WHERE user=?";
208 else
209 sql = "SELECT quota FROM UserQuota WHERE \"user\"=?";
210
211 quota = seaf_db_statement_get_int64 (mgr->session->db, sql,
212 1, "string", user);
213 if (quota <= 0)
214 quota = get_default_quota (seaf->cfg_mgr);
215
216 return quota;
217 }
218
219 int
seaf_quota_manager_set_org_quota(SeafQuotaManager * mgr,int org_id,gint64 quota)220 seaf_quota_manager_set_org_quota (SeafQuotaManager *mgr,
221 int org_id,
222 gint64 quota)
223 {
224 SeafDB *db = mgr->session->db;
225
226 if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL) {
227 gboolean exists, err;
228 int rc;
229
230 exists = seaf_db_statement_exists (db,
231 "SELECT 1 FROM OrgQuota WHERE org_id=?",
232 &err, 1, "int", org_id);
233 if (err)
234 return -1;
235
236 if (exists)
237 rc = seaf_db_statement_query (db,
238 "UPDATE OrgQuota SET quota=? WHERE org_id=?",
239 2, "int64", quota, "int", org_id);
240 else
241 rc = seaf_db_statement_query (db,
242 "INSERT INTO OrgQuota (org_id, quota) VALUES (?, ?)",
243 2, "int", org_id, "int64", quota);
244 return rc;
245 } else {
246 int rc = seaf_db_statement_query (db,
247 "REPLACE INTO OrgQuota (org_id, quota) VALUES (?, ?)",
248 2, "int", org_id, "int64", quota);
249 return rc;
250 }
251 }
252
253 gint64
seaf_quota_manager_get_org_quota(SeafQuotaManager * mgr,int org_id)254 seaf_quota_manager_get_org_quota (SeafQuotaManager *mgr,
255 int org_id)
256 {
257 char *sql;
258 gint64 quota;
259
260 sql = "SELECT quota FROM OrgQuota WHERE org_id=?";
261 quota = seaf_db_statement_get_int64 (mgr->session->db, sql, 1, "int", org_id);
262 if (quota <= 0)
263 quota = get_default_quota (seaf->cfg_mgr);
264
265 return quota;
266 }
267
268 int
seaf_quota_manager_set_org_user_quota(SeafQuotaManager * mgr,int org_id,const char * user,gint64 quota)269 seaf_quota_manager_set_org_user_quota (SeafQuotaManager *mgr,
270 int org_id,
271 const char *user,
272 gint64 quota)
273 {
274 SeafDB *db = mgr->session->db;
275 int rc;
276
277 if (seaf_db_type(db) == SEAF_DB_TYPE_PGSQL) {
278 gboolean exists, err;
279
280 exists = seaf_db_statement_exists (db,
281 "SELECT 1 FROM OrgUserQuota "
282 "WHERE org_id=? AND \"user\"=?",
283 &err, 2, "int", org_id, "string", user);
284 if (err)
285 return -1;
286
287 if (exists)
288 rc = seaf_db_statement_query (db,
289 "UPDATE OrgUserQuota SET quota=?"
290 " WHERE org_id=? AND \"user\"=?",
291 3, "int64", quota, "int", org_id,
292 "string", user);
293 else
294 rc = seaf_db_statement_query (db,
295 "INSERT INTO OrgUserQuota (org_id, \"user\", quota) VALUES "
296 "(?, ?, ?)",
297 3, "int", org_id, "string", user,
298 "int64", quota);
299 return rc;
300 } else {
301 rc = seaf_db_statement_query (db,
302 "REPLACE INTO OrgUserQuota (org_id, user, quota) VALUES (?, ?, ?)",
303 3, "int", org_id, "string", user, "int64", quota);
304 return rc;
305 }
306 }
307
308 gint64
seaf_quota_manager_get_org_user_quota(SeafQuotaManager * mgr,int org_id,const char * user)309 seaf_quota_manager_get_org_user_quota (SeafQuotaManager *mgr,
310 int org_id,
311 const char *user)
312 {
313 char *sql;
314 gint64 quota;
315
316 if (seaf_db_type(mgr->session->db) != SEAF_DB_TYPE_PGSQL)
317 sql = "SELECT quota FROM OrgUserQuota WHERE org_id=? AND user=?";
318 else
319 sql = "SELECT quota FROM OrgUserQuota WHERE org_id=? AND \"user\"=?";
320
321 quota = seaf_db_statement_get_int64 (mgr->session->db, sql,
322 2, "int", org_id, "string", user);
323 /* return org quota if per user quota is not set. */
324 if (quota <= 0)
325 quota = seaf_quota_manager_get_org_quota (mgr, org_id);
326
327 return quota;
328 }
329
330 static void
count_group_members(GHashTable * user_hash,GList * members)331 count_group_members (GHashTable *user_hash, GList *members)
332 {
333 GList *p;
334 CcnetGroupUser *user;
335 const char *user_name;
336 int dummy;
337
338 for (p = members; p; p = p->next) {
339 user = p->data;
340 user_name = ccnet_group_user_get_user_name (user);
341 g_hash_table_insert (user_hash, g_strdup(user_name), &dummy);
342 /* seaf_debug ("Shared to %s.\n", user_name); */
343 g_object_unref (user);
344 }
345
346 g_list_free (members);
347 }
348
349 static gint
get_num_shared_to(const char * user,const char * repo_id)350 get_num_shared_to (const char *user, const char *repo_id)
351 {
352 GHashTable *user_hash;
353 int dummy;
354 GList *personal = NULL, *groups = NULL, *members = NULL, *p;
355 gint n_shared_to = -1;
356
357 /* seaf_debug ("Computing share usage for repo %s.\n", repo_id); */
358
359 /* If a repo is shared to both a user and a group, and that user is also
360 * a member of the group, we don't want to count that user twice.
361 * This also applies to two groups with overlapped members.
362 * So we have to use a hash table to filter out duplicated users.
363 */
364 user_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
365
366 /* First count personal share */
367 personal = seaf_share_manager_list_shared_to (seaf->share_mgr, user, repo_id);
368 for (p = personal; p; p = p->next) {
369 char *email = p->data;
370 g_hash_table_insert (user_hash, g_strdup(email), &dummy);
371 /* seaf_debug ("Shared to %s.\n", email); */
372 }
373
374 /* Then groups... */
375 groups = seaf_repo_manager_get_groups_by_repo (seaf->repo_mgr,
376 repo_id, NULL);
377 for (p = groups; p; p = p->next) {
378 members = ccnet_group_manager_get_group_members (seaf->group_mgr, (int)(long)p->data, -1, -1, NULL);
379 if (!members) {
380 seaf_warning ("Cannot get member list for groupd %d.\n", (int)(long)p->data);
381 goto out;
382 }
383
384 count_group_members (user_hash, members);
385 }
386
387 /* Remove myself if i'm in a group. */
388 g_hash_table_remove (user_hash, user);
389
390 n_shared_to = g_hash_table_size(user_hash);
391 /* seaf_debug ("n_shared_to = %u.\n", n_shared_to); */
392
393 out:
394 g_hash_table_destroy (user_hash);
395 string_list_free (personal);
396 g_list_free (groups);
397
398 return n_shared_to;
399 }
400
401 int
seaf_quota_manager_check_quota_with_delta(SeafQuotaManager * mgr,const char * repo_id,gint64 delta)402 seaf_quota_manager_check_quota_with_delta (SeafQuotaManager *mgr,
403 const char *repo_id,
404 gint64 delta)
405 {
406 SeafVirtRepo *vinfo;
407 const char *r_repo_id = repo_id;
408 char *user = NULL;
409 gint64 quota, usage;
410 int ret = 0;
411
412 /* If it's a virtual repo, check quota to origin repo. */
413 vinfo = seaf_repo_manager_get_virtual_repo_info (seaf->repo_mgr, repo_id);
414 if (vinfo)
415 r_repo_id = vinfo->origin_repo_id;
416
417 user = seaf_repo_manager_get_repo_owner (seaf->repo_mgr, r_repo_id);
418 if (user != NULL) {
419 if (g_strrstr (user, "dtable@seafile") != NULL)
420 goto out;
421 quota = seaf_quota_manager_get_user_quota (mgr, user);
422 } else {
423 seaf_warning ("Repo %s has no owner.\n", r_repo_id);
424 ret = -1;
425 goto out;
426 }
427
428 if (quota == INFINITE_QUOTA)
429 goto out;
430
431 usage = seaf_quota_manager_get_user_usage (mgr, user);
432 if (usage < 0) {
433 ret = -1;
434 goto out;
435 }
436
437 if (delta != 0) {
438 usage += delta;
439 }
440 if (usage >= quota) {
441 ret = 1;
442 }
443
444 out:
445 seaf_virtual_repo_info_free (vinfo);
446 g_free (user);
447 return ret;
448 }
449
450 int
seaf_quota_manager_check_quota(SeafQuotaManager * mgr,const char * repo_id)451 seaf_quota_manager_check_quota (SeafQuotaManager *mgr,
452 const char *repo_id)
453 {
454 int ret = seaf_quota_manager_check_quota_with_delta (mgr, repo_id, 0);
455
456 if (ret == 1) {
457 return -1;
458 }
459 return ret;
460 }
461
462 gint64
seaf_quota_manager_get_user_usage(SeafQuotaManager * mgr,const char * user)463 seaf_quota_manager_get_user_usage (SeafQuotaManager *mgr, const char *user)
464 {
465 char *sql;
466
467 sql = "SELECT SUM(size) FROM "
468 "RepoOwner o LEFT JOIN VirtualRepo v ON o.repo_id=v.repo_id, "
469 "RepoSize WHERE "
470 "owner_id=? AND o.repo_id=RepoSize.repo_id "
471 "AND v.repo_id IS NULL";
472
473 return seaf_db_statement_get_int64 (mgr->session->db, sql,
474 1, "string", user);
475
476 /* Add size of repos in trash. */
477 /* sql = "SELECT size FROM RepoTrash WHERE owner_id = ?"; */
478 /* if (seaf_db_statement_foreach_row (mgr->session->db, sql, */
479 /* get_total_size, &total, */
480 /* 1, "string", user) < 0) */
481 /* return -1; */
482 }
483
484 static gint64
repo_share_usage(const char * user,const char * repo_id)485 repo_share_usage (const char *user, const char *repo_id)
486 {
487 gint n_shared_to = get_num_shared_to (user, repo_id);
488 if (n_shared_to < 0) {
489 return -1;
490 } else if (n_shared_to == 0) {
491 return 0;
492 }
493
494 gint64 size = seaf_repo_manager_get_repo_size (seaf->repo_mgr, repo_id);
495 if (size < 0) {
496 seaf_warning ("Cannot get size of repo %s.\n", repo_id);
497 return -1;
498 }
499
500 /* share_usage = repo_size * n_shared_to */
501 gint64 usage = size * n_shared_to;
502
503 return usage;
504 }
505
506 gint64
seaf_quota_manager_get_user_share_usage(SeafQuotaManager * mgr,const char * user)507 seaf_quota_manager_get_user_share_usage (SeafQuotaManager *mgr,
508 const char *user)
509 {
510 GList *repos, *p;
511 char *repo_id;
512 gint64 total = 0, per_repo;
513
514 repos = seaf_repo_manager_get_repo_ids_by_owner (seaf->repo_mgr, user);
515
516 for (p = repos; p != NULL; p = p->next) {
517 repo_id = p->data;
518 per_repo = repo_share_usage (user, repo_id);
519 if (per_repo < 0) {
520 seaf_warning ("Failed to get repo %s share usage.\n", repo_id);
521 string_list_free (repos);
522 return -1;
523 }
524
525 total += per_repo;
526 }
527
528 string_list_free (repos);
529 return total;
530 }
531
532 gint64
seaf_quota_manager_get_org_usage(SeafQuotaManager * mgr,int org_id)533 seaf_quota_manager_get_org_usage (SeafQuotaManager *mgr, int org_id)
534 {
535 char *sql;
536
537 sql = "SELECT SUM(size) FROM OrgRepo, RepoSize WHERE "
538 "org_id=? AND OrgRepo.repo_id=RepoSize.repo_id";
539
540 return seaf_db_statement_get_int64 (mgr->session->db, sql,
541 1, "int", org_id);
542 }
543
544 gint64
seaf_quota_manager_get_org_user_usage(SeafQuotaManager * mgr,int org_id,const char * user)545 seaf_quota_manager_get_org_user_usage (SeafQuotaManager *mgr,
546 int org_id,
547 const char *user)
548 {
549 char *sql;
550
551 sql = "SELECT SUM(size) FROM OrgRepo, RepoSize WHERE "
552 "org_id=? AND user = ? AND OrgRepo.repo_id=RepoSize.repo_id";
553
554 return seaf_db_statement_get_int64 (mgr->session->db, sql,
555 2, "int", org_id, "string", user);
556 }
557
558 static gboolean
collect_user_and_usage(SeafDBRow * row,void * data)559 collect_user_and_usage (SeafDBRow *row, void *data)
560 {
561 GList **p = data;
562 const char *user;
563 gint64 usage;
564
565 user = seaf_db_row_get_column_text (row, 0);
566 usage = seaf_db_row_get_column_int64 (row, 1);
567
568 if (!user)
569 return TRUE;
570
571 SeafileUserQuotaUsage *user_usage= g_object_new (SEAFILE_TYPE_USER_QUOTA_USAGE,
572 "user", user,
573 "usage", usage,
574 NULL);
575 if (!user_usage)
576 return FALSE;
577
578 *p = g_list_prepend (*p, user_usage);
579
580 return TRUE;
581 }
582
583 GList *
seaf_repo_quota_manager_list_user_quota_usage(SeafQuotaManager * mgr)584 seaf_repo_quota_manager_list_user_quota_usage (SeafQuotaManager *mgr)
585 {
586 GList *ret = NULL;
587 char *sql = NULL;
588
589 sql = "SELECT owner_id,SUM(size) FROM "
590 "RepoOwner o LEFT JOIN VirtualRepo v ON o.repo_id=v.repo_id, "
591 "RepoSize WHERE "
592 "o.repo_id=RepoSize.repo_id "
593 "AND v.repo_id IS NULL "
594 "GROUP BY owner_id";
595
596 if (seaf_db_statement_foreach_row (mgr->session->db, sql,
597 collect_user_and_usage,
598 &ret, 0) < 0) {
599 g_list_free_full (ret, g_object_unref);
600 return NULL;
601 }
602
603 return g_list_reverse (ret);
604 }
605