431 lines
15 KiB
C++
431 lines
15 KiB
C++
#include <common/storage/quota/Quota.h>
|
|
#include <common/toolkit/UnitTk.h>
|
|
#include <program/Program.h>
|
|
#include "QuotaTk.h"
|
|
|
|
#include <boost/lexical_cast.hpp>
|
|
|
|
#include <dlfcn.h>
|
|
// xfs/xfs.h includes linux/fs.h, which (sometimes) defines MS_RDONLY.
|
|
// sys/mount.h is the canonical source of MS_RDONLY, but declares it as an enum - which is then
|
|
// broken by linux/fs.h when included after it
|
|
#include <sys/mount.h>
|
|
#include <xfs/xfs.h>
|
|
#include <xfs/xqm.h>
|
|
|
|
#define QUOTATK_ZFS_USER_QUOTA "userused@"
|
|
#define QUOTATK_ZFS_GROUP_QUOTA "groupused@"
|
|
#define QUOTATK_ZFS_USER_INODE_QUOTA "userobjused@"
|
|
#define QUOTATK_ZFS_GROUP_INODE_QUOTA "groupobjused@"
|
|
|
|
/**
|
|
* Get quota data for a single ID and append it to the outQuotaDataList.
|
|
*
|
|
* @param id the user or group ID to check
|
|
* @param type the type of the ID user or group, enum QuotaDataType
|
|
* @param blockDevices the QuotaBlockDevice to check
|
|
* @param outQuotaDataList the return list with the quota data
|
|
* @param session a session for all required lib handles if zfs is used, it can be an uninitialized
|
|
* session, the initialization can be done by this function
|
|
*
|
|
* @return false on error (in which case outData is empty)
|
|
*/
|
|
bool QuotaTk::appendQuotaForID(unsigned id, QuotaDataType type, QuotaBlockDeviceMap* blockDevices,
|
|
QuotaDataList* outQuotaDataList, ZfsSession* session)
|
|
{
|
|
QuotaData data(id, type);
|
|
bool retVal = checkQuota(blockDevices, &data, session);
|
|
|
|
if(retVal && (data.getSize() != 0 || data.getInodes() != 0) )
|
|
outQuotaDataList->push_back(data);
|
|
|
|
return retVal;
|
|
}
|
|
|
|
/**
|
|
* get quota for the a range of IDs
|
|
*
|
|
* @param blockDevices the QuotaBlockDevice to check
|
|
* @param rangeStart the first ID of the ID range
|
|
* @param rangeEnd the last ID of the ID range
|
|
* @param type the quota data ID type user/group, QuotaDataType_...
|
|
* @param outQuotaDataList the return list with the quota data
|
|
* @param session a session for all required lib handles if zfs is used, it can be an uninitialized
|
|
* session, the initialization can be done by this function
|
|
*
|
|
* @return false on error (in which case outData is empty)
|
|
*/
|
|
bool QuotaTk::requestQuotaForRange(QuotaBlockDeviceMap* blockDevices, unsigned rangeStart,
|
|
unsigned rangeEnd, QuotaDataType type, QuotaDataList* outQuotaDataList, ZfsSession* session)
|
|
{
|
|
bool retVal = true;
|
|
|
|
for(unsigned id = rangeStart; id <= rangeEnd; id++)
|
|
{
|
|
bool errorVal = appendQuotaForID(id, type, blockDevices, outQuotaDataList, session);
|
|
|
|
if(!errorVal)
|
|
retVal = false;
|
|
}
|
|
|
|
return retVal;
|
|
}
|
|
|
|
/**
|
|
* get quota for the a range of IDs
|
|
*
|
|
* @param blockDevices the QuotaBlockDevice to check
|
|
* @param idList the list with the IDs to check
|
|
* @param type the quota data ID type user/group, QuotaDataType_...
|
|
* @param outQuotaDataList the return list with the quota data
|
|
* @param session a session for all required lib handles if zfs is used, it can be an uninitialized
|
|
* session, the initialization can be done by this function
|
|
*
|
|
* @return false on error (in which case outData is empty)
|
|
*/
|
|
bool QuotaTk::requestQuotaForList(QuotaBlockDeviceMap* blockDevices, UIntList* idList,
|
|
QuotaDataType type, QuotaDataList* outQuotaDataList, ZfsSession* session)
|
|
{
|
|
bool retVal = true;
|
|
|
|
for(UIntListIter iter = idList->begin(); iter != idList->end(); iter++)
|
|
{
|
|
bool errorVal = appendQuotaForID(*iter, type, blockDevices, outQuotaDataList, session);
|
|
|
|
if(!errorVal)
|
|
retVal = false;
|
|
}
|
|
|
|
return retVal;
|
|
}
|
|
|
|
/**
|
|
* get QuotaData for the given QuotaData which is initialized with type and ID
|
|
*
|
|
* @param blockDevices the QuotaBlockDevice to check
|
|
* @param outData needs to be initialized with type and ID
|
|
* @param session a session for all required lib handles if zfs is used, it can be an uninitialized
|
|
* session, the initialization can be done by this function
|
|
*
|
|
* @return false on error (in which case outData is not initialized)
|
|
*/
|
|
bool QuotaTk::checkQuota(QuotaBlockDeviceMap* blockDevices, QuotaData* outData, ZfsSession* session)
|
|
{
|
|
bool retVal = true;
|
|
int errorCode = 0;
|
|
|
|
dqblk quotaData; // required for extX
|
|
fs_disk_quota xfsQuotaData; // required for XFS
|
|
QuotaData tmpQuotaData(outData->getID(), outData->getType() ); // required for ZFS
|
|
|
|
QuotaDataType quotaType = outData->getType();
|
|
|
|
for(QuotaBlockDeviceMapIter iter = blockDevices->begin(); iter != blockDevices->end(); iter++)
|
|
{
|
|
QuotaBlockDeviceFsType fstype = iter->second.getFsType();
|
|
if(fstype == QuotaBlockDeviceFsType_XFS)
|
|
{
|
|
if(quotaType == QuotaDataType_USER)
|
|
errorCode = quotactl(QCMD(Q_XGETQUOTA, USRQUOTA),
|
|
iter->second.getBlockDevicePath().c_str(),
|
|
outData->getID(), (caddr_t)&xfsQuotaData);
|
|
else
|
|
if (quotaType == QuotaDataType_GROUP)
|
|
errorCode = quotactl(QCMD(Q_XGETQUOTA, GRPQUOTA),
|
|
iter->second.getBlockDevicePath().c_str(),
|
|
outData->getID(), (caddr_t)&xfsQuotaData);
|
|
else
|
|
{
|
|
LOG(QUOTA, ERR, "Quota request - useless quota type.",
|
|
("Type", QuotaData::QuotaDataTypeToString(outData->getType())),
|
|
("ID", outData->getID()));
|
|
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
if ( (fstype == QuotaBlockDeviceFsType_ZFS) || (fstype == QuotaBlockDeviceFsType_ZFSOLD) )
|
|
{
|
|
if(!session->isSessionValid() )
|
|
{
|
|
if(!session->initZfsSession(Program::getApp()->getDlOpenHandleLibZfs() ) )
|
|
return false;
|
|
}
|
|
|
|
if(QuotaTk::requestQuotaFromZFS(&iter->second, iter->first, &tmpQuotaData, session))
|
|
errorCode = FhgfsOpsErr_SUCCESS;
|
|
else
|
|
{
|
|
LOG(QUOTA, ERR, "Quota request - useless quota type.",
|
|
("Type", QuotaData::QuotaDataTypeToString(outData->getType())),
|
|
("ID", outData->getID()));
|
|
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(quotaType == QuotaDataType_USER)
|
|
errorCode = quotactl(QCMD(Q_GETQUOTA, USRQUOTA),
|
|
iter->second.getBlockDevicePath().c_str(),
|
|
outData->getID(), (caddr_t)"aData);
|
|
else
|
|
if (quotaType == QuotaDataType_GROUP)
|
|
errorCode = quotactl(QCMD(Q_GETQUOTA, GRPQUOTA),
|
|
iter->second.getBlockDevicePath().c_str(),
|
|
outData->getID(), (caddr_t)"aData);
|
|
else
|
|
{
|
|
LOG(QUOTA, ERR, "Quota request - useless quota type.",
|
|
("Type", QuotaData::QuotaDataTypeToString(outData->getType())),
|
|
("ID", outData->getID()));
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (errorCode != 0)
|
|
{
|
|
errorCode = errno;
|
|
|
|
// ignore error if no quota for user found (ESRCH) , especially for XFS (ENOENT)
|
|
if( (errorCode != ESRCH) &&
|
|
!((iter->second.getFsType() == QuotaBlockDeviceFsType_XFS) && (errorCode == ENOENT) ) )
|
|
{
|
|
LOG(QUOTA, ERR, "Quota request - quotactl failed.",
|
|
("Type", QuotaData::QuotaDataTypeToString(outData->getType())),
|
|
("ID", outData->getID()),
|
|
("sysErr", System::getErrString(errorCode)),
|
|
("fstype", boost::lexical_cast<std::string>(iter->second.getFsType())));
|
|
|
|
return false;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
if(fstype == QuotaBlockDeviceFsType_XFS)
|
|
{
|
|
uint64_t blocks = UnitTk::quotaBlockCountToByte(xfsQuotaData.d_bcount,
|
|
iter->second.getFsType() );
|
|
outData->forceMergeQuotaDataCounter(blocks, xfsQuotaData.d_icount);
|
|
}
|
|
else
|
|
if ( (fstype == QuotaBlockDeviceFsType_ZFS) || (fstype == QuotaBlockDeviceFsType_ZFSOLD) )
|
|
{
|
|
if(tmpQuotaData.isValid() )
|
|
{
|
|
uint64_t blocks = UnitTk::quotaBlockCountToByte(tmpQuotaData.getSize(),
|
|
iter->second.getFsType() );
|
|
outData->forceMergeQuotaDataCounter(blocks, tmpQuotaData.getInodes() );
|
|
tmpQuotaData.setIsValid(false);
|
|
}
|
|
else
|
|
{
|
|
// set return value to false, but do not abort the quota request from the other targets,
|
|
// maybe the other targets returns valid values
|
|
LOG(QUOTA, ERR, "Quota request - values not valid.",
|
|
("Type", QuotaData::QuotaDataTypeToString(outData->getType())),
|
|
("ID", outData->getID()));
|
|
|
|
retVal = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if((quotaData.dqb_valid & QIF_USAGE) != 0)
|
|
{
|
|
uint64_t blocks = UnitTk::quotaBlockCountToByte(quotaData.dqb_curspace,
|
|
iter->second.getFsType());
|
|
|
|
outData->forceMergeQuotaDataCounter(blocks, quotaData.dqb_curinodes);
|
|
}
|
|
else
|
|
{
|
|
// set return value to false, but do not abort the quota request from the other targets,
|
|
// maybe the other targets returns valid values
|
|
LOG(QUOTA, ERR, "Quota request - values not valid.",
|
|
("Type", QuotaData::QuotaDataTypeToString(outData->getType())),
|
|
("ID", outData->getID()));
|
|
|
|
retVal = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return retVal;
|
|
}
|
|
|
|
/**
|
|
* initialize the lib zfs
|
|
*
|
|
* @param dlHandle the handle from dlopen to the libzfs
|
|
* @return the libzfs handle (libzfs_handle_t*), which is required for all libzfs calls
|
|
*/
|
|
void* QuotaTk::initLibZfs(void* dlHandle)
|
|
{
|
|
App* app = Program::getApp();
|
|
|
|
char* dlErrorString;
|
|
void* (*libzfs_init)();
|
|
|
|
libzfs_init = (void* (*)())dlsym(dlHandle, "libzfs_init");
|
|
if ( (dlErrorString = dlerror() ) != NULL)
|
|
{
|
|
LOG(QUOTA, ERR, "Error during dynamic load of function libzfs_init.", dlErrorString);
|
|
app->setLibZfsErrorReported(true);
|
|
return NULL;
|
|
}
|
|
|
|
void* libZfsHandle = (*libzfs_init)();
|
|
if(!libZfsHandle)
|
|
{
|
|
LOG(QUOTA, ERR, "Error during create of libzfs_handle_t.");
|
|
app->setLibZfsErrorReported(true);
|
|
return NULL;
|
|
}
|
|
|
|
return libZfsHandle;
|
|
}
|
|
|
|
/**
|
|
* uninitialize the lib zfs
|
|
*
|
|
* @param dlHandle the handle from dlopen to the libzfs
|
|
* @param libZfsHandle the libzfs handle (libzfs_handle_t*) to the libzfs handle (libzfs_handle_t*)
|
|
* @return false in case of error
|
|
*/
|
|
bool QuotaTk::uninitLibZfs(void* dlHandle, void* libZfsHandle)
|
|
{
|
|
App* app = Program::getApp();
|
|
|
|
if( (!dlHandle) || (!libZfsHandle) )
|
|
return true;
|
|
|
|
void (*libzfs_fini)(void*);
|
|
char* dlErrorString;
|
|
|
|
libzfs_fini = (void (*)(void*))dlsym(dlHandle, "libzfs_fini");
|
|
if ( (dlErrorString = dlerror() ) != NULL)
|
|
{
|
|
LOG(QUOTA, ERR, "Error during dynamic load of function libzfs_fini.", dlErrorString);
|
|
app->setLibZfsErrorReported(true);
|
|
return false;
|
|
}
|
|
|
|
(*libzfs_fini)(libZfsHandle);
|
|
|
|
libZfsHandle = NULL;
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* checks if all required functions of the installed libzfs are available
|
|
*
|
|
* @param blockDevice a QuotaBlockDevice for a test the get quota workflow
|
|
* @param targetNumID the targetNumID of the storage target
|
|
* @return true if all function pointers are available, false if a error occurred
|
|
*/
|
|
bool QuotaTk::checkRequiredLibZfsFunctions(QuotaBlockDevice* blockDevice, uint16_t targetNumID)
|
|
{
|
|
App* app = Program::getApp();
|
|
|
|
ZfsSession session;
|
|
|
|
// check if all function names in the lib available
|
|
if(!session.initZfsSession(app->getDlOpenHandleLibZfs() ) )
|
|
{
|
|
if(!app->getConfig()->getQuotaDisableZfsSupport() )
|
|
LOG(QUOTA, ERR, "Cannot initialize libzfs. "
|
|
"The quota system for all zfs blockdevices/pools on this machine won't work.");
|
|
|
|
return false;
|
|
}
|
|
|
|
// check if the signature of all function pointers are compatible
|
|
QuotaData quotaData(0, QuotaDataType_USER); // test request for user root
|
|
if (!requestQuotaFromZFS(blockDevice, targetNumID, "aData, &session))
|
|
{ // quota update failed; try to get quota without inode quota (only supported on zfs >= 0.7.4)
|
|
// i.e. set the fsType to QuotaBlockDeviceFsType_ZFSOLD and try again
|
|
// if it still doesn't work, libzfs is not compatible at all, if it works now we fall back to
|
|
// quota without inode quota
|
|
blockDevice->setFsType(QuotaBlockDeviceFsType_ZFSOLD);
|
|
if (!requestQuotaFromZFS(blockDevice, targetNumID, "aData, &session))
|
|
return false;
|
|
}
|
|
|
|
// check function pointers which are required in case of errors happens
|
|
std::string errorDec( (*session.libzfs_error_description)(session.getlibZfsHandle() ) );
|
|
std::string errorAct( (*session.libzfs_error_action)(session.getlibZfsHandle() ) );
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* get QuotaData from zfs for the given QuotaData which is initialized with type and ID
|
|
*
|
|
* @param blockDevice the QuotaBlockDevice to check
|
|
* @param targetNumID the targetNumID of the storage target
|
|
* @param outData needs to be initialized with type and ID
|
|
* @param session a session for all required lib handles if zfs is used, it can be an uninitialized
|
|
* session, the initialization can be done by this function
|
|
* @return false on error (in which case outData is not initialized)
|
|
*/
|
|
bool QuotaTk::requestQuotaFromZFS(QuotaBlockDevice* blockDevice, uint16_t targetNumID,
|
|
QuotaData* outData, ZfsSession* session)
|
|
{
|
|
if(!session->isSessionValid() )
|
|
{
|
|
outData->setIsValid(false);
|
|
return false;
|
|
}
|
|
|
|
void* zfsHandle = session->getZfsDeviceHandle(targetNumID, blockDevice->getBlockDevicePath() );
|
|
if(!zfsHandle)
|
|
return false;
|
|
|
|
uint64_t usedSizeValue = 0;
|
|
uint64_t usedInodesValue = 0;
|
|
|
|
std::string sizeProp;
|
|
std::string inodeProp;
|
|
|
|
if(outData->getType() == QuotaDataType_USER)
|
|
{
|
|
sizeProp = QUOTATK_ZFS_USER_QUOTA + StringTk::uintToStr(outData->getID());
|
|
inodeProp = QUOTATK_ZFS_USER_INODE_QUOTA + StringTk::uintToStr(outData->getID());
|
|
}
|
|
else
|
|
{
|
|
sizeProp = QUOTATK_ZFS_GROUP_QUOTA + StringTk::uintToStr(outData->getID());
|
|
inodeProp = QUOTATK_ZFS_GROUP_INODE_QUOTA + StringTk::uintToStr(outData->getID());
|
|
}
|
|
|
|
if ((*session->zfs_prop_get_userquota_int)(zfsHandle, sizeProp.c_str(), &usedSizeValue))
|
|
{
|
|
LOG(QUOTA, ERR, "Error during request quota data.",
|
|
("ErrorAction", (*session->libzfs_error_action)(session->getlibZfsHandle())),
|
|
("ErrorDescription", (*session->libzfs_error_description)(session->getlibZfsHandle()))
|
|
);
|
|
return false;
|
|
}
|
|
|
|
if (blockDevice->getFsType() != QuotaBlockDeviceFsType_ZFSOLD) // no inode support on zfs<=0.7.4
|
|
{
|
|
if ((*session->zfs_prop_get_userquota_int)(zfsHandle, inodeProp.c_str(), &usedInodesValue))
|
|
{
|
|
// could not read inode quota (which is only supported since zfs 0.7.4)
|
|
// log the error and set 0 as inode quota
|
|
LOG(QUOTA, ERR, "Inode quota could not be requested. Please note that inode quota on"
|
|
" ZFS is not supported for zfs versions prior to 0.7.4.",
|
|
("ZFS Error", (*session->libzfs_error_description)(session->getlibZfsHandle())));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
outData->setQuotaData(usedSizeValue, usedInodesValue);
|
|
|
|
return true;
|
|
}
|