]> git.saurik.com Git - apple/system_cmds.git/blame - dirhelper.tproj/dirhelper.c
system_cmds-431.tar.gz
[apple/system_cmds.git] / dirhelper.tproj / dirhelper.c
CommitLineData
34d340d7
A
1/*
2 * Copyright (c) 2006-2007 Apple Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23#include <stdbool.h>
24#include <stdlib.h>
25#include <stdio.h>
26#include <dirent.h>
27#include <errno.h>
28#include <fcntl.h>
29#include <fts.h>
30#include <pthread.h>
31#include <pwd.h>
32#include <unistd.h>
33#include <sys/stat.h>
34#include <sys/sysctl.h>
35#include <sys/time.h>
36
37#include <mach/mach.h>
38#include <mach/mach_error.h>
39#include <servers/bootstrap.h>
40
41#include <bsm/libbsm.h>
42
43#include <asl.h>
44#include <membership.h>
45#include <launch.h>
46#include <dirhelper_priv.h>
47
48#include "dirhelper.h"
49#include "dirhelperServer.h"
50
51// globals for idle exit
52struct idle_globals {
53 mach_port_t mp;
54 long timeout;
55 struct timeval lastmsg;
56};
57
58struct idle_globals idle_globals;
59
60void* idle_thread(void* param __attribute__((unused)));
61
62int file_check(const char* path, int mode, int uid, int gid);
63#define is_file(x) file_check((x), S_IFREG, -1, -1)
64#define is_directory(x) file_check((x), S_IFDIR, -1, -1)
65#define is_root_wheel_directory(x) file_check((x), S_IFDIR, 0, 0)
66
67int is_safeboot(void);
68
69void clean_files_older_than(const char* path, time_t when);
70void clean_directory(const char* name, int);
71
72kern_return_t
73do___dirhelper_create_user_local(
74 mach_port_t server_port __attribute__((unused)),
75 audit_token_t au_tok)
76{
77 int res = 0;
78 uid_t euid;
79 gid_t gid = 0;
80 struct passwd* pwd = NULL;
81
82 gettimeofday(&idle_globals.lastmsg, NULL);
83
84 audit_token_to_au32(au_tok,
85 NULL, // audit uid
86 &euid, // euid
87 NULL, // egid
88 NULL, // ruid
89 NULL, // rgid
90 NULL, // remote_pid
91 NULL, // asid
92 NULL); // aud_tid_t
93
94 // Look-up the primary gid of the user. We'll use this for chown(2)
95 // so that the created directory is owned by a group that the user
96 // belongs to, avoiding warnings if files are moved outside this dir.
97 pwd = getpwuid(euid);
98 if (pwd) gid = pwd->pw_gid;
99
100 do { // begin block
101 char path[PATH_MAX];
102 char *next;
103
104 if (__user_local_dirname(euid, DIRHELPER_USER_LOCAL, path, sizeof(path)) == NULL) {
105 asl_log(NULL, NULL, ASL_LEVEL_ERR,
106 "__user_local_dirname: %s", strerror(errno));
107 break;
108 }
109
110 //
111 // 1. Starting with VAR_FOLDERS_PATH, make each subdirectory
112 // in path, ignoring failure if it already exists.
113 // 2. Change ownership of directory to the user.
114 //
115 next = path + strlen(VAR_FOLDERS_PATH);
116 while ((next = strchr(next, '/')) != NULL) {
117 *next = 0; // temporarily truncate
118 res = mkdir(path, 0755);
119 if (res != 0 && errno != EEXIST) {
120 asl_log(NULL, NULL, ASL_LEVEL_ERR,
121 "mkdir(%s): %s", path, strerror(errno));
122 break;
123 }
124 *next++ = '/'; // restore the slash and increment
125 }
126 if(next || res) // an error occurred
127 break;
128 res = chown(path, euid, gid);
129 if (res != 0) {
130 asl_log(NULL, NULL, ASL_LEVEL_ERR,
131 "chown(%s): %s", path, strerror(errno));
132 }
133 } while(0); // end block
134 return KERN_SUCCESS;
135}
136
137kern_return_t
138do___dirhelper_idle_exit(
139 mach_port_t server_port __attribute__((unused)),
140 audit_token_t au_tok __attribute__((unused))) {
141
142 struct timeval now;
143 gettimeofday(&now, NULL);
144 long delta = now.tv_sec - idle_globals.lastmsg.tv_sec;
145 if (delta >= idle_globals.timeout) {
146 asl_log(NULL, NULL, ASL_LEVEL_DEBUG,
147 "idle exit after %ld seconds", delta);
148 exit(EXIT_SUCCESS);
149 }
150
151 return KERN_SUCCESS;
152}
153
154void*
155idle_thread(void* param __attribute__((unused))) {
156 for(;;) {
157 struct timeval now;
158 gettimeofday(&now, NULL);
159 long delta = (now.tv_sec - idle_globals.lastmsg.tv_sec);
160 if (delta < idle_globals.timeout) {
161 // sleep for remainder of timeout
162 sleep(idle_globals.timeout - delta);
163 } else {
164 // timeout has elapsed, attempt to idle exit
165 __dirhelper_idle_exit(idle_globals.mp);
166 }
167 }
168 return NULL;
169}
170
171// If when == 0, all files are removed. Otherwise, only regular files older than when.
172void
173clean_files_older_than(const char* path, time_t when) {
174 FTS* fts;
175
176 char* path_argv[] = { (char*)path, NULL };
177 fts = fts_open(path_argv, FTS_PHYSICAL | FTS_XDEV, NULL);
178 if (fts) {
179 FTSENT* ent;
180 asl_log(NULL, NULL, ASL_LEVEL_INFO, "Cleaning %s", path);
181 while ((ent = fts_read(fts))) {
182 switch(ent->fts_info) {
183 case FTS_F:
184 case FTS_DEFAULT:
185 // Unlink the file if it has not been accessed since
186 // the specified time. Obtain an exclusive lock so
187 // that we can avoid a race with other processes
188 // attempting to open the file.
189 if (when == 0) {
190 (void)unlink(ent->fts_path);
191 } else if (S_ISREG(ent->fts_statp->st_mode) && ent->fts_statp->st_atime < when) {
192 int fd = open(ent->fts_path, O_RDONLY | O_NONBLOCK);
193 if (fd != -1) {
194 int res = flock(fd, LOCK_EX | LOCK_NB);
195 if (res == 0) {
196 struct stat sb;
197 res = fstat(fd, &sb);
198 if (res == 0 && sb.st_atime < when) {
199 (void)unlink(ent->fts_path);
200 }
201 (void)flock(fd, LOCK_UN);
202 }
203 close(fd);
204 }
205 }
206 break;
207
208 case FTS_SL:
209 case FTS_SLNONE:
210 if (when == 0) {
211 (void)unlink(ent->fts_path);
212 }
213 break;
214
215 case FTS_DP:
216 if (when == 0) {
217 (void)rmdir(ent->fts_path);
218 }
219 break;
220
221 case FTS_ERR:
222 case FTS_NS:
223 asl_log(NULL, NULL, ASL_LEVEL_ERR, "%s: %s", ent->fts_path, strerror(ent->fts_errno));
224 break;
225
226 default:
227 break;
228 }
229 }
230 fts_close(fts);
231 } else {
232 asl_log(NULL, NULL, ASL_LEVEL_ERR, "%s: %s", path, strerror(errno));
233 }
234}
235
236int
237file_check(const char* path, int mode, int uid, int gid) {
238 int check = 1;
239 struct stat sb;
240 if (lstat(path, &sb) == 0) {
241 check = check && ((sb.st_mode & S_IFMT) == mode);
242 check = check && ((sb.st_uid == (uid_t)uid) || uid == -1);
243 check = check && ((sb.st_gid == (gid_t)gid) || gid == -1);
244 } else {
245 if (errno != ENOENT) {
246 asl_log(NULL, NULL, ASL_LEVEL_ERR, "%s: %s", path, strerror(errno));
247 }
248 check = 0;
249 }
250 return check;
251}
252
253int
254is_safeboot(void) {
255 uint32_t sb = 0;
256 size_t sbsz = sizeof(sb);
257
258 if (sysctlbyname("kern.safeboot", &sb, &sbsz, NULL, 0) != 0) {
259 return 0;
260 } else {
261 return (int)sb;
262 }
263}
264
265void
266clean_directory(const char* name, int machineBoot) {
267 DIR* d;
268 time_t when = 0;
269
270 if (!machineBoot) {
271 struct timeval now;
272 long days = 3;
273 const char* str = getenv("CLEAN_FILES_OLDER_THAN_DAYS");
274 if (str) {
275 days = strtol(str, NULL, 0);
276 }
277 (void)gettimeofday(&now, NULL);
278
279 asl_log(NULL, NULL, ASL_LEVEL_INFO, "Cleaning %s older than %ld days", name, days);
280
281 when = now.tv_sec - (days * 60 * 60 * 24);
282 }
283
284 // Look up the boot time
285 struct timespec boottime;
286 size_t len = sizeof(boottime);
287 if (sysctlbyname("kern.boottime", &boottime, &len, NULL, 0) == -1) {
288 asl_log(NULL, NULL, ASL_LEVEL_ERR, "%s: %s", "sysctl kern.boottime", strerror(errno));
289 return;
290 }
291
292 if (!is_root_wheel_directory(VAR_FOLDERS_PATH)) {
293 asl_log(NULL, NULL, ASL_LEVEL_ERR, "%s: %s", VAR_FOLDERS_PATH, "invalid ownership");
294 return;
295 }
296
297 if ((d = opendir(VAR_FOLDERS_PATH))) {
298 struct dirent* e;
299 char path[PATH_MAX];
300
301 // /var/folders/*
302 while ((e = readdir(d))) {
303 if (strcmp(e->d_name, ".") == 0 || strcmp(e->d_name, "..") == 0) continue;
304
305 snprintf(path, sizeof(path), "%s%s", VAR_FOLDERS_PATH, e->d_name);
306 if (is_root_wheel_directory(path)) {
307 DIR* d2 = opendir(path);
308 if (d2) {
309 struct dirent* e2;
310
311 // /var/folders/*/*
312 while ((e2 = readdir(d2))) {
313 char temporary_items[PATH_MAX];
314 if (strcmp(e2->d_name, ".") == 0 || strcmp(e2->d_name, "..") == 0) continue;
315
316 snprintf(temporary_items, sizeof(temporary_items),
317 "%s/%s/%s", path, e2->d_name, name);
318 if (is_directory(temporary_items)) {
319 // at boot time we clean all files,
320 // otherwise only clean regular files.
321 clean_files_older_than(temporary_items, when);
322 }
323 }
324
325 closedir(d2);
326 } else {
327 asl_log(NULL, NULL, ASL_LEVEL_ERR, "%s: %s", path, strerror(errno));
328 }
329 }
330 }
331
332 closedir(d);
333 } else {
334 asl_log(NULL, NULL, ASL_LEVEL_ERR, "%s: %s", VAR_FOLDERS_PATH, strerror(errno));
335 }
336}
337
338int
339main(int argc, char* argv[]) {
340 mach_msg_size_t mxmsgsz = MAX_TRAILER_SIZE;
341 kern_return_t kr;
342 long idle_timeout = 30; // default 30 second timeout
343
344 // Clean up TemporaryItems directory when launched at boot.
345 // It is safe to clean all file types at this time.
346 if (argc > 1 && strcmp(argv[1], "-machineBoot") == 0) {
347 clean_directory(DIRHELPER_TEMP_STR, 1);
348 clean_directory("TemporaryItems", 1);
349 clean_directory("Cleanup At Startup", 1);
350 if (is_safeboot()) clean_directory(DIRHELPER_CACHE_STR, 1);
351 exit(EXIT_SUCCESS);
352 } else if (argc > 1 && strcmp(argv[1], "-cleanTemporaryItems") == 0) {
353 clean_directory(DIRHELPER_TEMP_STR, 0);
354 clean_directory("TemporaryItems", 0);
355 exit(EXIT_SUCCESS);
356 } else if (argc > 1) {
357 exit(EXIT_FAILURE);
358 }
359
360 launch_data_t config = NULL, checkin = NULL;
361 checkin = launch_data_new_string(LAUNCH_KEY_CHECKIN);
362 config = launch_msg(checkin);
363 if (!config || launch_data_get_type(config) == LAUNCH_DATA_ERRNO) {
364 asl_log(NULL, NULL, ASL_LEVEL_ERR, "launchd checkin failed");
365 exit(EXIT_FAILURE);
366 }
367
368 launch_data_t tmv;
369 tmv = launch_data_dict_lookup(config, LAUNCH_JOBKEY_TIMEOUT);
370 if (tmv) {
371 idle_timeout = launch_data_get_integer(tmv);
372 asl_log(NULL, NULL, ASL_LEVEL_DEBUG,
373 "idle timeout set: %ld seconds", idle_timeout);
374 }
375
376 launch_data_t svc;
377 svc = launch_data_dict_lookup(config, LAUNCH_JOBKEY_MACHSERVICES);
378 if (!svc) {
379 asl_log(NULL, NULL, ASL_LEVEL_ERR, "no mach services");
380 exit(EXIT_FAILURE);
381 }
382
383 svc = launch_data_dict_lookup(svc, DIRHELPER_BOOTSTRAP_NAME);
384 if (!svc) {
385 asl_log(NULL, NULL, ASL_LEVEL_ERR, "no mach service: %s",
386 DIRHELPER_BOOTSTRAP_NAME);
387 exit(EXIT_FAILURE);
388 }
389
390 mach_port_t mp = launch_data_get_machport(svc);
391 if (mp == MACH_PORT_NULL) {
392 asl_log(NULL, NULL, ASL_LEVEL_ERR, "NULL mach service: %s",
393 DIRHELPER_BOOTSTRAP_NAME);
394 exit(EXIT_FAILURE);
395 }
396
397 // insert a send right so we can send our idle exit message
398 kr = mach_port_insert_right(mach_task_self(), mp, mp,
399 MACH_MSG_TYPE_MAKE_SEND);
400 if (kr != KERN_SUCCESS) {
401 asl_log(NULL, NULL, ASL_LEVEL_ERR, "send right failed: %s",
402 mach_error_string(kr));
403 exit(EXIT_FAILURE);
404 }
405
406 // spawn a thread for our idle timeout
407 pthread_t thread;
408 idle_globals.mp = mp;
409 idle_globals.timeout = idle_timeout;
410 gettimeofday(&idle_globals.lastmsg, NULL);
411 pthread_create(&thread, NULL, &idle_thread, NULL);
412
413 // look to see if we have any messages queued. if not, assume
414 // we were launched because of the calendar interval, and attempt
415 // to clean the temporary items.
416 mach_msg_type_number_t status_count = MACH_PORT_RECEIVE_STATUS_COUNT;
417 mach_port_status_t status;
418 kr = mach_port_get_attributes(mach_task_self(), mp,
419 MACH_PORT_RECEIVE_STATUS, (mach_port_info_t)&status, &status_count);
420 if (kr == KERN_SUCCESS && status.mps_msgcount == 0) {
421 clean_directory(DIRHELPER_TEMP_STR, 0);
422 clean_directory("TemporaryItems", 0);
423 }
424
425 // main event loop
426
427 kr = mach_msg_server(dirhelper_server, mxmsgsz, mp,
428 MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AUDIT) |
429 MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0));
430 if (kr != KERN_SUCCESS) {
431 asl_log(NULL, NULL, ASL_LEVEL_ERR,
432 "mach_msg_server(mp): %s", mach_error_string(kr));
433 exit(EXIT_FAILURE);
434 }
435
436 exit(EXIT_SUCCESS);
437}