Cover V11, I02
feb2002.tar

Listing 2 The quota implementation

1 #include <stdlib.h>
  2 #include <stdio.h>
  3 #include <dirent.h>
  4 #include <unistd.h>
  5 #include <time.h>
  6 #include <string.h>
  7 #include <errno.h>
  8 #include <sys/types.h>
  9 #include <sys/stat.h>
 10 #include <sys/file.h>
 11 
 12 int do_quota_stuff()
 13 {
 14     /* Create the file-handlers: */
 15     FILE *file = 0;
 16     FILE *file2 = 0;
 17       FILE *file3 = 0;
 18 
 19     /* Create the directory-hanlders: */
 20     DIR *dir;
 21     DIR *dir2;
 22 
 23     /* The dirent-structure is used to investigate the directories: */
 24     struct dirent *dir_element;
 25     struct dirent *dir_element2;
 26     
 27     /* The stat-structure is used the get the file-sizes: */
 28     struct stat buf;
 29 
 30     /* This variable will hold the current size of a database: */
 31     double size = 0;
 32 
 33     /* This variable will hold the maximum allowed database-size: */    
 34     double quota = 0;
 35 
 36     /* 
 37      * If the maximum allowed database-size is exceeded, this
 38      * varaible will hold the number of exceeded megabytes:
 39     */
 40     double exc_mbytes = 0;
 41     
 42     /* This variable will hold the current database-directory: */
 43     char db_dir[512];
 44 
 45     /* This variable will hold the complete path to the current database-file: */
 46     char cur_file[512];
 47 
 48     /* This variable will hold the complete path to the current quota-file: */
 49     char cur_quota_file[512];
 50 
 51     /*
 52      * If the quota is exceeded for the currently processed database,
 53      * this variable will hold the complete path to the file that is
 54      * created to indicate that the problem has been reported, and that
 55      * it should not be reported again.
 56     */    
 57     char reported_file[512];
 58 
 59     /*
 60      * This variable will hold the complete command that is used to
 61      * send an e-mail to the administrator about the occurence or
 62      * solving of a quota-problem.
 63     */
 64     char mail_command[1024];
 65 
 66     /* This variable will be used to calculate the current time: */
 67     time_t t;
 68 
 69     /*
 70      * When a database has exceeded its quota, this variable will
 71      * be set to 1, which will cause a little different output
 72      * to the log-file.
 73     */ 
 74     short too_big = 0;
 75 
 76     /* Open a handle to the data-directory: */
 77     if( (dir = opendir( "/usr/local/mysql/data" )) == NULL )
 78     {
 79         perror( "opendir(/usr/local/mysql/data)" );
 80     }
 81 
 82     /* Open the log-file for writing: */
 83     if( (file2 = fopen( "/usr/local/mysql/logs/quota_log", "a" )) == NULL )
 84     {
 85         perror( "fopen(DB_DATA_DIR)" );
 86     }
 87 
 88     /* The code generates a timestamp and writes some info to the log-file: */
 89     time(&t);
 90     fprintf( file2, "-------------------------------------------------\n" );
 91     fprintf( file2, "Performing quota-check at %s", ctime(&t) );
 92 
 93     /* This loop scans each directory in /usr/local/mysql/data: */
 94     while( (dir_element = readdir(dir)) )
 95     {
 96         /*
 97          * Of course, size should always be 0 when we start calculating
 98          * the size of database-directory:
 99         */
100         size = 0;
101          
102          /*
103           * The database-directory contains a few elements that we want
104           * to skip. '.' and '..' is not interesting to us at all. 'mysql'
105           * is  database that MySQL uses internally to define user-privileges,
106           * so we want to skip that too. 'slackhack.pid' and 'slackhack.err'
107           * are also used by MySQL internally, so we want to skip these as well.
108           * However, note that the portion in front of the '.' in the two
109           * files depends of your hostname. In this, the software was running
110           * on a host named 'slackhack'. You need to change this to your
111           * own hostname.
112           */
113         if( (!strcmp( dir_element-d_name, ".")) || \
                    (!strcmp( dir_element-d_name, "..")) ||
114                 (!strcmp( dir_element-d_name, "mysql")) || \
                    (!strcmp( dir_element-d_name, "slackhack.err"))
115              || (!strcmp( dir_element-d_name, "slackhack.pid")) )
116         {
117             continue;
118         }
119         
120         /*
121          * Store the complete path to the currently processed database-
122          * directory in db_dir:
123         */
124         sprintf( db_dir, "%s/%s", "/usr/local/mysql/data", dir_element-d_name );
125         
126         /* Open a directory-handle for db_dir: */
127         if( (dir2 = opendir( db_dir )) == NULL )
128         {
129             perror( "opendir(db_dir)" );
130         }
131         
132         /*
133          * This loop scans each file in db_dir, adding each file's 
134          * size to the variable size.
135         */
136         while( (dir_element2 = readdir(dir2)) )
137         {
138             /* We skip '.' and '..': */
139             if( (!strcmp( dir_element2-d_name, ".")) || \
                   (!strcmp( dir_element2-d_name, "..")) )
140             {
141                 continue;
142             }
143             else
144             {
145                 /*
146                  * Store the complete path to the currently processed
147                  * file in the variable cur_file:
148                 */
149                 sprintf( cur_file, "%s/%s", db_dir, dir_element2-d_name );
150                 
151                 /*
152                  * Retrive information about the currently processed file: */
153                 stat( cur_file, &buf );
154     
155                 /*
156                  * Add the size of the currently processed file to
157                  * the variable size:
158                 */
159                 size = (size + buf.st_size);
160             }
161         }
162     
163         /*
164          * Now we're done with this database, so we
165          * close the directory-handle:
166         */
167         if( (closedir(dir2)) == -1 )
168         {
169             perror( "closedir(dir2)" );
170         }
171     
172         /* We want the size in megabytes, so we do a simple conversio n:*/
173         size = ((size/1024)/1024);
174     
175         /*
176          * Store the complete path to the current database's
177          * quota-file in cur_quota_file:
178         */     
179         sprintf( cur_quota_file, "%s/%s", "/usr/local/mysql/quota", \
                    dir_element-d_name );
180             
181         /* Open a file-stream to the current quota-file: */    
182         if( (file = fopen( cur_quota_file, "r" )) == NULL )
183         {
184             perror( "fdopen(fd,\"r\")" );
185         }
186             
187         /* Read the value saved in cur_quota_file and store it in quota: */
188         fscanf( file, "%lf", &quota );
189         
190         /* We're done reading from the quota-file, so we close the stream: */
191         if( (fclose( file )) != 0 )
192         {
193             perror( "fclose(file)" );
194         }
195     
196         /*
197          * Store the complete path to the file that indicates that a
198          * database has been reported as too big in the variable
199          * reported_file:
200         */
201         sprintf( reported_file, "%s/%s_reported", "/usr/local/mysql/quota", \
                    dir_element-d_name );
202     
203         /*
204          * Here, we call gen_www_info(), passing it the current
205         * database-name, size and maximum allowed size. This
206         * will make gen_www_info() dump some information about
207         * the current database in /var/lib/apache/htdocs/quota/$DB_NAME.
208         */
209         if( (gen_www_info( dir_element-d_name, size, quota )) != 0 )
210         {
211             /* If there was a problem, report that in the log-file: */
212             fprintf( file2, "WARNING: Could not create db-info for %s\n", dir_element-d_name );
213         }
214             
215         /* Check to see if the quota has been exceeded: */
216         if( (size  quota) )
217         {
218             /* Store the number of exceeded megabytes in exc_mbytes: */
219             exc_mbytes = (size - quota);
220     
221             /*
222              * Write information about the database that has exceeded
223              * it's maximum size to the log-file:
224             */
225             fprintf( file2, "%s: WARNING: %.2f/%.2f\n", dir_element-d_name, size, quota );
226                 
227             /* This will make the output to the log-file a little different: */
228             too_big = 1;
229     
230             /*
231              * If the reported-file does not exist, this database
232              * has obviously not been reported to the administrator.
233              * On the other hand, if the reported-file do exist,
234              * the database has been reported, and we avoid doing it
235              * again.
236             */
237             if( (fopen(reported_file, "r") == NULL) )
238             {
239                 /*
240                  * Generate the complete command for mailing the administrator
241                  * about this quota-problem:
242                 */
243                 sprintf( mail_command, "echo \"Hi there!\n\nThis is a 
                            report from the MySQL quota daemon. Unfortunately, 
                            it looks as the user %s has\nexceeded his/her 
                            maximum allowed database-size. The database is 
                            %.2fMb, while the maximum\nallowed size for this 
                            account is %.2fMb. Maybe we should contact the 
                            customer?\n\nUser/Database: %s\nCurrent Status: 
                            %.2fMb\nAllowed Size: %.2fMb\n\n\nSincerely,\nYour 
                            MySQL Quota Daemon\" | mail -s \"MySQL quota reports 
                            regarding %s\" daniel@solin.org", 
                            dir_element-d_name, size, quota, dir_element-d_name, 
                            size, quota, dir_element-d_name );
244                 
245                 /* We use system() to issue the command: */
246                 system( mail_command );
247
248                 /*
249                  * By creating the reported-file, we make sure 
250                  * this problem is reported only once:
251                 */
252                 file3 = fopen( reported_file, "w" );
253     
254                 /*
255                 * We output a "1" to the reported-file, but this could                     
256                 * be just about anything. The only thing that the program
257                  * cares about is if the file exists or not.
258                 */
259                 fprintf( file3, "1" );
260     
261                 /* We're done with the reported-file, so we close the stream: */
262                 fclose( file3 );
263             }
264         }
265     
266         /* 
267          * If the quota is not exceeded, but a reported-file exists,
268          * the problem has been solved. In other words, the database-
269          * size has been decreased by the customer, or the maximum
270          * allowed size has been increased by the administrator.
271          * In any case, the reported-file can now safely be removed.
272         */ 
273         if( (size < quota) && (file3 = fopen(reported_file, "r")) )
274         {
275             /*
276              * The file3-stream was opened again above, se we need
277              * to close it once more:
278             */
279             fclose( file3 );
280     
281             /* Remove the reported-file: */
282             unlink( reported_file );
283     
284             /*
285              * This generates the command that will send an e-mail to the
286              * administrator, informing him/her that the quota-problems
287              * for this database now has been solved, and stores it in the
288              * variable mail_command.
289             */
290             sprintf( mail_command, "echo \"Hi again!\n\nI just wanted to let 
                         you know that it the quota-problems regarding %s\nseems 
                         to be is solved now. See the new data 
                         below:\n\nUser/Database: %s\nCurrent Size: 
                         %.2fMb\nAllowed Size: %.2fMb\n\n\nAll best,\nMyQuotaD\" 
                         | mail -s \"Reports from MyQuotaD regarding %s\" 
                         daniel@solin.org", dir_element-d_name, 
                         dir_element-d_name, size, quota, dir_element-d_name );
291                 
292             /* Again, we use system() to issue the command: */
293             system( mail_command );
294         }
295     }
296     
297     /*
298      * We're done processing database-directories, so we can
299      * safely close the directory-handle:
300     */
301     if( (closedir(dir)) == -1 )
302     {
303         perror( "closedir(dir)" );
304     }
305     
306     /*
307      * If too_big is 1, this indicate that one or more exceeded
308      * accounts were found, and that the ending output to the log-
309      * file should be a little different, and that the ending output to the log
310      * file should be a little different.
311     */
312     if( too_big == 1 )
313     {
314         fprintf( file2, "Result: One or more databases have exceeded their \
                     quota!" );
315     }
316         
317     /*
318      * If too_big != 1, everything is okay, and we output the usual
319      * message that everything seems to be okay.
320     */
321     else
322     {
323         fprintf( file2, "Result: Everything seems to be okay!" );
324     }
325     
326     /*
327      * We output the ending-line to the log-file, and then closes
328      * the file-stream.
329     */
330     fprintf( file2, "\n---------------------------------------------\n\n" );
331     fclose( file2 );
332     
333     /* Everything went just fine, se we return 0: */
334     return 0;
335 }