Articles December 6, 2004 at 9:39 am

Disk Quota Reporting with PHP

I’ve had a few issues with disk quotas recently on my OS X Server file servers, and I needed to come up with a way for my student assistants to be able to view disk quota values for my staff so that they could easily tell who was over or approaching their quota.

Read on for a way to monitor disk quotas in a web browser…I really don’t want to give them any shell or Server Admin access for this server, so I decided to whip something up in PHP to do this. Be warned that I don’t consider myself the best PHP programmer on the face of the planet, so it is likely there are more efficient ways to do this, and I know there are definitely more elegant solutions….

Efficiency suggestions would be appreciated, I promise not to get all macho-geek and deride them. 🙂

To get started, issue the following command at the terminal:

<code>
/bin/date &gt; /tmp/quotas &amp;&amp; /usr/sbin/repquota -au &gt;&gt; /tmp/quotas
</code>

What this does is execute the command “date”, which will give you output something like:

<code>
Mon Dec  6 08:57:25 EST 2004
</code>

The ‘>’ redirects the output to replace the file “/tmp/quotas”.

The ‘&&’ means execute another command afterwards, in this case “repquota -au”, which reports all user quotas for all local filesystems.

The ‘>>’ redirects the output to append to the file “/tmp/quotas”.
If you have a look at this file, you’ll see something like:

<code>
me@server: ~ &#36; head /tmp/quotas 
Mon Dec  6 09:00:00 EST 2004
                        1K Block limits               File limits
User                used        soft        hard  grace    used  soft  hard  grace
cherylstaff--     2755724           0           0          15678     0     0       
brucestaff  --     5738512           0           0           7830     0     0       
</code>

The output of this is kind of ugly, so you’ll see there is a fair bit of munging going on in the php code to clean it up.

The output will look something like this example screenshot.

And here is the PHP code I use to display this. By default it will sort in descending order by the percent of quota used, so that the users closest to their quota will appear at the top. You can sort by any column however. The “$warnlimit” variable means that users who are above this value will appear in red and in a larger font.

You can also download the script as a file.

<code>
&lt;html&gt;
&lt;head&gt;&lt;title&gt;File Quota Report&lt;/title&gt;
&lt;style&gt;
    body { color: black; background: white; margin-left: 10% }
    .list0 {font:12px Arial,Helvetica; color:#000000; background-color:#eeeeee}
    .list1 {font:12px Arial,Helvetica; color:#000000; background-color:#cccccc}
    .headerrow {font:18px Arial,Helvetica; color:#000000; background-color:#888888}
    .warn {font:14px Arial, Helvetica, sans-serif; color:#ff0000; }
&lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;

&lt;?php

&#36;quotafile = file ("/tmp/quotas");
&#36;colour = 0;
&#36;self = &#36;_SERVER['PHP_SELF'];
&#36;warnlimit = 70;                

echo "&lt;b&gt;Report Date:&lt;/b&gt;&lt;i&gt; &#36;quotafile[0]&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;";

&#36;quotas = array();
for (&#36;i=3 ; &#36;i &lt; count(&#36;quotafile) ; &#36;i++) 
{
    &#36;thisline = &#36;quotafile[&#36;i];
    &#36;thisline = preg_replace("/([&#92;s]--)/", "", &#36;thisline);
    &#36;thisline = preg_split("/(&#92;s)/e", &#36;thisline, -1, PREG_SPLIT_NO_EMPTY);
    &#36;quotas[&#36;i]['user'] = str_replace("--", "", &#36;thisline[0]);
    &#36;quotas[&#36;i]['mb_used'] = round ((&#36;thisline[1] / 1024), 2);
    &#36;quotas[&#36;i]['mb_soft'] = round ((&#36;thisline[2] / 1024), 2);
    &#36;quotas[&#36;i]['mb_hard'] = round ((&#36;thisline[3] / 1024), 2);
    if (&#36;thisline[3] != 0) 
    {
        &#36;quotas[&#36;i]['mb_percent_used'] = round(((&#36;quotas[&#36;i]['mb_used'] / &#36;quotas[&#36;i]['mb_hard']) * 100), 1);
    }
    else
    {
        &#36;quotas[&#36;i]['mb_percent_used'] = 0;
    }
    &#36;quotas[&#36;i]['file_used'] = &#36;thisline[4];
    &#36;quotas[&#36;i]['file_soft'] = &#36;thisline[5];
    &#36;qoutas[&#36;i]['file_hard'] = &#36;thisline[6];
} 


if (!&#36;_GET['sort'])
{
    &#36;sort = 'mb_percent_used';
}
else
{
    &#36;sort = &#36;_GET['sort'];
}

if (!&#36;_GET['order'])
{
    &#36;order = "desc";
}
else
{
    &#36;order = &#36;_GET['order'];
}

foreach (&#36;quotas as &#36;key =&gt; &#36;row)
{
    &#36;user[&#36;key] = &#36;row['user'];
    &#36;mb_used[&#36;key] = &#36;row['mb_used'];
    &#36;mb_soft[&#36;key] = &#36;row['mb_soft'];
    &#36;mb_hard[&#36;key] = &#36;row['mb_hard'];
    &#36;mb_percent_used[&#36;key] = &#36;row['mb_percent_used'];
    &#36;file_used[&#36;key] = &#36;row['file_used'];
    &#36;file_soft[&#36;key] = &#36;row['file_soft'];
    &#36;file_hard[&#36;key] = &#36;row['file_hard'];
}

switch (&#36;sort)
{
    case "user": &#36;sortkey =&amp; &#36;user; break;
    case "mb_used": &#36;sortkey =&amp; &#36;mb_used; break;
    case "mb_soft": &#36;sortkey =&amp; &#36;mb_soft; break;
    case "mb_hard": &#36;sortkey =&amp; &#36;mb_hard; break;
    case "mb_percent_used":   &#36;sortkey =&amp; &#36;mb_percent_used; break;
    case "file_used": &#36;sortkey =&amp; &#36;file_used; break;
    case "file_soft": &#36;sortkey =&amp; &#36;file_used; break;
    case "file_hard": &#36;sortkey =&amp; &#36;file_hard; break;
}

switch (&#36;order)
{
    case "asc": &#36;sortorder = SORT_ASC; &#36;newsort = "desc"; break;
    case "desc": &#36;sortorder = SORT_DESC;&#36;newsort = "asc"; break;
}

array_multisort(&#36;sortkey, &#36;sortorder, &#36;quotas);

echo '&lt;table border="0" cellpadding="3"&gt;';

echo '&lt;tr class="headerrow"&gt;';
echo '&lt;td&gt;';
echo '&lt;a href="' . &#36;self . '?sort=user&amp;order=';
if (&#36;sort == "user") { echo &#36;newsort; } else { echo 'asc'; }
echo '"&gt;User&lt;/a&gt;';
echo '&lt;/td&gt;';
echo '&lt;td&gt;';
echo '&lt;a href="' . &#36;self . '?sort=mb_used&amp;order=';
if (&#36;sort == "mb_used") { echo &#36;newsort; } else { echo 'asc'; }
echo '"&gt;Used (Mb)&lt;/a&gt;';
echo '&lt;/td&gt;';
echo '&lt;td&gt;';
echo '&lt;a href="' . &#36;self . '?sort=mb_soft&amp;order=';
if (&#36;sort == "mb_soft") { echo &#36;newsort; } else { echo 'asc'; }
echo '"&gt;SoftQuota(Mb)&lt;/a&gt;';
echo '&lt;/td&gt;';
echo '&lt;td&gt;';
echo '&lt;a href="' . &#36;self . '?sort=mb_hard&amp;order=';
if (&#36;sort == "mb_hard") { echo &#36;newsort; } else { echo 'asc'; }
echo '"&gt;HardQuota(Mb)&lt;/a&gt;';
echo '&lt;/td&gt;';
echo '&lt;td&gt;';
echo '&lt;a href="' . &#36;self . '?sort=mb_percent_used&amp;order=';
if (&#36;sort == "mb_percent_used") { echo &#36;newsort; } else { echo 'asc'; }
echo '"&gt;% Used&lt;/a&gt;';
echo '&lt;/td&gt;';
echo '&lt;td&gt;';
echo '&lt;a href="' . &#36;self . '?sort=file_used&amp;order=';
if (&#36;sort == "file_used") { echo &#36;newsort; } else { echo 'asc'; }
echo '"&gt;Used(files)&lt;/a&gt;';
echo '&lt;/td&gt;';
echo '&lt;td&gt;';
echo '&lt;a href="' . &#36;self . '?sort=file_soft&amp;order=';
if (&#36;sort == "file_soft") { echo &#36;newsort; } else { echo 'asc'; }
echo '"&gt;SoftQuota(Files)&lt;/a&gt;';
echo '&lt;/td&gt;';

echo '&lt;/tr&gt;';

foreach (&#36;quotas as &#36;quotareport)
{
        if (&#36;colour == 0) { 
                &#36;colour = 1;
        } else {
                &#36;colour = 0;
        }
    if ( &#36;quotareport['mb_percent_used'] &gt; &#36;warnlimit)
    {
        &#36;warn = 1;
    }
    else
    {
        &#36;warn = 0;
    }
    echo '&lt;tr class="list' . &#36;colour . '"&gt;'; 
    foreach (&#36;quotareport as &#36;quotaelement)
    {
        if (&#36;warn)
        {
            echo '&lt;td class="warn"&gt;' . &#36;quotaelement . '&lt;/td&gt;';
        } 
        else
        {   
            echo '&lt;td&gt;' . &#36;quotaelement . '&lt;/td&gt;';
        }
    }
    echo '&lt;/tr&gt;';
}

echo '&lt;/table&gt;';

?&gt;

&lt;/table&gt;
&lt;/body&gt;
&lt;/html&gt;

</code>

Securing access to this file is up to you!

I’m not going to cover any Apache realm stuff as there is a wealth of documentation out there about it.

So, to keep this current, I have the following line in /etc/crontab

<code>
*/5     *       *       *       *       root    /bin/date &gt; /tmp/quotas &amp;&amp; /usr/sbin/repquota -au &gt;&gt; /tmp/quotas
</code>

This means that every five minutes, a quota report is dumped to “/tmp/quotas”. People viewing the php page can look at the date at the top of the file to see how current it is.

I’ve done the same thing for Cyrus mail quotas by the way, so if anyone is interested, I could post that code. It is almost exactly the same, but the file munging is slightly different.

No Comments

  • Over-quota users tends to be a chronic problem for me too. Here’s a script I
    made for checking the quota status for users with networked home folders on
    the client side as they login. Call it quotagauge and stick it in /usr/local/
    bin/:

    
    #!/bin/sh
    
    # quotagauge - displays network user's disk quote usage.
    # usage: quotagauge [-g graphic mode]
    
    # get user's short name.
    thisUser=`whoami`
    
    # uncomment to get quota from network.nidb and filter for jaguar 
    servers
    # maxSpace=`nicl / -read /users/$thisUser homedirectoryquota | awk '{print 
    $2}'`
    
    # get quota from LDAP for panther servers.  comment out if using jaguar 
    server.  replace <i>examples</i> with your info.
    maxSpace=`ldapsearch -h <i>hostname</i> -b "dc=<i>example</
    i>,dc=<i>edu</i>" -x -LLL uid=$thisUser apple-user-homequota|grep 
    apple|awk {'print $2'}`
    
    # convert to MB
    maxMegs=`expr $maxSpace / 1024 / 1024`
    
    # Returns user's home directory disk usage in 1024 KB blocks.
    currentSpace=`du -sk ~ | awk '{print $1}'`
    
    # convert to MB
    currentMegs=`expr $currentSpace / 1024`
    
    # floating point math for percentage used
    spaceUsed=`echo "scale=2; $currentMegs / $maxMegs" | bc`
    percentUsed=`echo "scale=0; $spaceUsed * 100" | bc | sed -e "s/.00//
    g"`
    
    if [ $# -eq 0 ]
    then
            echo "user: $thisUser 
    used: $currentMegs max: $maxMegs percent: $percentUsed"
            exit 0
    fi
    
    while [ $# -gt 0 ]
    do 
        case $1 in
            -g) # Use AppleScript 
    to present the usage data in a dialog box.
            eval `echo osascript 
    -e \'tell app \"Finder\" to activate\' \
            -e \'tell app 
    \"Finder\" to display dialog \"Disk usage for $thisUser:  \
            You have used 
    $currentMegs MB of your $maxMegs MB quota. \
            $percentUsed percent 
    full.\" buttons \{\"OK\"\} giving up after 30 default button 1\'`>/dev/
    null
            break
            ;;
            *) echo "Usage: 
    quotagauge [-g]; option -g for graphic mode" 1>&2
            exit 1
            break
            ;;
        esac
    done
    

    Create an AppleScript application to act as a wrapper with the following code:

    
    do shell script ("/usr/local/bin/quotagauge -g")
    

    Put the AppleScript in /Library/Scripts/ and add to to all your users Login
    Items.

    Letting users know where they stand in terms of remaining quota space at
    login has really cut down on the number problems. Hope this helps.

  • That’s an excellent idea. nice one.

    Do you find that the time to execute the ‘du’ command is too long? Perhaps it
    would be better to combine it with my method to grab the quota usage from
    the server directly, as it is keeping track of it all anyway.

    I might take that and modify that way, and change it to be used with LanOSD
    so that you get really pretty messages… 🙂

  • Funny, I was thinking "Hey, this quotagauge script looks <i>familiar</i>" then I look at who posted.

    I made similar changes for LDAP and it gets hung up at du as well.

    This is really cool, I think I’ll give a shot at integrating the two approaches.

    • Ha ha, my script-fu is world famous! Or at least CCA famous…

    • hi

      new to scripts, but like the idea of this. would be great if this could be modified
      to show a student when they log in how much of thier allocated space they
      have used… how have you implemented this?
      any help greatly appreciated

      many thanks
      chris

Leave a reply

You must be logged in to post a comment.