#pragma implementation #include #include #include #include #include #include #include #include #include "diskquotaconf.h" #include "diskquotaconf.m" #include "quota.h" #include "../paths.h" #include #include #include #include static const char K_QUOTA[]="diskquota"; static const char K_CMDFILE[]="cmdfile"; static const char K_MINUID[]="minuid"; static const char K_MINGID[]="mingid"; #ifdef __GLIBC__ #include #include // #include #include #else #include // #include #endif #include HELP_FILE help_quota ("diskquotaconf","quota"); static CONFIG_FILE f_quota (ETC_QUOTA_CONF,help_quota ,CONFIGF_MANAGED|CONFIGF_OPTIONAL ,"root","root",0600); static const char K_USER[]="user"; static const char K_USERDEF[]="userdef"; static const char K_USERDEF_G[]="userdef_g"; static const char K_GROUP[]="group"; static const char K_GROUPDEF[]="groupdef"; // Default for grace period is seven days. #define GRACE_DEF (7*24*60*60) /* #Specification: Quota management / strategy The files /quota.user and /quota.group are both configuration file and "work" files for the kernel. As such, their format is heavily ditacted by the performance requirement of the kernel itself. Mostly, these file are binary files and hold one record per potential users. As a configuration file, they are pretty poor. There is one record per user and it must be filled to be of any use. This defeats administration strategy such as providing defaults setup. While one can copy the setup of one user to another, as such providing some mecanism to apply a default, this is a one way situation: Once done, you can' tell if this user has a specific setup which has the same values as the default or simply inherited the default values. The strategy used by linuxconf does not managed the quota.user and quota.group files as configuration file. Linuxconf uses another file located in /etc to record the specifications and defaults. Using this new file, linuxconf is able to update the /quota.user and /quota.group. This new strategy allows one to # -leave a user account empty quota wise. The user account will receive some defaults values. -Change the default values and this will ripple to all users without specific setups. -Allows the definition of a per group default setup. (this is completly different from group quota). # */ /* #Specification: quota management / values All values stored in the quota.conf file may be in 3 states # 0: No limit -1: Inherit from defaults or group defaults >0: Use this limit # */ # PRIVATE void QUOTA_SPEC::init () { soft_maxk = soft_maxf = hard_maxk = hard_maxf = -1; grace_k.setfrom (GRACE_DEF); grace_f.setfrom (GRACE_DEF); } /* Reset to "open" (no limit) values */ PUBLIC void QUOTA_SPEC::reset () { soft_maxk = soft_maxf = hard_maxk = hard_maxf = 0; grace_k.setfrom (GRACE_DEF); grace_f.setfrom (GRACE_DEF); } /* Return true if any parameter do differ between two QUOTA_SPEC */ PUBLIC bool QUOTA_SPEC::isdiff(const QUOTA_SPEC &s) const { return soft_maxk != s.soft_maxk || soft_maxf != s.soft_maxf || hard_maxk != s.hard_maxk || hard_maxf != s.hard_maxf || grace_k.seconds != s.grace_k.seconds || grace_f.seconds != s.grace_f.seconds; } PUBLIC QUOTA_SPEC::QUOTA_SPEC() { init(); } PUBLIC QUOTA_SPEC::QUOTA_SPEC(const char *val) { init(); setfrom (val); } PUBLIC QUOTA_SPEC::QUOTA_SPEC(const QUOTA_SPEC *s) { setfrom (s); } PUBLIC void QUOTA_SPEC::setfrom(const QUOTA_SPEC *s) { name.setfrom (s->name); soft_maxk = s->soft_maxk; soft_maxf = s->soft_maxf; hard_maxk = s->hard_maxk; hard_maxf = s->hard_maxf; grace_k.setfrom (s->grace_k); grace_f.setfrom (s->grace_f); } PUBLIC void QUOTA_SPEC::setfrom (const char *val) { if (val != NULL){ val = name.copyword (val); long gk,gf; sscanf (val,"%d %d %d %d %ld %ld",&soft_maxk,&hard_maxk ,&soft_maxf,&hard_maxf,&gk,&gf); grace_k.setfrom (gk); grace_f.setfrom (gf); } } PUBLIC void QUOTA_SPEC::write ( CONFDB &conf, const char *key, const char *device) { char buf[200]; sprintf (buf,"%s %d %d %d %d %ld %ld",name.get() ,soft_maxk,hard_maxk,soft_maxf,hard_maxf ,grace_k.seconds,grace_f.seconds); conf.add (key,device,buf); } PUBLIC QUOTA_SPECS::QUOTA_SPECS( CONFDB &conf, const char *key, // user quota, group quota or group defaults const char *device) // Device on which those quota apply { SSTRINGS tb; int n = conf.getall (key,device,tb,0); for (int i=0; iget(); add (new QUOTA_SPEC(val)); } } PUBLIC QUOTA_SPECS::QUOTA_SPECS() { } PUBLIC void QUOTA_SPECS::write ( CONFDB &conf, const char *key, const char *device) { conf.removeall (key,device); int n = getnb(); for (int i=0; iwrite (conf,key,device); } } PUBLIC QUOTA_SPEC* QUOTA_SPECS::getitem (int no) const { return (QUOTA_SPEC*) ARRAY::getitem(no); } /* Locate one spec by name. Return NULL if not found */ PUBLIC QUOTA_SPEC* QUOTA_SPECS::getitem (const char *name) const { QUOTA_SPEC *ret = NULL; int n = getnb(); for (int i=0; iname.cmp(name)==0){ ret = sp; break; } } return ret; } PUBLIC QUOTA_DEV::QUOTA_DEV ( CONFDB &conf, const char *device, bool _has_quota_u, bool _has_quota_g) : userdef_g(conf,K_USERDEF_G,device), users (conf,K_USER,device), groups (conf,K_GROUP,device) { has_quota_u = _has_quota_u; has_quota_g = _has_quota_g; this->device.setfrom (device); userdef.setfrom (conf.getval (K_USERDEF,device)); userdef.name.setfrom ("none"); groupdef.setfrom (conf.getval(K_GROUPDEF,device)); groupdef.name.setfrom ("none"); } PUBLIC void QUOTA_DEV::write (CONFDB &conf) { const char *dev = device.get(); conf.removeall (K_USERDEF,dev); userdef.write (conf,K_USERDEF,dev); conf.removeall (K_GROUPDEF,dev); groupdef.write (conf,K_GROUPDEF,dev); users.write (conf,K_USER,dev); userdef_g.write (conf,K_USERDEF_G,dev); groups.write (conf,K_GROUP,dev); } static void quota_override (QUOTA_SPEC &eff, QUOTA_SPEC *one) { if (one != NULL){ if (one->soft_maxk != -1) eff.soft_maxk = one->soft_maxk; if (one->soft_maxf != -1) eff.soft_maxf = one->soft_maxf; if (one->hard_maxk != -1) eff.hard_maxk = one->hard_maxk; if (one->hard_maxf != -1) eff.hard_maxf = one->hard_maxf; if (one->grace_k.seconds != 0){ eff.grace_k.setfrom (one->grace_k); } if (one->grace_f.seconds != 0){ eff.grace_f.setfrom (one->grace_f); } } } /* Get the quota spec applicable to a user. Always return something useful, ultimatly, the device wide defaults. */ PUBLIC void QUOTA_DEV::geteffuserspec ( const char *name, const char *group, QUOTA_SPEC &eff) // Will contain the effective values { /* #Specification: Quota / user default management / strategy A given user may be in one of the following state (for the configuration of the user quota). He may # -have some specific quota values -not have any specific values, but there is a default defined for group he belongs. -not have any specific values and there is no default for his group, so the defaults are used. # Further the strategy is applied item per item of the quota record. This means that a user may have a soft quota on disk space but may inherit the other limits from the group defaults or the device defaults. */ eff.reset(); quota_override (eff,&userdef); quota_override (eff,userdef_g.getitem(group)); quota_override (eff,users.getitem(name)); } /* Get the quota spec applicable to a group. Always return something useful, ultimatly, the device wide defaults. */ PUBLIC void QUOTA_DEV::geteffgroupspec ( const char *group, QUOTA_SPEC &eff) { /* #Specification: Quota / group default management / strategy A given group may be in one of the following state (for the configuration of the group quota). He may # -have some specific quota values -not have any specific values, so the defaults are picked. # */ eff.reset(); quota_override (eff,&groupdef); quota_override (eff,groups.getitem(group)); } /* Return the quota specification for a user or NULL if the user do not have a specific record. */ PUBLIC QUOTA_SPEC *QUOTA_DEV::getuserspec (const char *name) { return users.getitem(name); } /* Return the quota specification for a group or NULL if the user do not have a specific record. */ PUBLIC QUOTA_SPEC *QUOTA_DEV::getgroupspec (const char *name) { return groups.getitem(name); } /* Return the quota specification for a per group defaults or NULL if there is not default for that group. */ PUBLIC QUOTA_SPEC *QUOTA_DEV::getuserdef_gspec (const char *name) { return userdef_g.getitem(name); } /* Delete the quota specification for a user */ PUBLIC int QUOTA_DEV::deluserspec (const char *name) { return users.remove_del (getuserspec(name)); } /* Delete the quota specification for a group. */ PUBLIC int QUOTA_DEV::delgroupspec (const char *name) { return groups.remove_del (getgroupspec(name)); } /* Delete the quota specification for the per group defaults. */ PUBLIC int QUOTA_DEV::deluserdef_gspec (const char *name) { return userdef_g.remove_del (getuserdef_gspec(name)); } #ifndef __GLIBC__ #if defined (__alpha__) #include #include #include int quotactl(int cmd, const char * special, int id, caddr_t addr) { return syscall(__NR_quotactl, cmd, special, id, addr); } #else #define __LIBRARY__ #include _syscall4(int, quotactl, int, cmd, const char *, special, int, id, caddr_t, addr); #endif #endif extern "C"{ #include "quotaapi/quota.h" } /* Read the current setting for a user */ int quota_read ( QUOTA_USED &used, int id, bool userquota, const char *device) { int ret = -1; used.block_used = used.inode_used = -1; #if 0 // This code does work, but it is sometime useful to use repquota // to extract the information. One interest is to have repquota // reporting on external volumes (modified repquota) not available // with the kernel interface int type = userquota ? QUOTA_USER : QUOTA_GROUP; quota_t *q = quota_new (type,id,(char*)device); if (quota_get(q) != 0){ used.block_used = q->block_used >> 10; used.inode_used = q->inode_used; ret = 0; } // fprintf (stderr,"quota_read %d %d %s %d -> %d %d\n",id,userquota,device,ret,used.block_used,used.inode_used); quota_delete (q); #else ret = show_getquota (device,userquota,id,used); #endif return ret; } static int quota_apply_real ( QUOTA_SPEC &spec, int id, bool userquota, const char *device) { #if 0 struct dqblk dq; // Translation from K to block is not done!!!!! dq.dqb_bhardlimit = spec.hard_maxk; dq.dqb_bsoftlimit = spec.soft_maxk; dq.dqb_ihardlimit = spec.hard_maxf; dq.dqb_isoftlimit = spec.soft_maxf; dq.dqb_btime = spec.grace_k.seconds; dq.dqb_itime = spec.grace_f.seconds; int type = userquota ? USRQUOTA : GRPQUOTA; return quotactl (QCMD(Q_SETQLIM,type),device,id,(caddr_t)&dq); #else int ret = -1; int type = userquota ? QUOTA_USER : QUOTA_GROUP; quota_t *q = quota_new (type,id,(char*)device); if (quota_get(q) != 0){ q->block_hard = spec.hard_maxk; q->block_soft = spec.soft_maxk; q->inode_hard = spec.hard_maxf; q->inode_soft = spec.soft_maxf; q->block_time = spec.grace_k.seconds; q->inode_time = spec.grace_f.seconds; ret = quota_set (q); } quota_delete (q); return ret; #endif } static FILE *fcmd = NULL; static POPEN *pcmd = NULL; static bool fcmd_checked = false; /* Close the batch command file if open. The file is re-open as needed. */ void quota_closecmdfile() { if (pcmd != NULL){ pcmd->closepipe(); while (pcmd->wait(10)>0){ char buf[1000]; while (pcmd->readerr(buf,sizeof(buf)-1)>0){ fprintf (stderr,"buferr=%s\n",buf); } } delete pcmd; pcmd = NULL; fcmd = NULL; }else if (fcmd != NULL){ fclose (fcmd); fcmd = NULL; } fcmd_checked = false; } /* Update the kernel or write to the batch command file. Return -1 if any error. */ int quota_apply ( QUOTA_SPEC &spec, int id, bool userquota, const char *device) { if (!fcmd_checked){ fcmd_checked = true; const char *bfile = linuxconf_getval (K_QUOTA,K_CMDFILE,NULL); if (bfile != NULL){ const char *pt = str_skip(bfile); if (pt[0] == '|'){ pcmd = new POPEN (pt+1); if (pcmd->isok()){ fcmd = pcmd->getfout(); }else{ delete pcmd; pcmd = NULL; } }else{ fcmd = xconf_fopen (pt,"a"); } } } int ret = -1; if (fcmd != NULL){ ret = 0; fprintf (fcmd,"%s %d %d %d %d %d %d %ld %ld\n" ,device,userquota,id ,spec.hard_maxk,spec.soft_maxk ,spec.hard_maxf,spec.soft_maxf ,spec.grace_k.seconds,spec.grace_f.seconds); }else{ ret = quota_apply_real (spec,id,userquota,device); } return ret; } /* Install the quota for a given user */ PUBLIC void QUOTA_DEV::applyone (struct passwd *p) { if (!user_isadmin(p->pw_name)){ struct group *g = getgrgid (p->pw_gid); if (g != NULL){ QUOTA_SPEC eff; geteffuserspec (p->pw_name,g->gr_name,eff); quota_apply (eff,p->pw_uid,true,realdev.get()); } } } /* Transform a LABEL= specification into a device Return -1 if this can't be done. */ PUBLIC int QUOTA_DEV::setrealdevice() { int ret = 0; const char *dev = device.get(); realdev.setfrom (dev); if (strncasecmp(dev,"LABEL=",6)==0){ dev = partition_findfromlabel(dev+6); if (dev == NULL){ xconf_error (MSG_U(E_TRANSLATELABEL ,"Can't translate the file system label %s to any device.\n" "Won't apply quota"),device.get()); ret = -1;; }else{ realdev.setfrom (dev); } } return ret; } /* Return the device associated with a volume. If the device is defined by a label, resolve. */ PUBLIC const char *QUOTA_DEV::getrealdevice() { if (realdev.is_empty()) setrealdevice(); return realdev.get(); } /* Fill the user and group quota for all users and group on the system It basically apply map /etc/quota.conf on this device */ PUBLIC void QUOTA_DEV::applyall() { if (setrealdevice()!=-1){ const char *devstr = realdev.get(); if (has_quota_u){ /* #Specification: minuid The minuid and mingid setting are only enforced when mapping defaults to all users and groups. It is still possible to set quota manually on specific group or user even if it located under minuid or mingid. */ unsigned minuid = linuxconf_getvalnum (K_QUOTA,K_MINUID,500); setpwent(); struct passwd *p; while ((p=getpwent())!=NULL){ if (p->pw_uid >= minuid){ applyone (p); } } } if (has_quota_g){ unsigned mingid = linuxconf_getvalnum (K_QUOTA,K_MINGID,500); setgrent (); struct group *g; while ((g=getgrent())!=NULL){ if (g->gr_gid >= mingid){ QUOTA_SPEC eff; geteffgroupspec (g->gr_name,eff); quota_apply (eff,g->gr_gid,false,devstr); } } } } } PUBLIC QUOTA_DEV *QUOTA_DEVS::getitem (int no) const { return (QUOTA_DEV*)ARRAY::getitem(no); } PUBLIC QUOTACTL::QUOTACTL() { conf = new CONFDB (f_quota); FSTAB fs; for (int i=0; ihas_quota_u() || e->has_quota_g()){ devs.add (new QUOTA_DEV (*conf,e->getsource() ,e->has_quota_u(),e->has_quota_g())); } } } PUBLIC QUOTACTL::~QUOTACTL() { delete conf; } PUBLIC int QUOTACTL::write (PRIVILEGE *priv) { for (int i=0; iwrite (*conf); } return conf->save(priv); } PUBLIC void QUOTA_SPEC::setupdia(DIALOG &dia) { static const char *tb[]={MSG_U(I_NOLIMIT,"No limit"),NULL}; static const int tbv[]={-1,0}; dia.newf_chkm_num (MSG_U(F_SOFTK,"Disk space soft limit"),soft_maxk ,tbv,tb); dia.newf_chkm_num (MSG_U(F_HARDK,"Disk space hard limit"),hard_maxk ,tbv,tb); dia.newf_str (MSG_U(F_GRACEK,"Disk space grace period"),grace_k); dia.newf_title ("",""); dia.newf_chkm_num (MSG_U(F_SOFTF,"Files soft limit"),soft_maxf ,tbv,tb); dia.newf_chkm_num (MSG_U(F_HARDF,"Files hard limit"),hard_maxf ,tbv,tb); dia.newf_str (MSG_U(F_GRACEF,"Files grace period"),grace_f); } PUBLIC void QUOTACTL::editdef () { if (devs.getnb()==0){ xconf_error (MSG_U(E_NODEVQUOTA, "There is currently no partition with disk quota enabled.\n" "Visit the \"Access local drive\" menu and enable\n" "disk quota on selected volumes.")); }else{ DIALOG dia; for (int i=0; idevice.get(); dia.newf_title (device,1,"",device); QUOTA_SPEC *s = &d->userdef; dia.newf_title (MSG_U(T_USERDEF,"User default"),2,"",MSG_R(T_USERDEF)); s->setupdia (dia); s = &d->groupdef; dia.newf_title (MSG_U(T_GROUPDEF,"Group default"),2,"",MSG_R(T_GROUPDEF)); s->setupdia (dia); } dia.newf_title (MSG_U(T_MODSETTINGS,"Module settings"),1,"" ,MSG_R(T_MODSETTINGS)); SSTRING bfile(linuxconf_getval(K_QUOTA,K_CMDFILE,"")); int minuid = linuxconf_getvalnum (K_QUOTA,K_MINUID,500); int mingid = linuxconf_getvalnum (K_QUOTA,K_MINGID,500); dia.newf_str (MSG_U(F_BATCHQFILE,"Batched quota commands"),bfile); dia.newf_num (MSG_U(F_MINUID,"Minimum user ID managed"),minuid); dia.newf_num (MSG_U(F_MINGID,"Minimum group ID managed"),mingid); int nof = 0; while (1){ MENU_STATUS code = dia.edit (MSG_U(T_QUOTADEF ,"Default quota for users and groups") ,MSG_U(I_QUOTADEF,"") ,help_quota ,nof); if (code == MENU_CANCEL || code == MENU_ESCAPE){ dia.restore(); break; }else{ linuxconf_replace_if (K_QUOTA,K_CMDFILE,bfile); linuxconf_replace (K_QUOTA,K_MINUID,minuid); linuxconf_replace (K_QUOTA,K_MINGID,mingid); linuxconf_save(); write(NULL); applyall(); quota_closecmdfile(); break; } } } } QUOTACTL *ctl; static short int nbuse=0; void quota_allocctl() { if (ctl == NULL){ ctl = new QUOTACTL; nbuse = 1; }else{ nbuse++; } } void quota_freectl() { nbuse--; if (nbuse == 0){ delete ctl; ctl = NULL; } } /* Edit the defaults for users and groups */ void quota_editdef () { quota_allocctl(); if (ctl != NULL) ctl->editdef(); quota_freectl(); } // Run through all users and group to update their quota // This is not efficient but will do the job PUBLIC void QUOTACTL::applyall () { for (int i=0; iapplyall(); } } // Run through all users and group to update their quota on a given device PUBLIC void QUOTACTL::applyall (const char *dev) { for (int i=0; idevice.cmp(dev)==0){ d->applyall(); break; } } } // Install the disk quota for one user on all devices PUBLIC int QUOTACTL::applyone (const char *user) { int ret = -1; struct passwd *p = getpwnam (user); if (p != NULL){ for (int i=0; isetrealdevice()!=-1) dev->applyone(p); } ret = 0; } return ret; } /* Install the disk quota for one user on all devices */ int quota_applyone (const char *user) { int ret = -1; quota_allocctl(); if (ctl != NULL) ret = ctl->applyone (user); quota_freectl(); return ret; } /* Set the quota limits for all users and groups on a device. */ void quota_applyall (const char *dev) { quota_allocctl(); if (ctl != NULL) ctl->applyall (dev); quota_freectl(); quota_closecmdfile(); } /* Walk all device with quota and update the quota record for all users This is used after having added some users without linuxconf (useradd) so the quota defaults apply correctly. */ void quota_applyall () { FSTAB fstab; for (int i=0; ihas_quota_u() || e->has_quota_g()){ const char *dev = e->getsource(); quota_applyall (dev); } } } PUBLIC QUOTA_DEV *QUOTACTL::getdev (const char *device) { QUOTA_DEV *ret = NULL; for (int i=0; isetrealdevice(); if (dev->realdev.cmp(device)==0){ ret = dev; break; } } return ret; } /* Remove the user from the quota.conf file */ PUBLIC int QUOTACTL::deluser ( QUOTA_TYPE type, const char *nam, PRIVILEGE *priv) { int ret = -1; if (nam[0] != '\0'){ bool one = false; for (int i=0; ideluserspec (nam) != -1) one = true; }else if (type == QUOTA_GROUP){ if (d->delgroupspec (nam) != -1) one = true; }else if (type == QUOTA_USERDEF_G){ if (d->deluserdef_gspec (nam) != -1) one = true; } } if (one){ ret = write(priv); }else{ // No need to save, all is fine ret = 0; } } return ret; } static void quota_setif (const char *val, int &field) { if (val != NULL){ if (strcmp(val,"default")==0){ field = -1; }else if(!isdigit(val[0])){ fprintf (stderr ,MSG_U(E_IVLDQUOTAVAL ,"Invalid quota value %s: expect default or a numeric value\n") ,val); }else{ field = atoi(val); } } } /* Command line support to set disk quota */ int quota_setquota ( bool is_group, // Update group or user quota const char *name, // Group or user name int argc, char *argv[]) { int ret = -1; bool some_errors = false; const char *device = NULL; const char *softk = NULL; const char *hardk = NULL; const char *softf = NULL; const char *hardf = NULL; int i; for (i=0; igroups : &dev->users; QUOTA_SPEC *spec = specs->getitem(name); if (spec == NULL){ spec = new QUOTA_SPEC; spec->name.setfrom (name); specs->add (spec); } quota_setif (softk,spec->soft_maxk); quota_setif (hardk,spec->hard_maxk); quota_setif (softf,spec->soft_maxf); quota_setif (hardf,spec->hard_maxf); ret = ctl.write(NULL); if (ret == 0) quota_applyall(); } } } return ret; return ret; } int quota_playcmd (const char *file) { int ret = -1; bool use_stdin = strcmp(file,"-")==0; FILE *fin = use_stdin ? stdin : xconf_fopen (file,"r"); if (fin != NULL){ QUOTA_SPEC spec; char device[PATH_MAX]; int user_quota,user; ret = 0; while (fscanf(fin,"%s %d %d %d %d %d %d %ld %ld\n" ,device,&user_quota,&user ,&spec.hard_maxk,&spec.soft_maxk ,&spec.hard_maxf,&spec.soft_maxf ,&spec.grace_k.seconds,&spec.grace_f.seconds)==9){ ret = quota_apply_real (spec,user,user_quota,device); if (ret == -1){ fprintf (stderr,MSG_U(E_CANTAPPLY,"Can't apply for user/group %d on device %s\n") ,user,device); break; } } if (!feof(fin)) ret = -1; if (!use_stdin) fclose (fin); } return ret; }