1 /* File created by Chris Youngworth, Apple Computer 2/11/99 */
7 #include <mach/mach_error.h>
8 #include <mach/mach_traps.h>
13 #include <mach/mach_syscalls.h>
14 #include <mach/mig_errors.h>
15 #include <sys/param.h>
16 #include <sys/mount.h>
20 #include <sys/sysctl.h>
22 #include <sys/types.h>
34 #include <CoreFoundation/CoreFoundation.h>
35 #include <IOKit/pwr_mgt/IOPMLibPrivate.h>
36 #include <IOKit/ps/IOPowerSources.h>
37 #include <IOKit/ps/IOPowerSourcesPrivate.h>
39 #include <default_pager/default_pager_types.h>
40 #include <default_pager_alerts_server.h>
41 #include <backing_store_alerts.h>
42 #include <backing_store_triggers_server.h>
46 * HI_WATER_DEFAULT set to this funny value to
47 * match the size that the low space application
48 * is asking for... need to keep MINIMUM_SIZE
51 #define HI_WATER_DEFAULT 40000000
52 #define MINIMUM_SIZE (1024 * 1024 * 64)
53 #define MAXIMUM_SIZE (1024 * 1024 * 1024)
60 unsigned int low_water
;
67 unsigned int hi_water
;
68 unsigned int local_hi_water
;
74 /* global parameters for application notification option */
75 mach_port_t trigger_port
= MACH_PORT_NULL
;
76 mach_port_t notify_port
= MACH_PORT_NULL
;
77 unsigned int notify_high
= 0;
78 unsigned int bs_recovery
;
81 void setprof __P((struct kvmvars *kvp, int state));
82 void dumpstate __P((struct kvmvars *kvp));
83 void reset __P((struct kvmvars *kvp));
90 mach_msg_size_t max_size
,
92 mach_msg_options_t options
)
94 mig_reply_error_t
*bufRequest
= 0, *bufReply
= 0;
95 register mach_msg_return_t mr
;
96 register kern_return_t kr
;
98 if ((kr
= vm_allocate(mach_task_self(),
99 (vm_address_t
*)&bufRequest
,
100 max_size
+ MAX_TRAILER_SIZE
,
101 TRUE
)) != KERN_SUCCESS
)
103 if ((kr
= vm_protect(mach_task_self(),
104 (vm_address_t
)bufRequest
,
105 max_size
+ MAX_TRAILER_SIZE
,
106 FALSE
, VM_PROT_ALL
)) != KERN_SUCCESS
)
108 mlock(bufRequest
, max_size
+ MAX_TRAILER_SIZE
);
109 if ((kr
= vm_allocate(mach_task_self(),
110 (vm_address_t
*)&bufReply
,
111 max_size
+ MAX_TRAILER_SIZE
,
112 TRUE
)) != KERN_SUCCESS
)
114 if ((kr
= vm_protect(mach_task_self(),
115 (vm_address_t
)bufReply
,
116 max_size
+ MAX_TRAILER_SIZE
,
117 FALSE
, VM_PROT_ALL
)) != KERN_SUCCESS
)
119 mlock(bufReply
, max_size
+ MAX_TRAILER_SIZE
);
121 mr
= mach_msg(&bufRequest
->Head
, MACH_RCV_MSG
|options
,
122 0, max_size
, rcv_name
,
123 MACH_MSG_TIMEOUT_NONE
, MACH_PORT_NULL
);
124 if (mr
== MACH_MSG_SUCCESS
) {
125 /* we have a request message */
127 if(!(default_pager_alerts_server(
128 &bufRequest
->Head
, &bufReply
->Head
)))
129 backing_store_triggers_server(
130 &bufRequest
->Head
, &bufReply
->Head
);
132 if (!(bufReply
->Head
.msgh_bits
& MACH_MSGH_BITS_COMPLEX
) &&
133 bufReply
->RetCode
!= KERN_SUCCESS
) {
134 if (bufReply
->RetCode
== MIG_NO_REPLY
)
136 * This return code is a little tricky--
137 * it appears that the demux routine found an
138 * error of some sort, but since that error
139 * would not normally get returned either to
140 * the local user or the remote one, we pretend it's
144 bufRequest
->Head
.msgh_remote_port
= MACH_PORT_NULL
;
145 mach_msg_destroy(&bufRequest
->Head
);
149 if (bufReply
->Head
.msgh_remote_port
== MACH_PORT_NULL
) {
150 /* no reply port, so destroy the reply */
151 if (bufReply
->Head
.msgh_bits
& MACH_MSGH_BITS_COMPLEX
)
152 mach_msg_destroy(&bufReply
->Head
);
161 (void)vm_deallocate(mach_task_self(),
162 (vm_address_t
) bufRequest
,
163 max_size
+ MAX_TRAILER_SIZE
);
164 (void)vm_deallocate(mach_task_self(),
165 (vm_address_t
) bufReply
,
166 max_size
+ MAX_TRAILER_SIZE
);
173 backing_store_triggers(dynamic_pager
, hi_wat
, flags
, port
)
174 mach_port_t dynamic_pager
;
181 if (file_count
> max_valid
)
182 cur_limits
= max_valid
;
184 cur_limits
= file_count
;
186 if((hi_wat
+ limits
[cur_limits
].size
) > limits
[cur_limits
].low_water
)
187 return KERN_FAILURE
; /* let ipc system clean up port */
189 /* If there was a previous registration, throw it away */
190 if (notify_port
!= MACH_PORT_NULL
) {
191 mach_port_deallocate(mach_task_self(), notify_port
);
192 notify_port
= MACH_PORT_NULL
;
196 notify_high
= hi_wat
;
197 if(hi_water
< notify_high
) {
198 local_hi_water
= notify_high
;
200 local_hi_water
= hi_water
;
202 if(notify_high
> hi_water
) {
203 default_pager_space_alert(trigger_port
, HI_WAT_ALERT
);
210 default_pager_space_alert(alert_port
, flags
)
211 mach_port_t alert_port
;
220 unsigned int cur_size
;
221 unsigned int notifications
;
224 if(flags
& HI_WAT_ALERT
) {
228 if (file_count
> max_valid
)
229 cur_limits
= max_valid
;
231 cur_limits
= file_count
;
233 cur_size
= limits
[cur_limits
].size
;
237 * because the LO_WAT threshold changes relative to
238 * the size of the swap file we're creating
239 * we need to reset the LO_WAT_ALERT threshold each
240 * time we create a new swap file
242 if (limits
[cur_limits
].low_water
)
243 notifications
= HI_WAT_ALERT
| LO_WAT_ALERT
;
245 notifications
= HI_WAT_ALERT
;
247 sprintf(subfile
, "%s%d", fileroot
, file_count
);
248 file_ptr
= fopen(subfile
, "w+");
249 if (file_ptr
== NULL
) {
250 /* force error recovery below */
253 fchmod(fileno(file_ptr
), (mode_t
)01600);
254 error
= fcntl(fileno(file_ptr
), F_SETSIZE
, &filesize
);
256 error
= ftruncate(fileno(file_ptr
), filesize
);
266 if (file_count
> max_valid
)
267 cur_limits
= max_valid
;
269 cur_limits
= file_count
;
271 if (limits
[cur_limits
].low_water
)
272 notifications
= HI_WAT_ALERT
| LO_WAT_ALERT
;
274 notifications
= HI_WAT_ALERT
;
276 local_hi_water
= local_hi_water
>>2;
277 if(notify_high
>= (local_hi_water
)) {
278 if(notify_port
!= MACH_PORT_NULL
) {
279 /* notify monitoring app of */
280 /* backing store shortage */
281 backing_store_alert(notify_port
,
283 mach_port_deallocate(mach_task_self(),
285 notify_port
= MACH_PORT_NULL
;
289 macx_triggers(local_hi_water
, limits
[cur_limits
].low_water
, notifications
, alert_port
);
291 if(hi_water
< notify_high
) {
292 if(local_hi_water
< notify_high
) {
293 bs_recovery
= notify_high
- local_hi_water
;
295 local_hi_water
= notify_high
;
297 if(local_hi_water
< hi_water
) {
298 bs_recovery
= hi_water
- local_hi_water
;
300 local_hi_water
= hi_water
;
302 ret
= macx_swapon(subfile
, flags
, cur_size
, priority
);
308 if (file_count
> max_valid
)
309 cur_limits
= max_valid
;
311 cur_limits
= file_count
;
313 if (limits
[cur_limits
].low_water
)
314 notifications
= HI_WAT_ALERT
| LO_WAT_ALERT
;
316 notifications
= HI_WAT_ALERT
;
318 local_hi_water
= local_hi_water
>>2;
319 if(notify_high
>= (local_hi_water
)) {
320 if(notify_port
!= MACH_PORT_NULL
) {
321 /* notify monitoring app of */
322 /* backing store shortage */
326 mach_port_deallocate(
329 notify_port
= MACH_PORT_NULL
;
333 macx_triggers(local_hi_water
, limits
[cur_limits
].low_water
, notifications
, alert_port
);
334 } else if(bs_recovery
<= cur_size
) {
335 if((bs_recovery
!= 0) && (notify_port
)) {
336 backing_store_alert(notify_port
,
338 mach_port_deallocate(mach_task_self(),
340 notify_port
= MACH_PORT_NULL
;
345 bs_recovery
= bs_recovery
-cur_size
;
347 macx_triggers(local_hi_water
, limits
[cur_limits
].low_water
, notifications
, alert_port
);
349 if(flags
& LO_WAT_ALERT
) {
350 sprintf(subfile
, "%s%d", fileroot
, file_count
);
351 if(hi_water
< notify_high
) {
352 local_hi_water
= notify_high
;
354 local_hi_water
= hi_water
;
356 if((bs_recovery
!= 0) && (notify_port
!= MACH_PORT_NULL
)) {
357 backing_store_alert(notify_port
, LO_WAT_ALERT
);
358 mach_port_deallocate(mach_task_self(), notify_port
);
359 notify_port
= MACH_PORT_NULL
;
363 if((error
= macx_swapoff(subfile
, flags
)) == 0) {
368 if (file_count
> max_valid
)
369 cur_limits
= max_valid
;
371 cur_limits
= file_count
;
373 if (file_count
> max_valid
)
374 cur_limits
= max_valid
;
376 cur_limits
= file_count
;
379 * only need to reset the LO_WAT_ALERT... the HI_WAT size is fixed,
380 * it doesn't change even if the swap file size shrinks or grows
382 macx_triggers(local_hi_water
, limits
[cur_limits
].low_water
, LO_WAT_ALERT
, alert_port
);
388 wait_on_paging_trigger(trigger_port
)
389 mach_port_t trigger_port
;
391 kern_return_t result
;
392 result
= server_alert_loop(4096, trigger_port
, MACH_MSG_OPTION_NONE
);
393 if (result
!= KERN_SUCCESS
) {
394 fprintf(stderr
, "dynamic_pager: default pager alert failed\n");
401 paging_setup(flags
, size
, priority
, low
, high
, encrypted
)
409 off_t filesize
= size
;
415 sprintf(subfile
, "%s%d", fileroot
, file_count
);
416 file_ptr
= fopen(subfile
, "w+");
417 if (file_ptr
== NULL
) {
418 fprintf(stderr
, "dynamic_pager: cannot create paging file %s!\n",
422 fchmod(fileno(file_ptr
), (mode_t
)01600);
424 error
= fcntl(fileno(file_ptr
), F_SETSIZE
, &filesize
);
426 error
= ftruncate(fileno(file_ptr
), filesize
);
431 fprintf(stderr
, "dynamic_pager: cannot extend paging file size %s to %llu!\n",
436 if (macx_triggers(0, 0,
440 MACH_PORT_NULL
) != 0) {
442 "dynamic_pager: warning: "
443 "could not turn encrypted swap %s\n",
444 (encrypted
? "on" : "off"));
447 macx_swapon(subfile
, flags
, size
, priority
);
450 mach_msg_type_name_t poly
;
452 if (mach_port_allocate(mach_task_self(),
453 MACH_PORT_RIGHT_RECEIVE
,
454 &trigger_port
) != KERN_SUCCESS
) {
455 fprintf(stderr
,"dynamic_pager: allocation of trigger port failed\n");
458 /* create a send right on our local port */
459 mach_port_extract_right(mach_task_self(), trigger_port
,
460 MACH_MSG_TYPE_MAKE_SEND
, &trigger_port
, &poly
);
461 macx_triggers(high
, low
, HI_WAT_ALERT
, trigger_port
);
464 macx_triggers(high
, low
, LO_WAT_ALERT
, trigger_port
);
466 /* register control port for applications wishing to */
467 /* get backing store notifications or change dynamic */
468 /* pager settings. */
469 set_dp_control_port(mach_host_self(), trigger_port
);
470 wait_on_paging_trigger(trigger_port
);
476 clean_swap_directory(const char *path
)
479 struct dirent
*entry
;
484 fprintf(stderr
,"dynamic_pager: cannot open swap directory %s\n", path
);
488 while ((entry
= readdir(dir
)) != NULL
) {
489 if (entry
->d_namlen
>= 4 && strncmp(entry
->d_name
, "swap", 4) == 0) {
490 snprintf(buf
, sizeof buf
, "%s/%s", path
, entry
->d_name
);
498 #define VM_PREFS_PLIST "/Library/Preferences/com.apple.virtualMemory.plist"
499 #define VM_PREFS_ENCRYPT_SWAP_KEY "UseEncryptedSwap"
502 should_encrypt_swap(void)
504 CFPropertyListRef propertyList
;
505 CFTypeID propertyListType
;
506 CFStringRef errorString
;
507 CFDataRef resourceData
;
511 boolean_t should_encrypt
;
512 boolean_t explicit_value
;
515 explicit_value
= false;
517 fileURL
= CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault
, (const UInt8
*)VM_PREFS_PLIST
, strlen(VM_PREFS_PLIST
), false);
518 if (fileURL
== NULL
) {
519 /*fprintf(stderr, "%s: CFURLCreateFromFileSystemRepresentation(%s) failed\n", getprogname(), VM_PREFS_PLIST);*/
523 if (!CFURLCreateDataAndPropertiesFromResource(kCFAllocatorDefault
, fileURL
, &resourceData
, NULL
, NULL
, &errorCode
)) {
524 /*fprintf(stderr, "%s: CFURLCreateDataAndPropertiesFromResource(%s) failed: %d\n", getprogname(), VM_PREFS_PLIST, (int)errorCode);*/
530 propertyList
= CFPropertyListCreateFromXMLData(kCFAllocatorDefault
, resourceData
, kCFPropertyListMutableContainers
, &errorString
);
531 if (propertyList
== NULL
) {
532 /*fprintf(stderr, "%s: cannot get XML propertyList %s\n", getprogname(), VM_PREFS_PLIST);*/
533 CFRelease(resourceData
);
537 propertyListType
= CFGetTypeID(propertyList
);
539 if (propertyListType
== CFDictionaryGetTypeID()) {
540 value
= (CFTypeRef
) CFDictionaryGetValue((CFDictionaryRef
) propertyList
, CFSTR(VM_PREFS_ENCRYPT_SWAP_KEY
));
542 /* no value: use the default value */
543 } else if (CFGetTypeID(value
) != CFBooleanGetTypeID()) {
544 fprintf(stderr
, "%s: wrong type for key \"%s\"\n",
545 getprogname(), VM_PREFS_ENCRYPT_SWAP_KEY
);
546 /* bogus value, assume it's "true" for safety's sake */
547 should_encrypt
= true;
548 explicit_value
= true;
550 should_encrypt
= CFBooleanGetValue((CFBooleanRef
)value
);
551 explicit_value
= true;
555 /*fprintf(stderr, "%s: invalid propertyList type %d (not a dictionary)\n", getprogname(), propertyListType);*/
557 CFRelease(resourceData
);
558 CFRelease(propertyList
);
561 if (! explicit_value
) {
562 /* by default, encrypt swap on laptops only */
566 /* wait up to 60 seconds for IOKit to quiesce */
569 kr
= IOKitWaitQuiet(kIOMasterPortDefault
, &w
);
570 if (kr
!= kIOReturnSuccess
) {
572 * Can't tell if we're on a laptop,
573 * assume we do want encrypted swap.
575 should_encrypt
= TRUE
;
576 /*fprintf(stderr, "dynamic_pager: IOKitWaitQuiet ret 0x%x (%s)\n", kr, mach_error_string(kr));*/
579 * Look for battery power source.
581 snap
= IOPSCopyPowerSourcesInfo();
582 should_encrypt
= (kCFBooleanTrue
== IOPSPowerSourceSupported(snap
, CFSTR(kIOPMBatteryPowerKey
)));
584 /*fprintf(stderr, "dynamic_pager: battery power source: %d\n", should_encrypt);*/
588 return should_encrypt
;
592 main(int argc
, char **argv
)
596 char default_filename
[] = "/private/var/vm/swapfile";
598 int variable_sized
= 1;
599 boolean_t encrypted_swap
;
607 strcpy(fileroot
, default_filename
);
609 limits
[0].size
= 20000000;
610 limits
[0].low_water
= 0;
615 encrypted_swap
= should_encrypt_swap();
617 while ((ch
= getopt(argc
, argv
, "EF:L:H:S:P:QO:")) != EOF
) {
621 encrypted_swap
= TRUE
;
625 strncpy(fileroot
, optarg
, 500);
630 limits
[0].low_water
= atoi(optarg
);
634 hi_water
= atoi(optarg
);
638 limits
[0].size
= atoi(optarg
);
641 priority
= atoi(optarg
);
645 /* just query for "encrypted swap" default value */
647 "dynamic_pager: encrypted swap will be %s\n",
648 encrypted_swap
? "ON": "OFF");
652 (void)fprintf(stderr
,
653 "usage: dynamic_pager [-F filename] [-L low water alert trigger] [-H high water alert trigger] [-S file size] [-P priority]\n");
658 if (variable_sized
) {
659 static char tmp
[1024];
667 u_int64_t fs_limit
= 0;
670 * if we get here, then none of the following options were specified... -L, H, or -S
671 * drop into a new mode that scales the size of the swap file based on how much free
672 * space is left on the volume being used for swap and the amount of physical ram
673 * installed on the system...
674 * basically, we'll pick a maximum size that doesn't exceed the following limits...
675 * 1/8 the remaining free space of the swap volume
676 * the size of phsyical ram
677 * MAXIMUM_SIZE - currently set to 1 Gbyte...
678 * once we have the maximum, we'll create a list of sizes and low_water limits
679 * we'll start with 2 files of MINIMUM_SIZE - currently 64 Mbytes...
680 * subsequent entries will double in size up to the calculated maximum... the low_water
681 * limit will be the sum of the current file size and the previous file size for each entry...
682 * as we add or delete files, we'll use the current file_count as an index into this
683 * table... if it's beyond the table size, we'll use the last entry
684 * the table entry will determine the size of the file to be created and the new low_water mark...
685 * the high_water mark is set to HI_WATER_DEFAULT which must be smaller than MINIMUM_SIZE...
686 * currently it is set to 40,000,000 to match the size being requested by the application
687 * monitoring low space conditions... having it set to the same size keeps us from creating
688 * an additional swap file when it really isn't necessary
692 * get rid of the filename at the end of the swap file specification
693 * we only want the portion of the pathname that should already exist
695 strcpy(tmp
, fileroot
);
696 if (q
= strrchr(tmp
, '/'))
699 if (statfs(tmp
, &sfs
) == -1) {
701 * Setup the swap directory.
703 if (mkdir(tmp
, 0755) == -1) {
704 (void)fprintf(stderr
, "dynamic_pager: cannot create swap directory %s\n", tmp
);
709 if (statfs(tmp
, &sfs
) == -1) {
711 * We really can't get filesystem status,
712 * so let's not limit the swap files...
714 fs_limit
= (u_int64_t
) -1;
719 * Remove all files in the swap directory.
721 clean_swap_directory(tmp
);
723 if (fs_limit
!= (u_int64_t
) -1) {
725 * Limit the maximum size of a swap file to 1/8 the free
726 * space available on the filesystem where the swap files
727 * are to reside. This will allow us to allocate and
728 * deallocate in finer increments on systems without much
731 fs_limit
= ((u_int64_t
)sfs
.f_bfree
* (u_int64_t
)sfs
.f_bsize
) / 8;
736 len
= sizeof(u_int64_t
);
738 if (sysctl(mib
, 2, &memsize
, &len
, NULL
, 0) < 0) {
740 * if the sysctl fails for some reason
741 * use the starting size as the default
743 memsize
= MINIMUM_SIZE
;
745 if (memsize
> fs_limit
)
747 * clip based on filesystem space available
752 * further limit the maximum size of a swap file
754 if (memsize
> MAXIMUM_SIZE
)
755 memsize
= MAXIMUM_SIZE
;
760 * start small and work our way up to the maximum
761 * sized allowed... this way, we don't tie up too
762 * much disk space if we never do any real paging
764 for (max_valid
= 0, i
= 0; i
< MAX_LIMITS
; i
++) {
765 limits
[i
].size
= size
;
768 limits
[i
].low_water
= size
* 2;
770 if ((limits
[i
- 1].size
/ 2) > HI_WATER_DEFAULT
)
771 limits
[i
].low_water
= size
+ (limits
[i
- 1].size
/ 2);
773 limits
[i
].low_water
= size
+ limits
[i
- 1].size
;
778 * make the first 2 files the same size
786 if (max_valid
>= MAX_LIMITS
)
787 max_valid
= MAX_LIMITS
- 1;
789 hi_water
= HI_WATER_DEFAULT
;
791 local_hi_water
= hi_water
;
793 if((limits
[0].low_water
!= 0) && (limits
[0].low_water
<= (limits
[0].size
+ hi_water
))) {
794 (void)fprintf(stderr
, "dynamic_pager: low water trigger must be larger than size + hi_water\n");
800 paging_setup(0, limits
[0].size
, priority
, limits
[0].low_water
, hi_water
,