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