Sign in Join
Wiki copytemporary

Accessing macOS System Information: what changed

Revision 1 to 2. Removed lines have a minus sign; added lines have a plus sign.

OldNewLine
11## Overview
22
33macOS provides four main ways to access system information:
44
55- Sysctl interface - An interface used by Unix and Unix-like operating systems to access system hardware and kernel configuration attributes.
66- NSProcessInfo class - A class in the Foundation framework which gives access to a collection of information about the current process.
77- Gestalt Manager - Allows applications to query the running system configuration. **Deprecated** after 10.7 (Lion) in favour of the NSProcessInfo class or the SysCtl interface.
88- IOKit framework - A framework that implements non-kernel access to I/O Kit objects (drivers and nubs) through the device-interface mechanism.
99
1010These are described below with code examples.
1111
1212## Sysctl
1313
1414Sysctl provides an interface that allows you to read and, with appropriate privileges, set many kernel attributes in macOS (and Linux and the BSDs). This provides a wealth of information detailing system hardware and configuration attributes which can be useful when debugging or simply optimising your application (eg to take advantage of threads on multi-core CPU systems).
1515
1616The '/usr/local/share/fpcsrc/rtl/darwin/sysctlh.inc' source file is a partial translation of the macOS '/usr/include/sys/sysctl.h' file. For details of the sysctls available, start a Terminal and type 'man sysctl'. Typing 'sysctl-a \| more' in a Terminal will list all the sysctls.
1717
1818### FPsysctl()
1919
2020> **Note:** The function definition below for FPsysctl() is for FPC 3.2.0 and later - For FPC 3.0.4 and earlier the first argument is *pchar* and not *pcint*. Adjust the code examples accordingly.
2121
2222The fpSysCtl() function allows you to retrieve system information. The data returned from the function comprises integers (integer and int64), strings (AnsiStrings) and C structures (records). The function is defined in `/usr/local/share/fpcsrc/rtl/bsd/sysctl.pp` as:
2323
2424``` pascal
2525function FPsysctl (Name: pcint; namelen:cuint; oldp:pointer;oldlenp:psize_t; newp:pointer;newlen:size_t):cint; cdecl; external name 'sysctl';
2626```
2727
2828*name* -- a pointer to a Management Information Base (MIB) array of integers. Each element of this array must contain the correct value to read or write the particular system attribute.
2929
3030*namelen* -- the length of the array of integers in name.
3131
3232Note: if the old attribute value is not required, the next two variables can be set to Nil and 0.
3333
3434*oldp* -- a pointer to the buffer into which the value is copied.
3535
3636*oldlenp* -- the size of the oldp buffer.
3737
3838Note: if the new attribute value is not being set, the next two variables should be set to Nil and 0.
3939
4040*newp* -- a pointer to the buffer containing the new value to be set.
4141
4242*newlen* -- the size of the newp buffer.
4343
4444The size of the MIB array depends on the data to be read or written. The first element indicates the level of the information and the subsequent elements indicate the value to read. The top level names are:
4545
46-`          --------------------------------------------------------------`\
47-`          Name              Next level names          Description`\
48-`                            are found in`\
49-`          --------------------------------------------------------------`\
50-`          CTL_DEBUG         sys/sysctl.h              Debugging`\
51-`          CTL_VFS           sys/mount.h               File system`\
52-`          CTL_HW            sys/sysctl.h              Generic CPU, I/O`\
53-`          CTL_KERN          sys/sysctl.h              High kernel limits`\
54-`          CTL_MACHDEP       sys/sysctl.h              Machine dependent`\
55-`          CTL_NET           sys/socket.h              Networking`\
56-`          CTL_USER          sys/sysctl.h              User-level`\
57-`          CTL_VM            sys/resources.h           Virtual memory`\
58-`          --------------------------------------------------------------`
46+```
47+ --------------------------------------------------------------
48+ Name Next level names Description
49+ are found in
50+ --------------------------------------------------------------
51+ CTL_DEBUG sys/sysctl.h Debugging
52+ CTL_VFS sys/mount.h File system
53+ CTL_HW sys/sysctl.h Generic CPU, I/O
54+ CTL_KERN sys/sysctl.h High kernel limits
55+ CTL_MACHDEP sys/sysctl.h Machine dependent
56+ CTL_NET sys/socket.h Networking
57+ CTL_USER sys/sysctl.h User-level
58+ CTL_VM sys/resources.h Virtual memory
59+ --------------------------------------------------------------
60+```
5961
6062Let's look at CTL_HW. The subsequent elements are found in the system sysctl.h file and are:
6163
62-`          -------------------------------------------------------------------------------          `\
63-`          Name                     Value          Description`\
64-`          -------------------------------------------------------------------------------          `\
65-`          #define HW_MACHINE       1              /* string: machine class */`\
66-`          #define HW_MODEL         2              /* string: specific machine model */`\
67-`          #define HW_NCPU          3              /* int: number of cpus */`\
68-`          #define HW_BYTEORDER     4              /* int: machine byte order */`\
69-`          #define HW_PHYSMEM       5              /* int: total memory */`\
70-`          #define HW_USERMEM       6              /* int: non-kernel memory */`\
71-`          #define HW_PAGESIZE      7              /* int: software page size */`\
72-`          #define HW_DISKNAMES     8              /* strings: disk drive names */`\
73-`          #define HW_DISKSTATS     9              /* struct: diskstats[] */`\
74-`          #define HW_EPOCH        10              /* int: 0 for Legacy, else NewWorld */`\
75-`          #define HW_FLOATINGPT   11              /* int: has HW floating point? */`\
76-`          #define HW_MACHINE_ARCH 12              /* string: machine architecture */`\
77-`          #define HW_VECTORUNIT   13              /* int: has HW vector unit? */`\
78-`          #define HW_BUS_FREQ     14              /* int: Bus Frequency */`\
79-`          #define HW_CPU_FREQ     15              /* int: CPU Frequency */`\
80-`          #define HW_CACHELINE    16              /* int: Cache Line Size in Bytes */`\
81-`          #define HW_L1ICACHESIZE 17              /* int: L1 I Cache Size in Bytes */`\
82-`          #define HW_L1DCACHESIZE 18              /* int: L1 D Cache Size in Bytes */`\
83-`          #define HW_L2SETTINGS   19              /* int: L2 Cache Settings */`\
84-`          #define HW_L2CACHESIZE  20              /* int: L2 Cache Size in Bytes */`\
85-`          #define HW_L3SETTINGS   21              /* int: L3 Cache Settings */`\
86-`          #define HW_L3CACHESIZE  22              /* int: L3 Cache Size in Bytes */`\
87-`          #define HW_TB_FREQ      23              /* int: Bus Frequency */`\
88-`          #define HW_MEMSIZE      24              /* uint64_t: physical ram size */`\
89-`          #define HW_AVAILCPU     25              /* int: number of available CPUs */`\
90-`          #define HW_MAXID        26              /* number of valid hw ids */`\
91-`          -------------------------------------------------------------------------------`
64+``` pascal
65+ -------------------------------------------------------------------------------
66+ Name Value Description
67+ -------------------------------------------------------------------------------
68+ #define HW_MACHINE 1 /* string: machine class */
69+ #define HW_MODEL 2 /* string: specific machine model */
70+ #define HW_NCPU 3 /* int: number of cpus */
71+ #define HW_BYTEORDER 4 /* int: machine byte order */
72+ #define HW_PHYSMEM 5 /* int: total memory */
73+ #define HW_USERMEM 6 /* int: non-kernel memory */
74+ #define HW_PAGESIZE 7 /* int: software page size */
75+ #define HW_DISKNAMES 8 /* strings: disk drive names */
76+ #define HW_DISKSTATS 9 /* struct: diskstats[] */
77+ #define HW_EPOCH 10 /* int: 0 for Legacy, else NewWorld */
78+ #define HW_FLOATINGPT 11 /* int: has HW floating point? */
79+ #define HW_MACHINE_ARCH 12 /* string: machine architecture */
80+ #define HW_VECTORUNIT 13 /* int: has HW vector unit? */
81+ #define HW_BUS_FREQ 14 /* int: Bus Frequency */
82+ #define HW_CPU_FREQ 15 /* int: CPU Frequency */
83+ #define HW_CACHELINE 16 /* int: Cache Line Size in Bytes */
84+ #define HW_L1ICACHESIZE 17 /* int: L1 I Cache Size in Bytes */
85+ #define HW_L1DCACHESIZE 18 /* int: L1 D Cache Size in Bytes */
86+ #define HW_L2SETTINGS 19 /* int: L2 Cache Settings */
87+ #define HW_L2CACHESIZE 20 /* int: L2 Cache Size in Bytes */
88+ #define HW_L3SETTINGS 21 /* int: L3 Cache Settings */
89+ #define HW_L3CACHESIZE 22 /* int: L3 Cache Size in Bytes */
90+ #define HW_TB_FREQ 23 /* int: Bus Frequency */
91+ #define HW_MEMSIZE 24 /* uint64_t: physical ram size */
92+ #define HW_AVAILCPU 25 /* int: number of available CPUs */
93+ #define HW_MAXID 26 /* number of valid hw ids */
94+ -------------------------------------------------------------------------------
95+```
9296
9397Armed with this information, we can now find out the number of CPUs:
9498
9599``` pascal
96100// The full source code for this example is available from:
97101// https://sourceforge.net/p/lazarus-wiki-projects/code/ci/master/tree/fpc_sysctls/num_cpu.pas
98102...
99103Uses
100104 SysUtils, SysCtl, Unix;
101105...
102106function NumberOfCPU: Integer;
103107
104108var
105109 mib: array[0..1] of Integer;
106110 status : Integer;
107111 len : size_t;
108112
109113begin
110114 mib[0] := CTL_HW;
111115 mib[1] := HW_NCPU;
112116
113117 len := SizeOf(Result);
114118 status := fpSysCtl(PCInt(@mib), Length(mib), @Result, @len, Nil, 0);
115119 if status <> 0 then RaiseLastOSError;
116120end;
117121```
118122
119123The above code returns the number of CPUs in an integer; you need to check the C header file to determine what is being returned (an integer, int64, string, struct/record).
120124
121125There is a possible issue with the above because if you have, for example, an Intel CPU with hyperthreading. One guess. Yes, it will return the total number of threads and not the number of CPUs or even the number of physical cores. HW_NCPU is pretty much deprecated these days as a useful attribute. Instead, we can retrieve the number of CPU packages, physical CPUs (cores) or logical CPUs (the same as HW_NCPU). To do so requires the use of a second function 'FPsysctlbyname'.
122126
123127However, before we move on, let's look at retrieving a string with FPsysctl() which is a little different than retrieving an integer.
124128
125129``` pascal
126130// The full source code for this example is available from:
127131// https://sourceforge.net/p/lazarus-wiki-projects/code/ci/master/tree/fpc_sysctls/hardware_model.pas
128132...
129133Uses
130134 SysUtils, SysCtl, Unix;
131135...
132136function HWmodel : AnsiString;
133137
134138var
135139 mib : array[0..1] of Integer;
136140 status : Integer;
137141 len : size_t;
138142 p : PChar;
139143
140144begin
141145 mib[0] := CTL_HW;
142146 mib[1] := HW_MODEL;
143147
144148 status := fpSysCtl(PCInt(@mib), Length(mib), Nil, @len, Nil, 0);
145149 if status <> 0 then RaiseLastOSError;
146150
147151 GetMem(p, len);
148152
149153 try
150154 status := fpSysCtl(PCInt(@mib), Length(mib), p, @len, Nil, 0);
151155 if status <> 0 then RaiseLastOSError;
152156 Result := p;
153157 finally
154158 FreeMem(p);
155159 end;
156160end;
157161```
158162
159163Notice that this time we needed two calls to FPsysctl() - one to find out the size of the buffer required to hold the string value, and the second to actually retrieve the string value into that buffer. On my Apple computer this returns the string "Macmini8,1" - the 2018 Mac mini.
160164
161165No, we're not quite ready to move on yet... we still need to see how to retrieve information in a C structure (Pascal record).
162166
163167``` pascal
164168// The full source code for this example is available from:
165169// https://sourceforge.net/p/lazarus-wiki-projects/code/ci/master/tree/fpc_sysctls/swap_usage.pas
166170...
167171Uses
168172 SysUtils, SysCtl, Unix;
169173...
170174function SwapUsage: String;
171175
172176type
173177 swapinfo = record
174178 xsu_total : Int64;
175179 xsu_avail : Int64;
176180 xsu_used : Int64;
177181 xsu_pagesize : Integer;
178182 xsu_encrypted: Boolean;
179183end;
180184
181185const
182186 VM_SWAPUSAGE = 5;
183187
184188var
185189 mib : array[0..1] of Integer;
186190 status : Integer;
187191 len : size_t;
188192 swap : swapinfo;
189193 SwapEncrypted: String;
190194
191195begin
192196 mib[0] := CTL_VM;
193197 mib[1] := VM_SWAPUSAGE;
194198 swap := Default(swapinfo);
195199
196200 FillChar(swap, sizeof(swap), 0);
197201 len := sizeof(swap);
198202
199203 status := fpSysCtl(PCInt(@mib), Length(mib), @swap, @len, Nil, 0);
200204 if status <> 0 then RaiseLastOSError;
201205
202206 if(swap.xsu_encrypted = true) then
203207 SwapEncrypted := 'Yes' else SwapEncrypted := 'No';
204208
205209 Result := 'Swap total: ' + FloatToStr(Round(swap.xsu_total /1024 /1024)) + ' MB'
206210 + LineEnding + 'Swap used: ' + FloatToStr(Round(swap.xsu_used /1024 /1024)) + ' MB'
207211 + LineEnding + 'Swap free: ' + FloatToStr(Round(swap.xsu_avail /1024 /1024)) + ' MB'
208212 + LineEnding + 'Swap page size: ' + IntToStr(swap.xsu_pagesize) + ' bytes'
209213 + LineEnding + 'Swap encrypted: ' + SwapEncrypted + LineEnding;
210214end;
211215```
212216
213217> **Note:** If your computer has not used any swap, then the results for Swap total, used and free will all be zero.
214218
215219On my computer this returned:
216220
217-` Swap total: 1024 MB`\
218-` Swap used: 191 MB`\
219-` Swap free: 833 MB`\
220-` Swap page size: 4096 bytes`\
221-` Swap encrypted: Yes`
221+```
222+ Swap total: 1024 MB
223+ Swap used: 191 MB
224+ Swap free: 833 MB
225+ Swap page size: 4096 bytes
226+ Swap encrypted: Yes
227+```
222228
223229The swapinfo record was pretty easily translated from the C structure found in the system `sysctl.h` file:
224230
225231``` c
226232struct xsw_usage {
227233 u_int64_t xsu_total;
228234 u_int64_t xsu_avail;
229235 u_int64_t xsu_used;
230236 u_int32_t xsu_pagesize;
231237 boolean_t xsu_encrypted;
232238};
233239```
234240
235241We also defined the MIB constant VM_SWAPUSAGE by looking at the system `sysctl.h` file. Why? Because Free Pascal 3.2.0 and earlier are missing this identifier (fixed in FPC 3.2.2). So, we looked it up and found:
236242
237243``` c
238244#define VM_SWAPUSAGE 5 /* total swap usage */
239245```
240246
241247Finally, yes, it is time to move on to the FPsysctlbyname() function.
242248
243249### FPsysctlbyname
244250
245251The FPsysctlbyname function is defined in '/usr/local/share/fpcsrc/rtl/bsd/sysctl.pp' as:
246252
247253``` pascal
248254function FPsysctlbyname (Name: pchar; oldp:pointer;oldlenp:psize_t; newp:pointer;newlen:size_t):cint; cdecl; external name 'sysctlbyname';
249255```
250256
251257There. Not really very different from the FPsysctl() function. Is that a sigh of relief? So let's look at finally retrieving the number of physical CPU cores.
252258
253259``` pascal
254260// Full source code for this example is available from:
255261// https://sourceforge.net/p/lazarus-wiki-projects/code/ci/master/tree/fpc_sysctls/num_cores.pas
256262...
257263{$mode objfpc}
258264...
259265
260266Uses
261267 SysCtl, SysUtils, Unix;
262268
263269function NumberOfCores: Integer;
264270
265271var
266272 status : Integer;
267273 len : size_t;
268274
269275begin
270276 len := SizeOf(Result);
271277
272278 status := fpSysCtlByName('hw.physicalcpu', @Result, @len, Nil, 0);
273279 if status <> 0 then RaiseLastOSError;
274280end;
275281```
276282
277283On my 2018 Mac mini this returns the correct number of cores: 6. Where do you find the special incantation **hw.physicalcpu** ? Yes, in the system `sysctl.h` file. Here's a brief excerpt of some of the hardware selectors:
278284
279-` These are the support HW selectors for sysctlbyname.  Parameters that are byte counts or frequencies are 64 bit numbers.`\
280-` All other parameters are 32 bit numbers.`\
281-` `\
282-` hw.memsize                - The number of bytes of physical memory in the system.`\
283-` hw.ncpu                   - The maximum number of processors that could be available this boot.`\
284-`                             Use this value for sizing of static per processor arrays; i.e. processor load statistics.`\
285-` hw.activecpu              - The number of processors currently available for executing threads.`\
286-`                             Use this number to determine the number threads to create in SMP aware applications.`\
287-`                             This number can change when power management modes are changed.`\
288-` hw.physicalcpu            - The number of physical processors available in the current power management mode.`\
289-` hw.physicalcpu_max        - The maximum number of physical processors that could be available this boot`\
290-` hw.logicalcpu             - The number of logical processors available in the current power management mode.`\
291-` hw.logicalcpu_max         - The maximum number of logical processors that could be available this boot`\
292-` hw.tbfrequency            - This gives the time base frequency used by the OS and is the basis of all timing services.`\
293-`                             In general is is better to use mach's or higher level timing services, but this value`\
294-`                             is needed to convert the PPC Time Base registers to real time.`\
295-` hw.cpufrequency           - These values provide the current, min and max cpu frequency.  The min and max are for`\
296-` hw.cpufrequency_max       - all power management modes.  The current frequency is the max frequency in the current mode.`\
297-` hw.cpufrequency_min       - All frequencies are in Hz.`\
298-` hw.busfrequency           - These values provide the current, min and max bus frequency.  The min and max are for`\
299-` hw.busfrequency_max       - all power management modes.  The current frequency is the max frequency in the current mode.`\
300-` hw.busfrequency_min       - All frequencies are in Hz.`\
301-` hw.cputype                - These values provide the mach-o cpu type and subtype.  A complete list is in <mach/machine.h>`\
302-` hw.cpusubtype             - These values should be used to determine what processor family the running cpu is from so that`\
303-`                             the best binary can be chosen, or the best dynamic code generated.  They should not be used`\
304-`                             to determine if a given processor feature is available.`\
305-` hw.cputhreadtype          - This value will be present if the processor supports threads.  Like hw.cpusubtype this selector`\
306-`                             should not be used to infer features, and only used to name the processors thread architecture.`\
307-`                             The values are defined in <mach/machine.h>`\
308-` hw.byteorder              - Gives the byte order of the processor.  4321 for big endian, 1234 for little.`\
309-` hw.pagesize               - Gives the size in bytes of the pages used by the processor and VM system.`\
310-` hw.cachelinesize          - Gives the size in bytes of the processor's cache lines.`\
311-`                             This value should be use to control the strides of loops that use cache control instructions`\
312-`                             like dcbz, dcbt or dcbst.`\
313-` hw.l1dcachesize           - These values provide the size in bytes of the L1, L2 and L3 caches.  If a cache is not present`\
314-` hw.l1icachesize           - then the selector will return an error.`\
315-` hw.l2cachesize            -`\
316-` hw.l3cachesize            -`\
317-` hw.packages               - Gives the number of processor packages.`
285+``` pascal
286+ These are the support HW selectors for sysctlbyname. Parameters that are byte counts or frequencies are 64 bit numbers.
287+ All other parameters are 32 bit numbers.
288+
289+ hw.memsize - The number of bytes of physical memory in the system.
290+ hw.ncpu - The maximum number of processors that could be available this boot.
291+ Use this value for sizing of static per processor arrays; i.e. processor load statistics.
292+ hw.activecpu - The number of processors currently available for executing threads.
293+ Use this number to determine the number threads to create in SMP aware applications.
294+ This number can change when power management modes are changed.
295+ hw.physicalcpu - The number of physical processors available in the current power management mode.
296+ hw.physicalcpu_max - The maximum number of physical processors that could be available this boot
297+ hw.logicalcpu - The number of logical processors available in the current power management mode.
298+ hw.logicalcpu_max - The maximum number of logical processors that could be available this boot
299+ hw.tbfrequency - This gives the time base frequency used by the OS and is the basis of all timing services.
300+ In general is is better to use mach's or higher level timing services, but this value
301+ is needed to convert the PPC Time Base registers to real time.
302+ hw.cpufrequency - These values provide the current, min and max cpu frequency. The min and max are for
303+ hw.cpufrequency_max - all power management modes. The current frequency is the max frequency in the current mode.
304+ hw.cpufrequency_min - All frequencies are in Hz.
305+ hw.busfrequency - These values provide the current, min and max bus frequency. The min and max are for
306+ hw.busfrequency_max - all power management modes. The current frequency is the max frequency in the current mode.
307+ hw.busfrequency_min - All frequencies are in Hz.
308+ hw.cputype - These values provide the mach-o cpu type and subtype. A complete list is in <mach/machine.h>
309+ hw.cpusubtype - These values should be used to determine what processor family the running cpu is from so that
310+ the best binary can be chosen, or the best dynamic code generated. They should not be used
311+ to determine if a given processor feature is available.
312+ hw.cputhreadtype - This value will be present if the processor supports threads. Like hw.cpusubtype this selector
313+ should not be used to infer features, and only used to name the processors thread architecture.
314+ The values are defined in <mach/machine.h>
315+ hw.byteorder - Gives the byte order of the processor. 4321 for big endian, 1234 for little.
316+ hw.pagesize - Gives the size in bytes of the pages used by the processor and VM system.
317+ hw.cachelinesize - Gives the size in bytes of the processor's cache lines.
318+ This value should be use to control the strides of loops that use cache control instructions
319+ like dcbz, dcbt or dcbst.
320+ hw.l1dcachesize - These values provide the size in bytes of the L1, L2 and L3 caches. If a cache is not present
321+ hw.l1icachesize - then the selector will return an error.
322+ hw.l2cachesize -
323+ hw.l3cachesize -
324+ hw.packages - Gives the number of processor packages.
325+```
318326
319327If you prefer, you can instead use the `sysctl` command line utility in a Terminal to list all the hw selectors with the `sysctl hw` command.
320328
321329Retrieving a string value, rather than an integer, is again reminiscent of FPsysctl(). Two calls to FPsysctlbyname() are needed: (1) to find out the size of the buffer to hold the string to be returned and (2) to return the string value into that buffer. Don't forget to allocate memory for the buffer before the second call!
322330
323331``` pascal
324332// Full source code for this example is available from:
325333// https://sourceforge.net/p/lazarus-wiki-projects/code/ci/master/tree/fpc_sysctls/brand_cpu.pas
326334...
327335{$mode objfpc}
328336...
329337
330338Uses
331339 SysCtl, SysUtils, Unix;
332340
333341function BrandOfCPU: AnsiString;
334342
335343var
336344 status : Integer;
337345 len : size_t;
338346 p : PChar;
339347
340348begin
341349 status := fpSysCtlByName('machdep.cpu.brand_string', Nil, @len, Nil, 0);
342350 if status <> 0 then RaiseLastOSError;
343351
344352 GetMem(p, len);
345353
346354 try
347355 status := fpSysCtlByName('machdep.cpu.brand_string', p, @len, Nil, 0);
348356 if status <> 0 then RaiseLastOSError;
349357 Result := p;
350358 finally
351359 FreeMem(p);
352360 end;
353361end;
354362```
355363
356364On my 2018 Mac mini computer, this returns the very informative string: *Intel(R) Core(TM) i7-8700B CPU @ 3.20GHz*. On my 2020 Mac mini computer, this returns the somewhat less informative string: *Apple M1*.
357365
358366Oh, you noticed, I changed the top level identifier from **hw** to **machdep** (machine dependent). Again, consult `sysctl.h` or use the `sysctl` command line utility in a Terminal.
359367
360368``` pascal
361369// Full source code for this example is available from:
362370// https://sourceforge.net/p/lazarus-wiki-projects/code/ci/master/tree/fpc_sysctls/os_version.pas
363371
364372// Note: The kern.osproductversion attribute was added to the Darwin kernel in December 2018
365373// https://github.com/apple/darwin-xnu/commit/5bbb823c13f3ab1ab58878f96b35433a29882676
366374
367375Program os_version;
368376
369377{$mode objfpc}
370378
371379Uses
372380 SysCtl, SysUtils, Unix;
373381
374382function OS_version : AnsiString;
375383
376384var
377385 status : Integer;
378386 len : size_t;
379387 p : PChar;
380388
381389begin
382390 status := fpSysCtlByName('kern.osproductversion', Nil, @len, Nil, 0);
383391 if status <> 0 then RaiseLastOSError;
384392
385393 GetMem(p, len);
386394
387395 try
388396 status := fpSysCtlByName('kern.osproductversion', p, @len, Nil, 0);
389397 if status <> 0 then RaiseLastOSError;
390398 Result := p;
391399 finally
392400 FreeMem(p);
393401 end;
394402end;
395403
396404begin
397405 WriteLn(OS_version);
398406end.
399407```
400408
401409The above FPC example program will return the operating system version. On my M1 processor Mac mini this was "11.2.3" (Big Sur).
402410
403411### Last words
404412
405413For the optimised, performance fiends out there, the man page for these functions notes that the FPsysctl() function runs in about a third the time as the same request made via the FPsysctlbyname() function.
406414
407415It should be noted that many of the sysctl attributes are read-only, but there are also quite a few that can be changed. I've left changing a sysctl attribute as an exercise for you dear reader. It should be simple if you study the definitions of the function parameters, paying particular attention to newp and newlen.
408416
409417## NSProcessinfo
410418
411419Each macOS process has a single, shared NSProcessInfo object, known as a process information agent. This process information agent can return information such as arguments, environment variables, host name, and process name. The processInfo class method returns the shared agent for the current process—that is, the process whose object sent the message.
412420
413421### Properties
414422
415423| Property | Description |
416424|----|----|
417425| processInfo | Returns the process information agent for the process. An NSProcessInfo object is created the first time this method is invoked, and that same object is returned on each subsequent invocation. |
418426| arguments | Array of strings with the command-line arguments for the process. |
419427| environment | The variable names (keys) and their values in the environment from which the process was launched. |
420428| globallyUniqueString | Global unique identifier for the process. The global ID for the process includes the host name, process ID, and a time stamp, which ensures that the ID is unique for the network. This property generates a new string each time its getter is invoked, and it uses a counter to guarantee that strings created from the same process are unique. |
421429| processIdentifier | The identifier of the process (often called process ID or PID). |
422430| processName | The name of the process. |
423431| userName | Returns the account name of the current user. macOS 10.12+ |
424432| fullUserName | Returns the full name of the current user. macOS 10.12+ |
425433| hostName | The name of the host computer on which the process is executing. |
426434| operatingSystemVersionString | A string containing the version of the operating system on which the process is executing. macOS 10.2+ |
427435| operatingSystemVersion | The version of the operating system on which the process is executing. macOS 10.10+ |
428436| processorCount | The number of processing cores available on the computer. Equivalent to: `sysctl -n hw.ncpu`. macOS 10.5+ |
429437| activeProcessorCount | The number of active processing cores available on the computer. Equivalent to: `sysctl -n hw.logicalcpu`. macOS 10.5+ |
430438| physicalMemory | The amount of physical memory on the computer in bytes. macOS 10.5+ |
431439| systemUptime | The amount of time in seconds that the system has been awake since the last time it was restarted. macOS 10.6+ |
432440| thermalState | The current thermal state of the system: Nominal, Fair, Serious or Critical. macOS 10.10.3+ |
433441| automaticTerminationSupportEnabled | A Boolean value indicating whether the app supports automatic termination. Currently, setting this property to False has no effect. macOS 10.7+ |
434442| | |
435443
436444### Methods
437445
438446| Method | Description |
439447|----|----|
440448| isOperatingSystemAtLeastVersion | Returns a Boolean value indicating whether the version of the operating system on which the process is executing is the same or later than the given version. macOS 10.10+ |
441449
442450### Example code
443451
444452``` pascal
445453unit Unit1;
446454
447455{$mode objfpc}{$H+}
448456{$modeswitch objectivec1}
449457
450458interface
451459
452460uses
453461 Classes, SysUtils, Forms, Dialogs, StdCtrls,
454462 ctypes, // needed for cint
455463 typinfo, // needed for GetEnumName
456464 CocoaAll; // needed for NS...
457465
458466type
459467 // From HERE...
460468 NSProcessInfoThermalState = (
461469 Nominal, // within the normal range; no corrective action is required.
462470 Fair, // thermal state is slightly elevated and the fan is audible.
463471 Serious, // fan is at maximum speed; performance may be affected;
464472 // applications should reduce use of CPU, GPU, and any I/O.
465473 Critical); // thermal state severely affects performance; need immediate cooling;
466474 // applications should reduce use of CPU, GPU, and any I/O to minimum
467475 // required for user interaction; stop using peripherals such as cameras.
468476
469477 NSOperatingSystemVersion = record
470478 majorVersion: NSInteger;
471479 minorVersion: NSInteger;
472480 patchVersion: NSInteger;
473481 end;
474482
475483 NSProcessInfo = objcclass external (NSObject)
476484 private
477485 environment_: NSDictionary;
478486 arguments_: NSArray;
479487 hostName_: NSString;
480488 name: NSString;
481489 automaticTerminationOptOutCounter: NSInteger;
482490 public
483491 class function processInfo: NSProcessInfo; message 'processInfo';
484492 function environment: NSDictionary; message 'environment';
485493 function arguments: NSArray; message 'arguments';
486494 function hostName: NSString; message 'hostName';
487495 procedure setProcessName(newValue: NSString); message 'setProcessName:';
488496 function processName: NSString; message 'processName';
489497 function processIdentifier: cint; message 'processIdentifier';
490498 function globallyUniqueString: NSString; message 'globallyUniqueString';
491499 function operatingSystemVersionString: NSString; message 'operatingSystemVersionString';
492500 function userName: NSString; message 'userName';
493501 function fullUserName: NSString; message 'fullUserName';
494502 function operatingSystemVersion: NSOperatingSystemVersion; message 'operatingSystemVersion';
495503 function processorCount: NSUInteger; message 'processorCount';
496504 function activeProcessorCount: NSUInteger; message 'activeProcessorCount';
497505 function physicalMemory: culonglong; message 'physicalMemory';
498506 function isOperatingSystemAtLeastVersion (os_version: NSOperatingSystemVersion): Boolean; message 'isOperatingSystemAtLeastVersion:';
499507 function systemUptime: NSTimeInterval; message 'systemUptime';
500508 function thermalState: NSInteger; message 'thermalState';
501509 {procedure disableSuddenTermination; message 'disableSuddenTermination';
502510 procedure enableSuddenTermination; message 'enableSuddenTermination';
503511 procedure disableAutomaticTermination (reason: NSString); message 'disableAutomaticTermination:';
504512 procedure enableAutomaticTermination (reason: NSString); message 'enableAutomaticTermination:';}
505513 procedure setAutomaticTerminationSupportEnabled(newValue: Boolean); message 'setAutomaticTerminationSupportEnabled:';
506514 function automaticTerminationSupportEnabled: Boolean; message 'automaticTerminationSupportEnabled';
507515 end;
508516 // ... To HERE is ONLY NEEDED if you are using FPC 3.0.4 or earlier. FPC 3.2.0 does not need this section.
509517
510518 { TForm1 }
511519
512520 TForm1 = Class(TForm)
513521 Button1: TButton;
514522 Memo1: TMemo;
515523 procedure Button1Click(Sender: TObject);
516524 private
517525
518526 public
519527
520528 end;
521529
522530var
523531 Form1: TForm1;
524532
525533implementation
526534
527535{$R *.lfm}
528536
529537{ TForm1 }
530538
531539procedure ProcessInfo;
532540var
533541 thermalState : String;
534542 myDictionary : NSDictionary;
535543 i : Byte = 0;
536544 minOsVer : NSOperatingSystemVersion;
537545begin
538546 Form1.Memo1.Lines.Clear;
539547
540548 Form1.Memo1.Lines.Add('Processor count: '
541549 + IntToStr(NSProcessInfo.ProcessInfo.processorCount)
542550 + LineEnding + 'Active processors: '
543551 + IntToStr(NSProcessInfo.ProcessInfo.activeProcessorCount)
544552 + LineEnding + 'Physical memory: '
545553 + FloatToStr(NSProcessInfo.ProcessInfo.physicalMemory / 1024 / 1024 / 1024) + 'GB'
546554 + LinEending + 'Process name: '
547555 + NSProcessInfo.ProcessInfo.processName.UTF8String
548556 + LinEending + 'PID: '
549557 + IntToStr(NSProcessInfo.ProcessInfo.processIdentifier)
550558 + LineEnding + 'Uptime: '
551559 + FormatFloat('0.00', NSProcessInfo.ProcessInfo.systemUptime / 60 / 60 / 24) + ' days'
552560 + LineEnding + 'Operating system: '
553561 + NSProcessInfo.ProcessInfo.operatingSystemVersionString.UTF8String
554562 + LineEnding
555563 + 'Major: ' + IntToStr(NSProcessInfo.ProcessInfo.OperatingSystemVersion.majorVersion)
556564 + ' Minor: ' + IntToStr(NSProcessInfo.ProcessInfo.OperatingSystemVersion.minorVersion)
557565 + ' Patch: ' + IntToStr(NSProcessInfo.ProcessInfo.OperatingSystemVersion.patchVersion)
558566 + LineEnding + 'Hostname: '
559567 + NSProcessInfo.ProcessInfo.hostName.UTF8String
560568 + LineEnding + 'Globally unique string: '
561569 + NSProcessInfo.ProcessInfo.globallyUniqueString.UTF8String
562570 + LineEnding + 'Username: '
563571 + NSProcessInfo.ProcessInfo.userName.UTF8String
564572 + LineEnding + 'Full Username: '
565573 + NSProcessInfo.ProcessInfo.fullUserName.UTF8String
566574 + LineEnding
567575 );
568576
569577 myDictionary := NSProcessInfo.ProcessInfo.environment;
570578 for i := 0 to (myDictionary.count - 1) do
571579 begin
572580 Form1.Memo1.Lines.Add('Env Var: '
573581 + myDictionary.allKeys.objectAtIndex(i).UTF8String
574582 + ' = '
575583 + myDictionary.allValues.objectAtIndex(i).UTF8String
576584 );
577585 end;
578586
579587 ThermalState := GetEnumName(TypeInfo(NSProcessInfoThermalState), Ord(NSProcessInfo.ProcessInfo.thermalState));
580588 Form1.Memo1.Lines.Add(LineEnding + 'Thermal state: ' + ThermalState
581589 );
582590
583591 // Setup minimum OS version
584592 minOsVer.majorVersion:= 10;
585593 minOsVer.minorVersion:= 14;
586594 minOsVer.patchVersion:= 6;
587595
588596 if(NSProcessInfo.ProcessInfo.isOperatingSystemAtLeastVersion(minOSVer)) then
589597 Form1.Memo1.Lines.Add(LineEnding + 'OS version is at least 10.14.6' + LineEnding)
590598 else
591599 Form1.Memo1.Lines.Add(LineEnding + 'OS version is NOT at least 10.14.6' + LineEnding)
592600end;
593601
594602procedure TForm1.Button1Click(Sender: TObject);
595603begin
596604 processInfo;
597605end;
598606
599607end.
600608```
601609
602610## Gestalt Manager
603611
604612Despite Apple's deprecation of the Gestalt Manager in favour of the NSProcessInfo class or the SysCtl Interface, there are still occasions when you will need to use it. For example, the NSProcessInfo instance property `OperatingSystemVersion` is only available from macOS 10.10 (Yosemite) onwards according to Apple's documentation, although it seems to also work in macOS 10.9 (Mavericks).
605613
606614So what do you do if you want to check the version of the operating system across all versions back to Mac OS X 10.0 (Cheetah)? The example code below holds the solution.
607615
608616### Example: Retrieving OS version from Mac OS X 10.0+
609617
610618``` pascal
611619unit Unit1;
612620
613621{$mode objfpc}{$H+}
614622{$modeswitch objectivec2}
615623
616624interface
617625
618626uses
619627 Classes, SysUtils, Forms, Dialogs, StdCtrls, ExtCtrls,
620628 MacOSAll,
621629 CocoaAll;
622630
623631type
624632
625633 { TForm1 }
626634
627635 TForm1 = Class(TForm)
628636 Memo1: TMemo;
629637 Button1: TButton;
630638 procedure Button1Click(Sender: TObject);
631639 private
632640 public
633641
634642 end;
635643
636644var
637645 Form1: TForm1;
638646 counter: Int64;
639647
640648implementation
641649
642650{$R *.lfm}
643651
644652{ TForm1 }
645653
646654procedure ProcessInfo;
647655begin
648656 Form1.Memo1.Lines.Clear;
649657
650658 Form1.Memo1.Lines.Add('macOS version: '
651659 + IntToStr(NSProcessInfo.ProcessInfo.operatingSystemVersion.majorVersion)
652660 + '.'
653661 + IntToStr(NSProcessInfo.ProcessInfo.operatingSystemVersion.minorVersion)
654662 + '.'
655663 + IntToStr(NSProcessInfo.ProcessInfo.operatingSystemVersion.patchVersion)
656664 );
657665end;
658666
659667Procedure GestaltInfo;
660668var
661669 majorVersion: LongInt = 0;
662670 minorVersion: LongInt = 0;
663671 patchVersion: LongInt = 0;
664672begin
665673 Form1.Memo1.Lines.Clear;
666674 Form1.Memo1.Lines.Add('Trying another way...');
667675
668676 Gestalt(gestaltSystemVersionMajor, majorVersion);
669677 Gestalt(gestaltSystemVersionMinor, minorVersion);
670678 Gestalt(gestaltSystemVersionBugFix, patchVersion);
671679
672680 Form1.Memo1.Lines.Add('macOS version: ' + IntToStr(majorVersion) + '.' +
673681 IntToStr(minorVersion) + '.' + IntToStr(patchVersion));
674682end;
675683
676684procedure TForm1.Button1Click(Sender: TObject);
677685var
678686 aSEL : SEL;
679687begin
680688 // Check whether OS version responds to this selector
681689 aSEL := objcselector('operatingSystemVersion');
682690 if NSProcessInfo.processInfo.respondsToSelector(aSEL) then
683691 ProcessInfo
684692 // Otherwise fallback to the deprecated Gestalt Manager
685693 else
686694 GestaltInfo;
687695end;
688696
689697end.
690698```
691699
692700## IOKit
693701
694702IOKit is Apple’s object-oriented framework for developing device drivers for macOS. While the main use of the IOKit framework is for developers creating device drivers that are to be resident in the kernel, it is also of use to application developers who can using an IOKit device interface to communicate with the Apple hardware.
695703
696704While the focus of this section is on accessing macOS system information, it should be noted that the IOKit framework has been deprecated by Apple in macOS 10.15 Catalina in favour of the DriverKit framework which debuted in that version of macOS. DriverKit provides a fully modernized replacement for IOKit to create device drivers. System extensions and drivers built with DriverKit run in user space, where they can't compromise the security or stability of macOS.
697705
698706### Example: Retrieving the platform serial number
699707
700708``` pascal
701709program project1;
702710
703711{$mode objfpc}{$H+}
704712{$modeswitch cvar}
705713{$linkframework IOKit}
706714
707715uses
708716 ctypes, Classes, MacOSAll;
709717{ you can add units after this };
710718
711719type
712720 kern_return_t = cint; // if you use "integer" then you don't need "ctypes" unit
713721 io_object_t = mach_port_t;
714722 io_registry_entry_t = io_object_t;
715723
716724 io_string_t = array [0..511] of ansichar;
717725 IOOptionBits = UInt32;
718726
719727const
720728 kIOPlatformSerialNumber = 'IOPlatformSerialNumber';
721729
722730var
723731 kIOMasterPortDefault:mach_port_t; cvar; external;
724732
725733function IORegistryEntryFromPath( masterPort: mach_port_t; const path: io_string_t): io_registry_entry_t; cdecl; external;
726734function IORegistryEntryCreateCFProperty(entry: io_registry_entry_t; key: CFStringRef; allocator:CFAllocatorRef; options: IOOptionBits): CFTypeRef; cdecl; external;
727735function IOObjectRelease(entry: io_registry_entry_t): kern_return_t; cdecl; external;
728736
729737function get_platform_serial_num: string;
730738var
731739 ioRegistryRoot :io_registry_entry_t;
732740 serialnumCf :CFStringRef;
733741 l :integer;
734742begin
735743 ioRegistryRoot := IORegistryEntryFromPath(kIOMasterPortDefault, 'IOService:/');
736744 serialnumCf := CFStringRef (IORegistryEntryCreateCFProperty(ioRegistryRoot, CFSTR(kIOPlatformSerialNumber), kCFAllocatorDefault, 0));
737745 IOObjectRelease(ioRegistryRoot);
738746 SetLength(Result, 32);
739747 CFStringGetCString(serialnumCf, @Result[1], length(Result), kCFStringEncodingMacRoman);
740748 CFRelease(serialnumCf);
741749 l:=StrLen(@Result[1]);
742750 SetLength(Result, l);
743751end;
744752
745753begin
746754 WriteLn('Platform serial number: ', get_platform_serial_num);
747755end.
748756```
749757
750758### Example: Retrieving the platform UUID
751759
752760A UUID (Universally Unique Identifier) is a 16-byte long number. It is normally expressed in a standard format of characters representing hexadecimal digits. UUIDs were formerly known as Globally Unique Identifiers (GUIDs), but are now officially Universally Unique Identifiers, or UUIDs, and are governed by Open Software Foundation, ISO/IEC, and IETF standards.
753761
754762``` pascal
755763program project1;
756764
757765{$mode objfpc}{$H+}
758766{$modeswitch cvar}
759767{$linkframework IOKit}
760768
761769uses
762770 ctypes, Classes, MacOSAll
763771{ you can add units after this };
764772
765773type
766774 kern_return_t = cint; // if you use "integer" then you don't need "ctypes" unit
767775 io_object_t = mach_port_t;
768776 io_registry_entry_t = io_object_t;
769777
770778 io_string_t = array [0..511] of ansichar;
771779 IOOptionBits = UInt32;
772780
773781const
774782 kIOPlatformUUIDKey = 'IOPlatformUUID';
775783
776784var
777785 kIOMasterPortDefault:mach_port_t; cvar; external;
778786
779787function IORegistryEntryFromPath( masterPort: mach_port_t; const path: io_string_t): io_registry_entry_t; cdecl; external;
780788function IORegistryEntryCreateCFProperty(entry: io_registry_entry_t; key: CFStringRef; allocator:CFAllocatorRef; options: IOOptionBits): CFTypeRef; cdecl; external;
781789function IOObjectRelease(entry: io_registry_entry_t): kern_return_t; cdecl; external;
782790
783791function get_platform_uuid: string;
784792var
785793 ioRegistryRoot :io_registry_entry_t;
786794 uuidCf :CFStringRef;
787795 l :integer;
788796begin
789797 ioRegistryRoot := IORegistryEntryFromPath(kIOMasterPortDefault, 'IOService:/');
790798 uuidCf := CFStringRef (IORegistryEntryCreateCFProperty(ioRegistryRoot, CFSTR(kIOPlatformUUIDKey), kCFAllocatorDefault, 0));
791799 IOObjectRelease(ioRegistryRoot);
792800 SetLength(Result, 1024);
793801 CFStringGetCString(uuidCf, @Result[1], length(Result), kCFStringEncodingMacRoman);
794802 CFRelease(uuidCf);
795803 l:=StrLen(@Result[1]);
796804 SetLength(Result, l);
797805end;
798806
799807begin
800808 WriteLn('Platform UUID: ', get_platform_uuid);
801809end.
802810```
803811
804812## External links
805813
806814- [Apple: SysCtl](https://developer.apple.com/documentation/installerjs/system/1812308-sysctl)
807815- [Apple: NSProcessinfo](https://developer.apple.com/documentation/foundation/nsprocessinfo)
808816- [Apple: Gestalt Manager](https://developer.apple.com/documentation/coreservices/carbon_core/gestalt_manager)
809817- [Apple: Introduction to I/O Kit Fundamentals](https://developer.apple.com/library/archive/documentation/DeviceDrivers/Conceptual/IOKitFundamentals/Introduction/Introduction.html)
810818- [Apple: IOKit framework](https://developer.apple.com/documentation/iokit)
811819- [Apple: DriverKit framework](https://developer.apple.com/documentation/driverkit)
812820- [Apple: Uniquely Identifying a Macintosh Computer](https://developer.apple.com/library/archive/technotes/tn1103/_index.html#//apple_ref/doc/uid/DTS10002943)
813821
814822---
815823*From the Free Pascal wiki, page "Accessing macOS System Information", as it stood on 2022-01-05 after a change by Trev, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*

Back to history