File: //proc/self/root/scripts.20110531.215904.25158/initquotas
#!/usr/bin/perl
# cpanel - initquotas Copyright(c) 2010 cPanel, Inc.
# All Rights Reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
BEGIN { unshift @INC, '/usr/local/cpanel'; }
use strict;
use IO::Handle ();
use IPC::Open3 ();
use Cpanel::RcsRecord ();
use Cpanel::SafeFile ();
use Cpanel::FileUtils::TouchFile ();
use Cpanel::FindBin ();
use Cpanel::SafeRun::Simple ();
use Cpanel::SafeRun::Errors ();
use Cpanel::Logger ();
use Cpanel::ProcessCheck ();
use Cpanel::Sys::OS ();
use Cpanel::Config::Backup ();
use Cpanel::Config::LoadWwwAcctConf ();
$| = 1;
check_previous_initquotas();
my @QUOTA_FILES = ( 'quota.user', 'aquota.user' );
my $supported_file_system_regex = 'ext(?:2|3|4)|reiserfs';
my $logger = Cpanel::Logger->new();
my $skipquotacheck = ( $ARGV[0] =~ m/skipquotacheck/i || -d '/proc/vz/vzaquota' ) ? 1 : 0;
my $system = $^O;
my $backup_cfg_ref = Cpanel::Config::Backup::load();
my $mountkeyword = 'remount';
my %cmd = (
'quota' => undef,
'quotaon' => undef,
'quotaoff' => undef,
'quotacheck' => undef,
'convertquota' => undef,
);
my @missing_cmds;
foreach my $cmd_name ( keys %cmd ) {
$cmd{$cmd_name} = Cpanel::FindBin::findbin($cmd_name);
if ( !( $cmd{$cmd_name} && -x $cmd{$cmd_name} ) ) {
# Exclude "convertquota" when running on FreeBSD
if ( !( $system =~ m/freebsd/i && $cmd_name =~ m/convertquota/ ) ) {
push @missing_cmds, $cmd_name;
}
}
}
if ( scalar @missing_cmds ) {
print "Incomplete quota kit: unable to initialize quotas.\n";
print 'Missing commands: ', join( ', ', @missing_cmds ), "\n";
exit 1;
}
chmod oct(4755), $cmd{'quota'};
if ( !-e '/etc/quota.conf' ) {
Cpanel::FileUtils::TouchFile::touchfile('/etc/quota.conf');
chmod( 0600, '/etc/quota.conf' );
}
my @mount_output = split( /\n/, Cpanel::SafeRun::Simple::saferun('mount') );
if ( $system =~ m/freebsd/i ) {
$mountkeyword = 'update';
if ( !grep( /with\s+quotas|usrquota/, @mount_output ) ) {
print <<'EOM';
FreeBSD Quota Support
!! No Filesystems are mounted with quota support !!
Under freebsd you must edit /etc/fstab and turn on quotas.
Note: make sure your kernel supports quotas before doing this.
After you have remounted your file systems with quota support,
re-run this script.
EOM
exit 1;
}
}
else {
print "Linux Quota Support\n";
print "Quotas are now on\n";
}
my $distro = Cpanel::Sys::OS::getos();
my $distro_version = Cpanel::Sys::OS::getreleaseversion();
#
# These distros cannot reliable have quotas turned on for the / filesystem.
# This is really < 2.6 or patched rh kernels
#
my $distro_cannot_handle_slash_quota = ( ( $distro eq 'fedora' && $distro_version < 6 ) || $distro eq 'debian' || ( $distro eq 'redhat' && $distro_version =~ /^7/ ) || ( $distro eq 'mandrake' && $distro_version =~ /^(?:8|9)/ ) ) ? 1 : 0;
#
# Build up our list of mounts
# @MOUNTS is a list of mounts in /etc/mtab
# @ALL_MOUNTED_FSES includes /proc/mounts as well
#
my @MOUNTS = _fetch_mounts( \@mount_output );
my @ALL_MOUNTED_FSES = @MOUNTS;
_add_proc_mounts( \@ALL_MOUNTED_FSES );
#
# Make sure virtfs is not mounted. Die if it is and we cannot unmount it
#
check_for_virtfs_mounts( \@ALL_MOUNTED_FSES );
my $backup_dir_mount_point = get_backup_dir_mount_point($backup_cfg_ref);
my $fses_to_convert_arrayref = setup_quotas( $backup_cfg_ref, $backup_dir_mount_point );
Cpanel::SafeRun::Errors::saferunnoerror( $cmd{'quotaoff'} );
exit if $skipquotacheck;
run_quota_check();
convert_quotas($fses_to_convert_arrayref);
Cpanel::SafeRun::Errors::saferunnoerror( $cmd{'quotaon'} );
reset_quota_caches();
exit 0;
sub reset_quota_caches {
unlink('/var/cpanel/repquota.cache');
unlink('/var/cpanel/repquota.datastore');
}
sub convert_quotas {
my $fses_to_convert_arrayref = shift;
foreach my $mntpoint (@$fses_to_convert_arrayref) {
Cpanel::SafeRun::Errors::saferunnoerror( $cmd{'convertquota'}, $mntpoint );
_set_quota_file_perms($mntpoint);
}
}
sub run_quota_check {
my $val = Cpanel::SafeRun::Errors::saferunallerrors( $cmd{'quotacheck'}, '--help' );
print 'Updating Quota Files......';
if ( $val =~ /quota-format/ || $val =~ /Utility for checking/ ) {
quotarun( $cmd{'quotacheck'}, '-guvamf', '-F', 'vfsv0' );
quotarun( $cmd{'quotacheck'}, '-guvamf', '-F', 'vfsold' );
}
elsif ( $val =~ m/invalid option/mi || $val =~ m/illegal option/mi || $val =~ m/usage: quotacheck/mi ) {
quotarun( $cmd{'quotacheck'}, '-guva' );
}
else {
quotarun( $cmd{'quotacheck'}, '-Ffguva' );
}
print '....Done' . "\n";
}
sub quotarun {
my (@CMD) = @_;
print "\n\t";
my $qout_fh = IO::Handle->new();
my $pid = IPC::Open3::open3( '>/dev/null', $qout_fh, $qout_fh, @CMD );
while ( read( $qout_fh, $_, 1 ) ) {
s/\n/\n\t/g;
print;
}
print "\n";
close($qout_fh);
waitpid( $pid, 0 );
}
sub _fetch_mounts {
my ($mount_output_arr_ref) = @_;
if ( !defined $mount_output_arr_ref || ref $mount_output_arr_ref ne 'ARRAY' ) {
@{$mount_output_arr_ref} = split( /\n/, Cpanel::SafeRun::Simple::saferun('mount') );
}
my @mounts;
foreach my $mount_line ( @{$mount_output_arr_ref} ) {
next if $mount_line =~ m/^(?:proc|procfs|sysfs|devpts|devfs|tmpfs|autofs|usbfs|none)\s/;
push @mounts, ( split( /\s+/, $mount_line ) )[2];
}
return @mounts;
}
sub _set_quota_file_perms {
my $mntpoint = shift;
foreach my $quota_file (@QUOTA_FILES) {
if ( -e $mntpoint . '/' . $quota_file ) { chmod 0744, $mntpoint . '/' . $quota_file }
}
}
sub wall {
my $wall_txt = shift;
my $wall_cmd = Cpanel::FindBin::findbin('wall');
if ( !-x $wall_cmd ) { return; }
if ( open( my $wall_fh, '|-' ) || exec($wall_cmd ) ) {
print {$wall_fh} $wall_txt;
close($wall_fh);
}
}
#
# Checks to make sure virtfs is not mounted.
# If it is try to kill jailshell to force it to unmount
# Finally bail out if that all fails
#
sub check_for_virtfs_mounts {
my $all_mounted_fses_ref = shift;
if ( grep( /\/virtfs/, @$all_mounted_fses_ref ) ) {
print <<'EOM';
Warning virtual file systems are mounted. Quotas may be counted as double
for users who are currently logged in. Please have jailshell users logout
before running quotacheck in the future!
All jailed users will be logged out in 60 seconds!
EOM
wall("Warning! The system is about to perform quota maintenance.\nAll users will be logged out in 60 seconds.\nPLEASE Do not log back in for 30 minutes, or you may inadvertantly disable your account.\n");
sleep(60);
system( 'killall', '-TERM', 'jailshell' );
sleep(5);
# Clean up virtfs mounts
system '/scripts/clear_orphaned_virtfs_mounts', '--errorsonly', '--clearall';
# Need to refresh the 'mount' output as some mounts were supposed to be removed
@mount_output = split( /\n/, Cpanel::SafeRun::Simple::saferun('mount') );
@$all_mounted_fses_ref = _fetch_mounts( \@mount_output );
_add_proc_mounts($all_mounted_fses_ref);
if ( grep( /\/virtfs/, @$all_mounted_fses_ref ) ) {
die "Failed to unmount virtual file system.";
}
}
}
#
# Takes an arrayref of mount "lines" and pushes on the contents of /proc/mounts
#
sub _add_proc_mounts {
my ($mount_array_ref) = @_;
if ( -r '/proc/mounts' ) {
if ( open( my $proc_mnt_fh, '<', '/proc/mounts' ) ) {
while ( my $line = readline $proc_mnt_fh ) {
chomp $line;
my ( $device, $mount, $type, undef ) = split( /\s+/, $line, 4 );
next if !$type || $device eq 'none' || $type =~ m/^(?:proc|procfs|sysfs|devpts|devfs|tmpfs|autofs|usbfs|none)$/ || grep { $_ eq $mount } @{$mount_array_ref};
push @{$mount_array_ref}, $mount;
}
close($proc_mnt_fh);
}
}
return $mount_array_ref;
}
#
# Make sure we are not running twice or something else in not running quotacheck
#
sub check_previous_initquotas {
my $parent = getppid();
my %PPIDS = Cpanel::ProcessCheck::previouspids( 'process' => 'initquotas' );
delete @PPIDS{ $parent, $$ };
if ( scalar keys %PPIDS ) {
print "initquotas is already running. Please wait for the previous process to complete and run initquotas again if you really want to rebuild the quota database.\n";
exit 1;
}
%PPIDS = Cpanel::ProcessCheck::previouspids( 'process' => 'quotacheck' );
if ( scalar keys %PPIDS ) {
print "quotacheck currently running. Please wait for the previous process to complete and run initquotas again if you really want to rebuild the quota database.\n";
exit 1;
}
}
#
# Cycle though the fstab and add usrquota to all supported filesystems
# and remove from filesystems that should not have them
#
sub setup_quotas {
my $backup_cfg_ref = shift;
my $backup_dir_mount_point = shift;
my @CFILE;
my @MOUNT_CMDS;
my @NEED_CONVERT;
my $wwwacct_ref = Cpanel::Config::LoadWwwAcctConf::loadwwwacctconf();
my $home = $wwwacct_ref->{'HOMEDIR'} || '/home';
Cpanel::RcsRecord::rcsrecord('/etc/fstab');
my $fstab_fh = IO::Handle->new();
my $home_dir_mount_point = _get_fs_mountpoint($home);
if ( !-e '/etc/fstab' ) { $logger->die('Could not edit /etc/fstab'); }
my $fstablock = Cpanel::SafeFile::safeopen( $fstab_fh, '+<', '/etc/fstab' );
if ( !$fstablock ) {
$logger->die('Could not edit /etc/fstab');
}
LINE:
while ( my $fstab_line = readline($fstab_fh) ) {
if ( $fstab_line =~ /^(\S+)\s*(\S+)/ ) {
my $dsk = $1;
my $mntpoint = $2;
my $has_usr_quota = ( $fstab_line =~ /usrquota/ ? 1 : 0 );
$dsk =~ s/^LABEL=//g;
if ( $fstab_line =~ /\s*$supported_file_system_regex/ && $fstab_line =~ /defaults/ ) {
foreach my $quota_file (@QUOTA_FILES) {
if ( -l $mntpoint . '/' . $quota_file ) {
push( @CFILE, $fstab_line );
next LINE; #openvz
}
}
my $mountpnt_can_do_quota = ( $mntpoint =~ /^(?:\/boot|\/tmp)/ ? 0 : 1 );
#NOTE:: QUOTAS CAN BE ON A BACKUP DISK SINCE ALL FILES ARE ALWAYS OWNED BY ROOT -- HOWEVER IT IS SLOW
if ( $distro_cannot_handle_slash_quota && $mntpoint eq '/' ) {
if ( $home_dir_mount_point eq '/' ) {
print "Warning : Your system does not have a separate partition for accounts (home) and you are using an older kernel which has problems with quotas on the root filesystem.\n";
}
else {
$mountpnt_can_do_quota = 0;
}
}
if ($mountpnt_can_do_quota) {
if ( $backup_dir_mount_point eq $mntpoint ) {
if ( $mntpoint eq '/' ) {
print "Warning : Your system does not have a separate filesystem for backups. This may cause performance degradation during the backup process.\n";
}
else {
print "Quotas will not be enabled on $mntpoint to prevent performance degradation.\n";
$mountpnt_can_do_quota = 0;
}
}
}
if ( $mountpnt_can_do_quota && !$has_usr_quota ) {
print "$dsk (enabling quotas)\n";
$fstab_line =~ s/defaults/defaults,usrquota/g;
push @MOUNT_CMDS, [ 'mount', '-o', $mountkeyword . ',usrquota', $dsk ];
}
if ( !$mountpnt_can_do_quota && $has_usr_quota ) {
print "$dsk (remove)\n";
$fstab_line =~ s/defaults,usrquota/defaults/g;
push @MOUNT_CMDS, [ 'mount', '-o', $mountkeyword, $dsk ];
}
if ($mountpnt_can_do_quota) {
_set_quota_file_perms($mntpoint);
foreach my $quota_file (@QUOTA_FILES) {
my $quota_file_with_path = $mntpoint eq '/' ? $mntpoint . $quota_file : $mntpoint . '/' . $quota_file;
if ( !-e $quota_file_with_path ) {
Cpanel::FileUtils::TouchFile::touchfile($quota_file_with_path);
}
}
_set_quota_file_perms($mntpoint);
push @NEED_CONVERT, $mntpoint;
}
}
}
push( @CFILE, $fstab_line );
}
seek( $fstab_fh, 0, 0 );
print {$fstab_fh} join( '', @CFILE );
truncate( $fstab_fh, tell($fstab_fh) );
Cpanel::SafeFile::safeclose( $fstab_fh, $fstablock );
Cpanel::RcsRecord::rcsrecord('/etc/fstab');
# Now actually remount the file systems with or without quotas based on the logic above.
# So /etc/fstab matches what is actually going on
foreach (@MOUNT_CMDS) {
system(@$_);
}
return \@NEED_CONVERT;
}
# Scans the backup configuraiton for fses that are set to be backup fses that have
# quotas enabled on them and disables them. Returns a hashref list of backup fses that exist
#
sub get_backup_dir_mount_point {
my $backup_cfg_ref = shift;
return _get_fs_mountpoint( $backup_cfg_ref->{'BACKUPDIR'} || '/backup' );
}
sub _get_fs_mountpoint {
my $path = shift;
my %fs_for_mountpoint;
foreach my $fs (@ALL_MOUNTED_FSES) {
my $fspath = ( split( /\s+/, $fs ) )[0];
$fs_for_mountpoint{$fspath} = undef;
}
my @PATH = split( /\//, $path );
while (@PATH) {
my $test_path = join '/', @PATH;
return $test_path if exists $fs_for_mountpoint{$test_path};
pop @PATH;
}
return '/';
}