/* Unix SMB/CIFS implementation. System QUOTA function wrappers Copyright (C) Stefan (metze) Metzmacher 2003 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; either version 3 of the License, or (at your option) any later version. 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 have received a copy of the GNU General Public License along with this program. If not, see . */ #include "includes.h" #include "lib/util/util_file.h" #include "lib/util/smb_strtox.h" #undef DBGC_CLASS #define DBGC_CLASS DBGC_QUOTA #ifdef HAVE_SYS_QUOTAS #if defined(HAVE_QUOTACTL_4A) /*#endif HAVE_QUOTACTL_4A */ #elif defined(HAVE_QUOTACTL_4B) /*#endif HAVE_QUOTACTL_4B */ #elif defined(HAVE_QUOTACTL_3) #error HAVE_QUOTACTL_3 not implemented /* #endif HAVE_QUOTACTL_3 */ #else /* NO_QUOTACTL_USED */ #endif /* NO_QUOTACTL_USED */ static bool sys_dev_to_bdev(TALLOC_CTX *mem_ctx, dev_t dev, char **mntpath, char **bdev, char **fs) { static bool have_proc_self_mountinfo = true; FILE *f = NULL; char *line = NULL; size_t len = 0; ssize_t nread; bool ret = false; if (!have_proc_self_mountinfo) { /* * try only once */ return false; } /* * /proc/self/mountinfo gives us all we need without stat()ing * every mountpoint. */ f = fopen("/proc/self/mountinfo", "r"); if (f == NULL) { have_proc_self_mountinfo = false; return false; } while ((nread = getline(&line, &len, f)) != -1) { unsigned long major, minor; char **entry = NULL; size_t entrylen; int num; entry = str_list_make(mem_ctx, line, " "); if (entry == NULL) { break; } entrylen = str_list_length((const char *const *)entry); if (entrylen < 11) { TALLOC_FREE(entry); continue; } num = sscanf(entry[2], "%lu:%lu", &major, &minor); if (num != 2) { TALLOC_FREE(entry); continue; } if (makedev(major, minor) != dev) { TALLOC_FREE(entry); continue; } *mntpath = talloc_move(mem_ctx, &entry[4]); *bdev = talloc_move(mem_ctx, &entry[9]); *fs = talloc_move(mem_ctx, &entry[8]); TALLOC_FREE(entry); ret = true; break; } SAFE_FREE(line); if (f != NULL) { fclose(f); f = NULL; } return ret; } #if defined(HAVE_MNTENT) && defined(HAVE_REALPATH) static int sys_path_to_bdev(TALLOC_CTX *mem_ctx, const char *path, char **mntpath, char **bdev, char **fs) { int ret = -1; SMB_STRUCT_STAT S; FILE *fp; struct mntent *mnt = NULL; SMB_DEV_T devno; char *stat_mntpath = NULL; char *p; /* find the block device file */ (*mntpath) = NULL; (*bdev) = NULL; (*fs) = NULL; if (sys_stat(path, &S, false) != 0) { return -1; } devno = S.st_ex_dev ; stat_mntpath = sys_realpath(path); if (stat_mntpath == NULL) { DBG_WARNING("realpath(%s) failed - %s\n", path, strerror(errno)); goto out; } if (sys_stat(stat_mntpath, &S, false) != 0) { DBG_WARNING("cannot stat real path %s - %s\n", stat_mntpath, strerror(errno)); goto out; } if (S.st_ex_dev != devno) { DBG_WARNING("device on real path has changed\n"); goto out; } while (true) { char save_ch; p = strrchr(stat_mntpath, '/'); if (p == NULL) { DBG_ERR("realpath for %s does not begin with a '/'\n", path); goto out; } if (p == stat_mntpath) { ++p; } save_ch = *p; *p = 0; if (sys_stat(stat_mntpath, &S, false) != 0) { DBG_WARNING("cannot stat real path component %s - %s\n", stat_mntpath, strerror(errno)); goto out; } if (S.st_ex_dev != devno) { *p = save_ch; break; } if (p <= stat_mntpath + 1) { break; } } fp = setmntent(MOUNTED,"r"); if (fp == NULL) { goto out; } while ((mnt = getmntent(fp))) { if (!strequal(mnt->mnt_dir, stat_mntpath)) { continue; } if ( sys_stat(mnt->mnt_dir, &S, false) == -1 ) continue ; if (S.st_ex_dev == devno) { (*mntpath) = talloc_strdup(mem_ctx, mnt->mnt_dir); (*bdev) = talloc_strdup(mem_ctx, mnt->mnt_fsname); (*fs) = talloc_strdup(mem_ctx, mnt->mnt_type); if ((*mntpath)&&(*bdev)&&(*fs)) { ret = 0; } else { TALLOC_FREE(*mntpath); TALLOC_FREE(*bdev); TALLOC_FREE(*fs); ret = -1; } break; } } endmntent(fp) ; out: SAFE_FREE(stat_mntpath); return ret; } /* #endif HAVE_MNTENT */ #elif defined(HAVE_DEVNM) /* we have this on HPUX, ... */ static int sys_path_to_bdev(TALLOC_CTX *mem_ctx, const char *path, char **mntpath, char **bdev, char **fs) { int ret = -1; char dev_disk[256]; SMB_STRUCT_STAT S; if (!path||!mntpath||!bdev||!fs) smb_panic("sys_path_to_bdev: called with NULL pointer"); (*mntpath) = NULL; (*bdev) = NULL; (*fs) = NULL; /* find the block device file */ if ((ret=sys_stat(path, &S, false))!=0) { return ret; } if ((ret=devnm(S_IFBLK, S.st_ex_dev, dev_disk, 256, 1))!=0) { return ret; } /* we should get the mntpath right... * but I don't know how * --metze */ (*mntpath) = talloc_strdup(mem_ctx, path); (*bdev) = talloc_strdup(mem_dev_disk); if ((*mntpath)&&(*bdev)) { ret = 0; } else { TALLOC_FREE(*mntpath); TALLOC_FREE(*bdev); ret = -1; } return ret; } /* #endif HAVE_DEVNM */ #else /* we should fake this up...*/ static int sys_path_to_bdev(TALLOC_CTX *mem_ctx, const char *path, char **mntpath, char **bdev, char **fs) { int ret = -1; if (!path||!mntpath||!bdev||!fs) smb_panic("sys_path_to_bdev: called with NULL pointer"); (*mntpath) = NULL; (*bdev) = NULL; (*fs) = NULL; (*mntpath) = talloc_strdup(mem_ctx, path); if (*mntpath) { ret = 0; } else { TALLOC_FREE(*mntpath); ret = -1; } return ret; } #endif /********************************************************************* Now the list of all filesystem specific quota systems we have found **********************************************************************/ static struct { const char *name; int (*get_quota)(const char *path, const char *bdev, enum SMB_QUOTA_TYPE qtype, unid_t id, SMB_DISK_QUOTA *dp); int (*set_quota)(const char *path, const char *bdev, enum SMB_QUOTA_TYPE qtype, unid_t id, SMB_DISK_QUOTA *dp); } sys_quota_backends[] = { #ifdef HAVE_JFS_QUOTA_H {"jfs2", sys_get_jfs2_quota, sys_set_jfs2_quota}, #endif #if defined HAVE_XFS_QUOTAS {"xfs", sys_get_xfs_quota, sys_set_xfs_quota}, {"gfs", sys_get_xfs_quota, sys_set_xfs_quota}, {"gfs2", sys_get_xfs_quota, sys_set_xfs_quota}, #endif /* HAVE_XFS_QUOTAS */ #ifdef HAVE_NFS_QUOTAS {"nfs", sys_get_nfs_quota, sys_set_nfs_quota}, {"nfs4", sys_get_nfs_quota, sys_set_nfs_quota}, #endif /* HAVE_NFS_QUOTAS */ {NULL, NULL, NULL} }; static int command_get_quota(const char *path, enum SMB_QUOTA_TYPE qtype, unid_t id, SMB_DISK_QUOTA *dp) { const struct loadparm_substitution *lp_sub = loadparm_s3_global_substitution(); const char *get_quota_command = NULL; char **lines = NULL; char *line = NULL; int _id = -1; char **argl = NULL; int ret; get_quota_command = lp_get_quota_command(talloc_tos(), lp_sub); if ((get_quota_command == NULL) || (get_quota_command[0] == '\0')) { errno = ENOSYS; return -1; } switch(qtype) { case SMB_USER_QUOTA_TYPE: case SMB_USER_FS_QUOTA_TYPE: _id = id.uid; break; case SMB_GROUP_QUOTA_TYPE: case SMB_GROUP_FS_QUOTA_TYPE: _id = id.gid; break; default: DBG_ERR("invalid quota type.\n"); return -1; } argl = str_list_make_empty(talloc_tos()); str_list_add_printf(&argl, "%s", get_quota_command); str_list_add_printf(&argl, "%s", path); str_list_add_printf(&argl, "%d", qtype); str_list_add_printf(&argl, "%d", _id); if (argl == NULL) { return -1; } DBG_NOTICE("Running command %s %s %d %d\n", get_quota_command, path, qtype, _id); lines = file_lines_ploadv(talloc_tos(), argl, NULL); TALLOC_FREE(argl); if ((lines == NULL) || (lines[0] == NULL)) { DBG_ERR("get_quota_command failed!\n"); TALLOC_FREE(lines); return -1; } line = lines[0]; DBG_NOTICE("Read output from get_quota, \"%s\"\n", line); dp->bsize = 1024; ret = sscanf(line, "%" SCNu32 " %" SCNu64 " %" SCNu64 " %" SCNu64 " %" SCNu64 " %" SCNu64 " %" SCNu64 " %" SCNu64, &dp->qflags, &dp->curblocks, &dp->softlimit, &dp->hardlimit, &dp->curinodes, &dp->isoftlimit, &dp->ihardlimit, &dp->bsize); TALLOC_FREE(lines); if (ret != 7) { DBG_ERR("The output of get_quota_command is invalid!\n"); return -1; } DBG_INFO("Parsed output of get_quota, ...\n" "qflags:%" PRIu32 " curblocks:%" PRIu64 " softlimit:%" PRIu64 " hardlimit:%" PRIu64 "\n" "curinodes:%" PRIu64 " isoftlimit:%" PRIu64 " ihardlimit:%" PRIu64 " bsize:%" PRIu64 "\n", dp->qflags, dp->curblocks, dp->softlimit, dp->hardlimit, dp->curinodes, dp->isoftlimit, dp->ihardlimit, dp->bsize); return 0; } static int command_set_quota(const char *path, enum SMB_QUOTA_TYPE qtype, unid_t id, SMB_DISK_QUOTA *dp) { const struct loadparm_substitution *lp_sub = loadparm_s3_global_substitution(); const char *set_quota_command = NULL; char **lines = NULL; int _id = -1; char **argl = NULL; set_quota_command = lp_set_quota_command(talloc_tos(), lp_sub); if ((set_quota_command == NULL) || (set_quota_command[0] == '\0')) { errno = ENOSYS; return -1; } switch (qtype) { case SMB_USER_QUOTA_TYPE: case SMB_USER_FS_QUOTA_TYPE: _id = id.uid; break; case SMB_GROUP_QUOTA_TYPE: case SMB_GROUP_FS_QUOTA_TYPE: _id = id.gid; break; default: return -1; } argl = str_list_make_empty(talloc_tos()); str_list_add_printf(&argl, "%s", set_quota_command); str_list_add_printf(&argl, "%s", path); str_list_add_printf(&argl, "%d", qtype); str_list_add_printf(&argl, "%d", _id); str_list_add_printf(&argl, "%u", dp->qflags); str_list_add_printf(&argl, "%" PRIu64, dp->softlimit); str_list_add_printf(&argl, "%" PRIu64, dp->hardlimit); str_list_add_printf(&argl, "%" PRIu64, dp->isoftlimit); str_list_add_printf(&argl, "%" PRIu64, dp->ihardlimit); str_list_add_printf(&argl, "%" PRIu64, dp->bsize); if (argl == NULL) { return -1; } DBG_NOTICE("Running command " "%s %s %d %d " "%" PRIu32 " %" PRIu64 " %" PRIu64 " " "%" PRIu64 " %" PRIu64 " %" PRIu64 "\n", set_quota_command, path, qtype, _id, dp->qflags, dp->softlimit, dp->hardlimit, dp->isoftlimit, dp->ihardlimit, dp->bsize); lines = file_lines_ploadv(talloc_tos(), argl, NULL); TALLOC_FREE(argl); if (lines) { char *line = lines[0]; DEBUG (3, ("Read output from set_quota, \"%s\"\n", line)); TALLOC_FREE(lines); return 0; } DEBUG (0, ("set_quota_command failed!\n")); return -1; } int sys_get_quota(dev_t dev, const char *path, enum SMB_QUOTA_TYPE qtype, unid_t id, SMB_DISK_QUOTA *dp) { bool ok; int ret = -1; int i; bool ready = False; char *mntpath = NULL; char *bdev = NULL; char *fs = NULL; if (!path||!dp) smb_panic("sys_get_quota: called with NULL pointer"); if (command_get_quota(path, qtype, id, dp)==0) { return 0; } else if (errno != ENOSYS) { return -1; } ok = sys_dev_to_bdev(talloc_tos(), dev, &mntpath, &bdev, &fs); if (!ok) { ret = sys_path_to_bdev( talloc_tos(), path, &mntpath, &bdev, &fs); if (ret != 0) { DEBUG(0,("sys_path_to_bdev() failed for path [%s]!\n",path)); return ret; } } errno = 0; DEBUG(10,("sys_get_quota() uid(%u, %u), fs(%s)\n", (unsigned)getuid(), (unsigned)geteuid(), fs)); for (i=0;(fs && sys_quota_backends[i].name && sys_quota_backends[i].get_quota);i++) { if (strcmp(fs,sys_quota_backends[i].name)==0) { ret = sys_quota_backends[i].get_quota(mntpath, bdev, qtype, id, dp); if (ret!=0) { DEBUG(3,("sys_get_%s_quota() failed for mntpath[%s] bdev[%s] qtype[%d] id[%d]: %s.\n", fs,mntpath,bdev,qtype,(qtype==SMB_GROUP_QUOTA_TYPE?id.gid:id.uid),strerror(errno))); } else { DEBUG(10,("sys_get_%s_quota() called for mntpath[%s] bdev[%s] qtype[%d] id[%d].\n", fs,mntpath,bdev,qtype,(qtype==SMB_GROUP_QUOTA_TYPE?id.gid:id.uid))); } ready = True; break; } } if (!ready) { /* use the default vfs quota functions */ ret=sys_get_vfs_quota(mntpath, bdev, qtype, id, dp); if (ret!=0) { DEBUG(3,("sys_get_%s_quota() failed for mntpath[%s] bdev[%s] qtype[%d] id[%d]: %s\n", "vfs",mntpath,bdev,qtype,(qtype==SMB_GROUP_QUOTA_TYPE?id.gid:id.uid),strerror(errno))); } else { DEBUG(10,("sys_get_%s_quota() called for mntpath[%s] bdev[%s] qtype[%d] id[%d].\n", "vfs",mntpath,bdev,qtype,(qtype==SMB_GROUP_QUOTA_TYPE?id.gid:id.uid))); } } TALLOC_FREE(mntpath); TALLOC_FREE(bdev); TALLOC_FREE(fs); return ret; } int sys_set_quota(dev_t dev, const char *path, enum SMB_QUOTA_TYPE qtype, unid_t id, SMB_DISK_QUOTA *dp) { bool ok; int ret = -1; int i; bool ready = False; char *mntpath = NULL; char *bdev = NULL; char *fs = NULL; /* find the block device file */ if (!path||!dp) smb_panic("get_smb_quota: called with NULL pointer"); if (command_set_quota(path, qtype, id, dp)==0) { return 0; } else if (errno != ENOSYS) { return -1; } ok = sys_dev_to_bdev(talloc_tos(), dev, &mntpath, &bdev, &fs); if (!ok) { ret = sys_path_to_bdev( talloc_tos(), path, &mntpath, &bdev, &fs); if (ret != 0) { DEBUG(0,("sys_path_to_bdev() failed for path [%s]!\n",path)); return ret; } } errno = 0; DEBUG(10,("sys_set_quota() uid(%u, %u)\n", (unsigned)getuid(), (unsigned)geteuid())); for (i=0;(fs && sys_quota_backends[i].name && sys_quota_backends[i].set_quota);i++) { if (strcmp(fs,sys_quota_backends[i].name)==0) { ret = sys_quota_backends[i].set_quota(mntpath, bdev, qtype, id, dp); if (ret!=0) { DEBUG(3,("sys_set_%s_quota() failed for mntpath[%s] bdev[%s] qtype[%d] id[%d]: %s.\n", fs,mntpath,bdev,qtype,(qtype==SMB_GROUP_QUOTA_TYPE?id.gid:id.uid),strerror(errno))); } else { DEBUG(10,("sys_set_%s_quota() called for mntpath[%s] bdev[%s] qtype[%d] id[%d].\n", fs,mntpath,bdev,qtype,(qtype==SMB_GROUP_QUOTA_TYPE?id.gid:id.uid))); } ready = True; break; } } if (!ready) { /* use the default vfs quota functions */ ret=sys_set_vfs_quota(mntpath, bdev, qtype, id, dp); if (ret!=0) { DEBUG(3,("sys_set_%s_quota() failed for mntpath[%s] bdev[%s] qtype[%d] id[%d]: %s.\n", "vfs",mntpath,bdev,qtype,(qtype==SMB_GROUP_QUOTA_TYPE?id.gid:id.uid),strerror(errno))); } else { DEBUG(10,("sys_set_%s_quota() called for mntpath[%s] bdev[%s] qtype[%d] id[%d].\n", "vfs",mntpath,bdev,qtype,(qtype==SMB_GROUP_QUOTA_TYPE?id.gid:id.uid))); } } TALLOC_FREE(mntpath); TALLOC_FREE(bdev); TALLOC_FREE(fs); return ret; } #else /* HAVE_SYS_QUOTAS */ void dummy_sysquotas_c(void); void dummy_sysquotas_c(void) { return; } #endif /* HAVE_SYS_QUOTAS */