Some Differences between macOS and Common Unix Systems: A Technical User’s Perspective

DONG Yuxuan <https://www.dyx.name>

01 Feb 2020 (+0800)

macOS is a Unix system. However, it doesn’t conform to many conventions of common Unix systems. This text discusses these differences, not in the implementation level, but in the perspective of technical users, e.g. system administrators and application programmers.

Users and Groups

macOS manages users and groups by directory services instead of /etc/passwd and /etc/group. Consider a local network, for example, the one in your office. There are many computers in the network, and each has its own files,accounts, etc. A directory service organizes these distributed resources into one single index, and makes them look like a directory hierarchy.

For example, /Hosts/PC1 represents a host in the network, and /Users/Smith represents a user in the network.1

macOS uses the dscl command to interact with directory services.

To list all users on the system, we could run the following.

dscl . -list /Users

The first argument sets which data source we want to retrieve from. A dot means the local data source.

# List groups
dscl . -list /Groups

The -list option lets dscl print items of a resource. Items of /Users are all users. Items of /Groups are all groups.

If we want to check the information about the resource, we should use -read. For example, we could check the information of the user Smith:

# Retrieve the information of Smith
dscl . -read /Users/Smith

-read prints keys and values of the specified resource. We could also specify the keys we want.

# Get the primary group ID and default shell of Smith
dscl . -read /Users/Smith PrimaryGroupID UserShell

To add a resource, we use dscl . -create. The following command adds a new user belson.

sudo dscl . -create /Users/belson

If we try to set a password for belson using sudo passwd belson, passwd will tell us the user doesn’t exist. Because dscl just adds a record to the data source, but it doesn’t check the integrity. It doesn’t even assign a UID for belson. We must assign a UID and a primary group for belson by ourselves, after adding it to the data source.

# UID 511, GID 20 for example
sudo dscl . -create /Users/belson UniqueID 511
sudo dscl . -create /Users/belson PrimaryGroupID 20

Now, belson is a Unix user, and you can set a password for him using passwd. However, you still can’t login as belson, for we don’t assign him a shell.

sudo dscl . -create /Users/belson UserShell /bin/bash

We can login as belson now, but he doesn’t have a home directory. We must create the directory and specify it with dscl.

sudo dscl . -create /Users/belson NFSHomeDirectory /Users/belson
sudo mkdir /User/belson
sudo chmod -R belson:staff /User/belson

However, running dscl . -read /Groups/staff will show that neither the GroupMembers nor the GroupMembership attribute has any information about belson. We have specified his primary group to staff. But as mentioned before, dscl doesn’t check the integrity. Bidirectional relationships must be maintained manually.

To solve the user-group relationship problem, we could use dseditgroup. The following command adds belson to the staff group, and it takes care of the integrity.2

sudo dseditgroup -o edit -a belson -t user staff

We should make a summary here. To create a user belson in the group staff, we need:

sudo dscl . -create /Users/belson
sudo dscl . -create /Users/belson UniqueID 511
sudo dscl . -create /Users/belson PrimaryGroupID 20
sudo dscl . -create /Users/belson UserShell /bin/bash
sudo dscl . -create /Users/belson NFSHomeDirectory /Users/belson
sudo dseditgroup -o edit -a belson -t user staff
sudo mkdir /User/belson
sudo chmod -R belson:staff /User/belson

It is painful for a system administrator to type so many commands just to create a new user.

Since Mac OS X 10.10, the sysadminctl command is provided. It’s a high-level interface of dscl. Type sysadminctl in the terminal to see its usage3.

We use sysadminctl to delete belson first.

sudo sysadminctl -deleteUser belson -secure

Using dscl to check the directory service, we could find that everything we did is removed.

Now, we add belson again with sysadminctl:

sudo sysadminctl -addUser belson
sudo dseditgroup -o edit -a belson -t user staff

File System Hierarchy

If you check the root directory, you will see some traditional Unix things like /usr, /var, and /etc. You may also find mac-specified things like /Library /Applications, and /cores.

A list of mac-specified directories with their purposes is here4.

/private Contains tmp, var, and etc. /tmp, /var, /etc are symbolic links to them.
/Library Contains support files for locally installed applications, among other things.
/System Contains a subdirectory Library that holds support files for the system and system applications, among other things.
/Network Contains network-mounted Application, Library, and Users directories, as well as a Servers directory that contains directories mounted by the auto mount daemon.
/Users Contains home directories for the users on the system. The root user’s home directory is /var/root (actually /private/var/root).
/Volumes Contains all visible mounted file systems, including removable media and mounted disk images.

We could make a list to compare some of them with Linux.

macOS Linux
/System/Library /lib
/Library /usr/lib, /usr/local/lib
/Users /home
/Volumes /mnt, /media

You may be confused here for the difference between /Library and /usr/lib. Which directory should we put libraries into?

/Library is for macOS-specified software, like the data of things you download from App Store, and BSD programs shipped with macOS, like modules of pre-installed Perl and of pre-installed Python.

/usr/lib and /usr/local/lib are for Unix programs installed by yourself.

Booting

The init program of macOS is launchd5.

The launchd program manages two types of services, LaunchDaemon and LaunchAgent. LaunchDaemon will be started after the system is booted. LaunchAgent will be started after a user is logged-in.

If a program needs to be launched after the system is booted, a .plist file is required to be placed into /Library/LaunchDaemons or /System/LaunchDaemons.

If a program needs to be launched after a user logged-in, a .plist file is required to be placed into /Library/LaunchAgents or /System/LaunchAgents, or ~/Library/LaunchAgents of the user.

The launchctl command is used to control services of launchd.

# to enable sshd
sudo launchctl load -w /System/Library/LaunchDaemons/ssh.plist

# to disable sshd
sudo launchctl unload -w /System/Library/LaunchDaemons/ssh.plist

Development

There is no /usr/include in macOS. Headers are in the package of Xcode. Thus your compiler may complain about it. You can run xcrun --show-sdk-path to print the path6.

$ xcrun --show-sdk-path
/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk

Then you can use -isysroot to specify the path for the compiler, for example:

gcc -isysroot `xcrun --show-sdk-path` hello.c

Package Manager

macOS has no default package manager. Homebrew is the most popular third-party package manager for macOS.

Homebrew is good for many people, but not for one who wants things to go more like common Unix systems. Homebrew assumes you are the only user of the system, but Unix users often share a machine.

Homebrew can’t run as root. If you want to install python, you run brew install python instead of sudo brew install python. Homebrew installs packages to /usr/local. To achieve its sudo-free goal, Homebrew changes the ownership of /usr/local to the user who installed Homebrew.

This brings security issues in a shared system. Considering a malware faking gcc to infect all the code you compile. Without what Homebrew does, it must be run as root to achieve its goal, but now, even it’s run as a normal user, it can write to /usr/local/bin/gcc.

Besides the security issue, Homebrew can bring another issue, if there are multiple administrators in a shared system. Two administrators both want to use Homebrew to install packages, but /usr/local/bin can belong to only one of them. So another administrator will fail. If he or she tries to use Homebrew with sudo, he or she will also fail. Because Homebrew will check that if it’s run as root. If it is, Homebrew will refuse to work unless Homebrew itself belongs to root.

It seems the way to use Homebrew on a shared system is to install Homebrew by root and use it with sudo. However, Homebrew strongly recommends us not to give it the root privilege. The best practice mentioned in the Homebrew documentation is quoted below.

If you need to run Homebrew in a multi-user environment, consider creating a separate user account especially for use of Homebrew.

This means every time we want to use Homebrew, we must switch the account first.

$ sudo -u brewuser brew install python

If you’re not a fan of Homebrew, there are other package managers. MacPorts is one of them and it may be more appealing by Unix users. It requires sudo to install packages and installs them to /opt. Thus you can install self-compiled packages to /usr/local.


  1. Andrew. Managing users and groups from the OS X terminal <http://ajmccluskey.com/2015/01/managing-users-and-groups-from-the-os-x-terminal/>↩︎

  2. How to add user to a group from Mac OS X command line? <https://superuser.com/questions/214004/how-to-add-user-to-a-group-from-mac-os-x-command-line>↩︎

  3. Charles S. Edge. Using sysadminctl on macOS <https://krypted.com/mac-os-x/using-sysadminctl-macos/>↩︎

  4. Jepson; Rothman; Rosen. Mac OS X for Unix Geeks.↩︎

  5. Apple Inc. Creating Launch Daemons and Agents <https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingLaunchdJobs.html>↩︎

  6. Apple Inc. Xcode 10 Release Notes #3035624 <https://developer.apple.com/documentation/xcode_release_notes/xcode_10_release_notes#3035624>↩︎