From patchwork Wed Feb 11 15:11:53 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Konstantin Khlebnikov X-Patchwork-Id: 5813281 Return-Path: X-Original-To: patchwork-linux-fsdevel@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork2.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.136]) by patchwork2.web.kernel.org (Postfix) with ESMTP id DCDCBBF440 for ; Wed, 11 Feb 2015 15:13:53 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id B4129201C0 for ; Wed, 11 Feb 2015 15:13:52 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 4ACD5200D0 for ; Wed, 11 Feb 2015 15:13:51 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1753916AbbBKPMz (ORCPT ); Wed, 11 Feb 2015 10:12:55 -0500 Received: from forward-corp1f.mail.yandex.net ([95.108.130.40]:51019 "EHLO forward-corp1f.mail.yandex.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1753827AbbBKPL7 (ORCPT ); Wed, 11 Feb 2015 10:11:59 -0500 Received: from smtpcorp1m.mail.yandex.net (smtpcorp1m.mail.yandex.net [77.88.61.150]) by forward-corp1f.mail.yandex.net (Yandex) with ESMTP id 438632420338; Wed, 11 Feb 2015 18:11:54 +0300 (MSK) Received: from smtpcorp1m.mail.yandex.net (localhost [127.0.0.1]) by smtpcorp1m.mail.yandex.net (Yandex) with ESMTP id 1039A2CA03DD; Wed, 11 Feb 2015 18:11:54 +0300 (MSK) Received: from unknown (unknown [2a02:6b8:0:408:f4d2:daa0:d7a5:c625]) by smtpcorp1m.mail.yandex.net (nwsmtp/Yandex) with ESMTPSA id GCfuZV87j9-BsO8SDY2; Wed, 11 Feb 2015 18:11:54 +0300 (using TLSv1.2 with cipher AES128-SHA (128/128 bits)) (Client certificate not present) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=yandex-team.ru; s=default; t=1423667514; bh=7vcvZGuLQ4hb2qovdVW2Vofj3Nb7IZi6/6K3H/dRk50=; h=Subject:From:To:Cc:Date:Message-ID:In-Reply-To:References: User-Agent:MIME-Version:Content-Type:Content-Transfer-Encoding; b=VNIO2AW9e9lt5LXHwTtFYwxP5do4mU6opc+cd0r/2orthaJqrqQOmh0XxViub3aCu iiuYBewbWgtBi9UQVEwUEpRoE8kQ3RsnT3KaBS1ficlX0Ne6KtaRzbz25aITIHK8NI wlmsvHhCVL1fQ8eEbS2+Xd8fk8o8UApN2d06mrkM= Authentication-Results: smtpcorp1m.mail.yandex.net; dkim=pass header.i=@yandex-team.ru Subject: [PATCH RFC 6/6] tools/quota/project_quota: sample tool for early adopters From: Konstantin Khlebnikov To: Linux FS Devel , linux-ext4@vger.kernel.org, linux-kernel@vger.kernel.org Cc: Jan Kara , Linux API , containers@lists.linux-foundation.org, Dave Chinner , Andy Lutomirski , Christoph Hellwig , Dmitry Monakhov , "Eric W. Biederman" , Li Xi , Theodore Ts'o , Al Viro Date: Wed, 11 Feb 2015 18:11:53 +0300 Message-ID: <20150211151153.6717.51053.stgit@buzz> In-Reply-To: <20150211151146.6717.62017.stgit@buzz> References: <20150211151146.6717.62017.stgit@buzz> User-Agent: StGit/0.17.1-dirty MIME-Version: 1.0 Sender: linux-fsdevel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-fsdevel@vger.kernel.org X-Spam-Status: No, score=-6.8 required=5.0 tests=BAYES_00,DKIM_SIGNED, RCVD_IN_DNSWL_HI,T_DKIM_INVALID,T_RP_MATCHES_RCVD,UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP Usage: ./project_quota [args]... Commands: init initialize quota file on turn on off turn off info show project, usage and limits project [] get / set project id limit [] get / set space limit ilimit [] get / set inodes limit How to enable feature using debugfs tool: # debugfs debugfs: open -w debugfs: feature +FEATURE_R12 debugfs: quit # mount ... # project_quota init # project_quota on Signed-off-by: Konstantin Khlebnikov --- tools/quota/.gitignore | 1 tools/quota/Makefile | 6 + tools/quota/project_quota.c | 324 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 331 insertions(+) create mode 100644 tools/quota/.gitignore create mode 100644 tools/quota/Makefile create mode 100644 tools/quota/project_quota.c -- To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html diff --git a/tools/quota/.gitignore b/tools/quota/.gitignore new file mode 100644 index 0000000..4aacefc --- /dev/null +++ b/tools/quota/.gitignore @@ -0,0 +1 @@ +project_quota diff --git a/tools/quota/Makefile b/tools/quota/Makefile new file mode 100644 index 0000000..0c3daef --- /dev/null +++ b/tools/quota/Makefile @@ -0,0 +1,6 @@ +CFLAGS=-Wall -W + +project_quota: + +clean: + rm project_quota diff --git a/tools/quota/project_quota.c b/tools/quota/project_quota.c new file mode 100644 index 0000000..ca7f49a --- /dev/null +++ b/tools/quota/project_quota.c @@ -0,0 +1,324 @@ +/* + * project_quota: Tool for project disk quota manipulations + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should find a copy of v2 of the GNU General Public License somewhere on + * your Linux system; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA. + * + * Copyright (C) 2015 Yandex LLC + * + * Authors: Konstantin Khlebnikov + */ + +#define _FILE_OFFSET_BITS 64 +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef F_LINUX_SPECIFIC_BASE +#define F_LINUX_SPECIFIC_BASE 1024 +#endif + +#ifndef F_GET_PROJECT +#define F_GET_PROJECT (F_LINUX_SPECIFIC_BASE + 11) +#define F_SET_PROJECT (F_LINUX_SPECIFIC_BASE + 12) +#endif + +#ifndef PRJQUOTA +#define PRJQUOTA 2 +#endif + +/* First generic header */ +struct v2_disk_dqheader { + __le32 dqh_magic; /* Magic number identifying file */ + __le32 dqh_version; /* File version */ +}; + +/* Header with type and version specific information */ +struct v2_disk_dqinfo { + __le32 dqi_bgrace; /* Time before block soft limit becomes hard limit */ + __le32 dqi_igrace; /* Time before inode soft limit becomes hard limit */ + __le32 dqi_flags; /* Flags for quotafile (DQF_*) */ + __le32 dqi_blocks; /* Number of blocks in file */ + __le32 dqi_free_blk; /* Number of first free block in the list */ + __le32 dqi_free_entry; /* Number of block with at least one free entry */ +}; + +#define PROJECT_QUOTA_FILE "quota.project" +#define PROJECT_QUOTA_MAGIC 0xd9c03f14 + +static int find_mountpoint(const char *path, struct stat *path_st, + char **device, char **fstype, char **root_path) +{ + struct stat dev_st; + char *buf = NULL, *ptr, *real_device; + unsigned major, minor; + size_t len; + FILE *file; + + if (stat(path, path_st)) + return -1; + + *root_path = malloc(PATH_MAX + 1); + + /* since v2.6.26 */ + file = fopen("/proc/self/mountinfo", "r"); + if (!file) + goto parse_mounts; + while (getline(&buf, &len, file) > 0) { + sscanf(buf, "%*d %*d %u:%u %*s %s", &major, &minor, *root_path); + if (makedev(major, minor) != path_st->st_dev) + continue; + ptr = strstr(buf, " - ") + 3; + *fstype = strdup(strsep(&ptr, " ")); + *device = strdup(strsep(&ptr, " ")); + goto found; + } + +parse_mounts: + /* for older versions */ + file = fopen("/proc/mounts", "r"); + if (!file) + goto not_found; + while (getline(&buf, &len, file) > 0) { + ptr = buf; + strsep(&ptr, " "); + if (*buf != '/' || stat(buf, &dev_st) || + dev_st.st_rdev != path_st->st_dev) + continue; + strcpy(*root_path, strsep(&ptr, " ")); + *fstype = strdup(strsep(&ptr, " ")); + *device = strdup(buf); + goto found; + } +not_found: + free(*root_path); + errno = ENODEV; + return -1; + +found: + real_device = realpath(*device, NULL); + if (real_device) { + free(*device); + *device = real_device; + } + return 0; +} + +static int init_project_quota(const char *quota_path) +{ + struct { + struct v2_disk_dqheader header; + struct v2_disk_dqinfo info; + char zero[1024 * 2 - 8 * 4]; + } quota_init = { + .header = { + .dqh_magic = PROJECT_QUOTA_MAGIC, + .dqh_version = 1, + }, + .info = { + .dqi_bgrace = 7 * 24 * 60 * 60, + .dqi_igrace = 7 * 24 * 60 * 60, + .dqi_flags = 0, + .dqi_blocks = 2, /* header and root */ + .dqi_free_blk = 0, + .dqi_free_entry = 0, + }, + .zero = {0, }, + }; + int fd; + + fd = open(quota_path, O_CREAT|O_RDWR|O_EXCL, 0600); + if (fd < 0) + return fd; + write(fd, "a_init, sizeof(quota_init)); + fsync(fd); + close(fd); + return 0; +} + +static int get_project_id(const char *path, unsigned *project_id) +{ + int fd, ret; + + fd = open(path, O_PATH); + if (fd < 0) + return fd; + ret = fcntl(fd, F_GET_PROJECT, project_id); + close(fd); + return ret; +} + +static int set_project_id(const char *path, unsigned project_id) +{ + int fd, ret; + + fd = open(path, O_PATH); + if (fd < 0) + return fd; + ret = fcntl(fd, F_SET_PROJECT, project_id); + close(fd); + return ret; +} + +static void get_project_quota(const char *device, unsigned project_id, + struct if_dqblk *quota) +{ + if (quotactl(QCMD(Q_GETQUOTA, PRJQUOTA), device, + project_id, (caddr_t)quota)) + err(2, "cannot get project quota \"%u\" at \"%s\"", + project_id, device); +} + +static void set_project_quota(const char *device, unsigned project_id, + struct if_dqblk *quota) +{ + if (quotactl(QCMD(Q_SETQUOTA, PRJQUOTA), + device, project_id, (caddr_t)quota)) + err(2, "cannot set project quota \"%u\" at \"%s\"", + project_id, device); +} + +int main (int argc, char **argv) { + char *cmd, *path, *device, *fstype, *root_path; + struct if_dqblk quota; + struct stat path_st; + unsigned project_id; + + if (argc < 3) + goto usage; + + cmd = argv[1]; + path = argv[2]; + if (find_mountpoint(path, &path_st, &device, &fstype, &root_path)) + err(2, "cannot find mountpoint for \"%s\"", path); + + if (!strcmp(cmd, "limit") || !strcmp(cmd, "ilimit") || + !strcmp(cmd, "info") || !strcmp(cmd, "parent")) { + if (get_project_id(path, &project_id)) + err(2, "cannot get project id for \"%s\"", path); + } + + if (!strcmp(cmd, "init")) { + if (S_ISDIR(path_st.st_mode)) + asprintf(&path, "%s/%s", path, PROJECT_QUOTA_FILE); + + if (init_project_quota(path)) + err(2, "cannot init project quota file \"%s\"", path); + + } else if (!strcmp(cmd, "on")) { + struct v2_disk_dqheader header; + int fd, format; + + if (S_ISDIR(path_st.st_mode)) + asprintf(&path, "%s/%s", path, PROJECT_QUOTA_FILE); + + fd = open(path, O_RDONLY); + if (fd < 0) + err(2, "cannot open quota file \"%s\"", path); + if (read(fd, &header, sizeof(header)) != sizeof(header)) + err(2, "cannot read quota file \"%s\"", path); + close(fd); + + if (header.dqh_magic != PROJECT_QUOTA_MAGIC) + errx(2, "wrong quota file magic"); + + if (header.dqh_version == 1) + format = QFMT_VFS_V1; + else + errx(2, "unsupported quota file version"); + + if (mount(NULL, root_path, NULL, MS_REMOUNT, "prjquota")) + err(2, "cannot remount \"%s\"", root_path); + + if (quotactl(QCMD(Q_QUOTAON, PRJQUOTA), device, + format, (caddr_t)path)) + err(2, "cannot turn on project quota for %s", device); + + } else if (!strcmp(cmd, "off")) { + + if (quotactl(QCMD(Q_QUOTAOFF, PRJQUOTA), device, 0, NULL)) + err(2, "cannot turn off project quota for %s", device); + + } else if (!strcmp(cmd, "project")) { + if (argc < 4) { + if (get_project_id(path, &project_id)) + err(2, "cannot get project id for \"%s\"", path); + printf("%u\n", project_id); + } else { + project_id = atoi(argv[3]); + if (set_project_id(path, project_id)) + err(2, "cannot set project id for \"%s\"", path); + } + } else if (!strcmp(cmd, "limit")) { + if (argc < 4) { + get_project_quota(device, project_id, "a); + printf("%lld\n", quota.dqb_bhardlimit * QIF_DQBLKSIZE); + } else { + quota.dqb_bhardlimit = atoll(argv[3]) / QIF_DQBLKSIZE; + quota.dqb_bsoftlimit = 0; + quota.dqb_valid = QIF_BLIMITS; + set_project_quota(device, project_id, "a); + } + } else if (!strcmp(cmd, "ilimit")) { + if (argc < 4) { + get_project_quota(device, project_id, "a); + printf("%lld\n", quota.dqb_ihardlimit); + } else { + quota.dqb_ihardlimit = atoll(argv[3]); + quota.dqb_isoftlimit = 0; + quota.dqb_valid = QIF_ILIMITS; + set_project_quota(device, project_id, "a); + } + } else if (!strcmp(cmd, "info")) { + get_project_quota(device, project_id, "a); + printf("project %u\n", project_id); + printf("usage %llu\n", quota.dqb_curspace); + printf("limit %llu\n", quota.dqb_bhardlimit * QIF_DQBLKSIZE); + printf("inodes %llu\n", quota.dqb_curinodes); + printf("ilimit %llu\n", quota.dqb_ihardlimit); + } else { + warnx("Unknown command \"%s\"", cmd); + goto usage; + } + + free(device); + free(fstype); + free(root_path); + + return 0; + +usage: + fprintf(stderr, "Usage: %s [args]...\n" + "Commands:\n" + " init initialize quota file\n" + " on turn on\n" + " off turn off\n" + " info show project, usage and limits\n" + " project [] get / set project id\n" + " limit [] get / set space limit\n" + " ilimit [] get / set inodes limit\n", + argv[0]); + return 2; +}