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", "a );
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 }
|