23

I'm trying to change my process' name as it appears in ps and Activity Monitor at runtime. I found several notes that there is no portable way to do this (which I don't care about).

Here's what I tried. None of these approaches worked for me.

  • Changing argv[0] (seems to be the way to go on some Unix systems)
  • Calling [[NSProcessInfo processInfo] setProcessName:@"someName"]
  • Calling setprogname (calling getprogname returns the name I set, but that is irrelevant)

I also read about a function called setproctitle which should be defined in stdlib.h if it is available, but it's not there.

There must be a way to accomplish this because QTKitServer - the faceless decoder for QuickTime Player X - has its corresponding QuickTime Player's PID in its process name.

Does anybody have a clue about how to accomplish this? I'd very much prefer a Core Foundation or POSIXy way over an Objective-C method to do this.

Thanks,

Marco

Edit: If it is in any way relevant, I'm using Mac OS X 10.6.5 and Xcode 3.2.5

Alex Brown
  • 41,819
  • 10
  • 94
  • 108
Marco Masser
  • 1,076
  • 1
  • 8
  • 14
  • 1
    @ Alex Brown (who edited the tags): I deliberately set the tag for this question to "Mac" instead of "posix" because the POSIXy-ways I found to do this didn't work for me (`argv[0]` and `setproctitle()`). – Marco Masser Nov 20 '10 at 11:40

2 Answers2

13

There are good reasons to change the process name. Java software should change process names because when running different java tools I want to see which java process is for which tool.

Chromium does it: http://src.chromium.org/viewvc/chrome/trunk/src/base/mac/mac_util.mm.

Node.js uses same code to implement Process.title = 'newtitle': https://github.com/joyent/node/blob/master/src/platform_darwin_proctitle.cc

Note: This fails if someone does su to a different not logged-in user: https://github.com/joyent/node/issues/1727

Here the source code in its full complex glory. By the way, someone told me it also works for Mac OS X Lion and also fails with su.

// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
void SetProcessName(CFStringRef process_name) {
  if (!process_name || CFStringGetLength(process_name) == 0) {
    NOTREACHED() << "SetProcessName given bad name.";
    return;
  }

  if (![NSThread isMainThread]) {
    NOTREACHED() << "Should only set process name from main thread.";
    return;
  }

  // Warning: here be dragons! This is SPI reverse-engineered from WebKit's
  // plugin host, and could break at any time (although realistically it's only
  // likely to break in a new major release).
  // When 10.7 is available, check that this still works, and update this
  // comment for 10.8.

  // Private CFType used in these LaunchServices calls.
  typedef CFTypeRef PrivateLSASN;
  typedef PrivateLSASN (*LSGetCurrentApplicationASNType)();
  typedef OSStatus (*LSSetApplicationInformationItemType)(int, PrivateLSASN,
                                                          CFStringRef,
                                                          CFStringRef,
                                                          CFDictionaryRef*);

  static LSGetCurrentApplicationASNType ls_get_current_application_asn_func =
      NULL;
  static LSSetApplicationInformationItemType
      ls_set_application_information_item_func = NULL;
  static CFStringRef ls_display_name_key = NULL;

  static bool did_symbol_lookup = false;
  if (!did_symbol_lookup) {
    did_symbol_lookup = true;
    CFBundleRef launch_services_bundle =
        CFBundleGetBundleWithIdentifier(CFSTR("com.apple.LaunchServices"));
    if (!launch_services_bundle) {
      LOG(ERROR) << "Failed to look up LaunchServices bundle";
      return;
    }

    ls_get_current_application_asn_func =
        reinterpret_cast<LSGetCurrentApplicationASNType>(
            CFBundleGetFunctionPointerForName(
                launch_services_bundle, CFSTR("_LSGetCurrentApplicationASN")));
    if (!ls_get_current_application_asn_func)
      LOG(ERROR) << "Could not find _LSGetCurrentApplicationASN";

    ls_set_application_information_item_func =
        reinterpret_cast<LSSetApplicationInformationItemType>(
            CFBundleGetFunctionPointerForName(
                launch_services_bundle,
                CFSTR("_LSSetApplicationInformationItem")));
    if (!ls_set_application_information_item_func)
      LOG(ERROR) << "Could not find _LSSetApplicationInformationItem";

    CFStringRef* key_pointer = reinterpret_cast<CFStringRef*>(
        CFBundleGetDataPointerForName(launch_services_bundle,
                                      CFSTR("_kLSDisplayNameKey")));
    ls_display_name_key = key_pointer ? *key_pointer : NULL;
    if (!ls_display_name_key)
      LOG(ERROR) << "Could not find _kLSDisplayNameKey";

    // Internally, this call relies on the Mach ports that are started up by the
    // Carbon Process Manager.  In debug builds this usually happens due to how
    // the logging layers are started up; but in release, it isn't started in as
    // much of a defined order.  So if the symbols had to be loaded, go ahead
    // and force a call to make sure the manager has been initialized and hence
    // the ports are opened.
    ProcessSerialNumber psn;
    GetCurrentProcess(&psn);
  }
  if (!ls_get_current_application_asn_func ||
      !ls_set_application_information_item_func ||
      !ls_display_name_key) {
    return;
  }

  PrivateLSASN asn = ls_get_current_application_asn_func();
  // Constant used by WebKit; what exactly it means is unknown.
  const int magic_session_constant = -2;
  OSErr err =
      ls_set_application_information_item_func(magic_session_constant, asn,
                                               ls_display_name_key,
                                               process_name,
                                               NULL /* optional out param */);
  LOG_IF(ERROR, err) << "Call to set process name failed, err " << err;
}

Edit: It's a complex and confusing problem.

On OS X there is no setproctitle(3). One has to write into the argv array (ugly and a bit dangerous because it is possible to overwrite some environment variables with bogus stuff). Done correctly it works very well.

Additionally Apple has the ActivityMonitor application, something like the Task Manager under Windows. The code above manipulates ActivityMonitor but this manipulation doesn't seem to be encouraged by Apple (hence the use of undocumented functions).

Important: ps and ActivityMonitor don't show the same information.

Also important: ActivityMonitor is not available if you don't have GUI. This can happen if you ssh in to a remote Apple box and there is nobody logged in by GUI. Sadly there is a bug by Apple IMO. Just querying if there is a GUI sends an annoying warning message to stderr.

Summary: If you need to change ActivityMonitor, use the code above. If you have GUI-less situations and and dislike warnings on stderr, redirect stderr temporarily to /dev/null during the call of SetProcessName. If you need to change ps information, write into argv.

tbodt
  • 16,609
  • 6
  • 58
  • 83
nalply
  • 26,770
  • 15
  • 78
  • 101
  • 1
    By the way, is your project open source? If not, think about copyright because the code belongs to the Chromium Project. – nalply Sep 19 '11 at 10:04
  • No, the project I need that for is not open source. But don't worry, I won't copy the code as it is. What I was looking for is essentially the information that calling _LSSetApplicationInformationItem() gives me what I want. The code I'll write around that function will look quite different in the context of our own library and should therefore not violate the GPL. – Marco Masser Sep 19 '11 at 12:58
  • Hmmm... I played around with that some more and it seems like some mechanisms don't pick up that change at all. `[[NSProcessInfo processInfo] processName]` for example still has the old name. I don't know where that gets its information from, but if there are such inconsistencies, I don't feel comfortable enough to use that code. Anyways, thanks again! – Marco Masser Sep 19 '11 at 13:50
  • It is partially undocumented code directly from Apple. No wonder Chromium developers say: Warning: here be dragons! Also it is a common convention that names starting with underscore like `_LSSetApplicationInformationItem` are private. It's a pity that Apple had to cook something proprietary with Activity Monitor. – nalply Sep 20 '11 at 09:53
  • This was removed from Chromium. From the revision log: 'This involves Process Manager functions, which in 10.9 cause a WindowServer checkin, which causes "not responding" notifications on any child process that doesn't pump events (which is everything but the renderers).' – whoKnows May 04 '19 at 22:28
  • Ugh. One more nail in the coffin. – nalply May 05 '19 at 07:12
7

You can use the lsappinfo tool which comes with macOS since at least 10.6 and up to present day (10.13.2):

Shell:

lsappinfo setinfo <PID> --name <NAME>

C++:

#include <sstream>
#include <string>
#include <stdlib.h>

void setProcessName (pid_t pid, std::string name)
{
    std::ostringstream cmd;
    cmd << "/usr/bin/lsappinfo setinfo " << pid;
    cmd << " --name \"" << name << "\"";
    system (cmd.str().c_str());
}
yairchu
  • 23,680
  • 7
  • 69
  • 109
  • 1
    Wow, I didn’t know about this command, seems pretty interesting. Setting the name like this works like a charm! `lsappinfo` seems to be a front-end for the functionality described by @nalply’s code. More than seven years later, the need for doing that is gone, though – Marco Masser Mar 06 '18 at 09:58
  • I was afraid the code in the previous answer might stop working or require maintenance, so after seeing that answer I kept looking and found about `lsappinfo`. Hopefully by being a public API rather than a "secret API" this tool will keep working well in future OSs :) – yairchu Mar 06 '18 at 11:14
  • 3
    I tried using it for renaming a Python process without success. The output of `lsappinfo processList` only lists graphical applications, not processes running on a shell, so I think it only works for that kind of app. – hdiogenes Apr 24 '18 at 18:02
  • 4
    I see `err=-600` on Catalina – ldeck Feb 09 '20 at 13:25
  • 1
    lsappinfo changes name only when you run it with sudo. – Alexander Artemenko Apr 25 '20 at 11:52
  • 7
    I think this will only work with GUI apps, not with scripts run from the terminal, for those you will get a err=-600 meaning not a valid PID – Jan M May 20 '20 at 12:09
  • 1
    Tested just now on MacOS 12.6. If the process already appears in the list given by `lsappinfo` then you can change the name with `lsappinfo setinfo '65432' -name "my-app-name"`. For a Java app this means if it was started using system java you can change the name but if by homebrew java you can't. – Ben Oct 02 '22 at 16:30