3

I have been using Terminal to change the "date added" attribute of a file without success.

What I'm doing is:

  1. Display the attributes of a file:

    mdls file_name

  2. Display the "date added" attribute:

    xattr -p "kMDItemDateAdded" file_name

  3. Change it:

    sudo xattr -w "kMDItemDateAdded" "201111111111.11" file_name

The third instruction does not work! Whereas touching the file with the same date value DOES change date modified.

I'm running Yosemite. Any ideas from the community?

Thomas Dickey
  • 51,086
  • 7
  • 70
  • 105
  • Show what is displayed as a result of your commands. In particular, I'd be surprised if step 2 displayed an actual attribute. The date added is not stored in an extended attribute, so `xattr` should have nothing to do with it. Also, try `ls -l@ ` and show the output of that. (Note, there would, of course, be an extended attribute named `kMDItemDateAdded` after you've done `xattr -w kMDItemDateAdded ...` on a file, which may be fooling you in step 2. Try it on a new file.) – Ken Thomases Sep 25 '16 at 15:27
  • 1
    I will also point out that `setattrlist()`, Apple's consolidated system call for modifying file metadata, doesn't allow setting `ATTR_CMN_ADDEDTIME`, although `getattrlist()` can query it. Similarly, `NSURL`'s `NSURLAddedToDirectoryDateKey` resource key is documented as read-only. It seems that the date added can't be set directly. You would have to move the file out of its directory and then back to change it and you could only change it to the current time. – Ken Thomases Sep 25 '16 at 15:36
  • Hi Ken, Here is the result of: `touch a; mdls a;` kMDItemContentCreationDate = 2016-09-25 4:20:45 pm +0000 kMDItemContentModificationDate = 2016-09-25 4:20:45 pm +0000 kMDItemContentType = "public.data" kMDItemContentTypeTree = ( "public.data", "public.item" ) kMDItemDateAdded = 2016-09-25 4:20:45 pm +0000 kMDItemDisplayName = "a" kMDItemFSContentChangeDate = 2016-09-25 4:20:45 pm +0000 kMDItemFSCreationDate = 2016-09-25 4:20:45 pm +0000 kMDItemFSCreatorCode = "" kMDItemFSFinderFlags = 0 –  Sep 25 '16 at 16:23
  • Sorry for the mess. Probably like you said (read only).. It is being set by the file system and there is no access for the user to it. –  Sep 25 '16 at 16:31
  • First, don't put that stuff in comments. Edit your question and add the details. Second, I was asking about the output of `xattr -p kMDItemDateAdded` on a new file, not about `mdls`. I don't believe that the date added is an extended attribute. It certainly isn't on my Mac. – Ken Thomases Sep 25 '16 at 16:56
  • Yes you're right, you can't display it with 'xattr', but 'mdls' does show this attribute. So are you implying that xattr can't edit attributes shown with 'mdls'? –  Sep 27 '16 at 21:46
  • `mdls` doesn't show extended attributes as such. It shows metadata properties. Those are a completely different thing. Some of them may be stored in the file system metadata for the file, others may be derived from extended attributes (although I can't think of any off hand), and still others may be derived from other things (such as the content type being derived from the file extension and the Launch Services database). – Ken Thomases Sep 28 '16 at 00:21
  • Thank you Ken, this explanation makes much more sense. Do you know about any other way one can change those metadata properties? –  Sep 30 '16 at 20:38
  • Well, since they are derived, you don't change them directly. You change whatever it is they are based on. For example, you can change the content type by changing a file's extension (using `mv` or whatever). To change the modification date, you can use `touch`. Etc. As discussed, I don't think there's a way to change the date added, except to the current time. – Ken Thomases Sep 30 '16 at 20:59

1 Answers1

1

Thanks to Ken Thomases above for pointing me in the direction of setattrlist; the following code works for me on macOS Monterey 12.5.1.

#include <stdlib.h>
#include <string.h>
#include <sys/attr.h>
#include <unistd.h>

/*
 * Get kMDItemDateAdded of path.
 *
 * Returns:
 *   • 0 on success
 *   • 1 if a system call failed: check errno
 *   • 2 if something else went wrong
 */
int get_date_added(const char* path, struct timespec * out) {
    attrgroup_t request_attrs = ATTR_CMN_RETURNED_ATTRS | ATTR_CMN_ADDEDTIME;

    struct attrlist request;
    memset(&request, 0, sizeof(request));
    request.bitmapcount = ATTR_BIT_MAP_COUNT;
    request.commonattr = request_attrs;

    typedef struct {
        u_int32_t length;
        attribute_set_t returned;
        struct timespec added;
    } __attribute__((aligned(4), packed)) response_buf_t;

    response_buf_t response;

    int err = getattrlist(path, &request, &response, sizeof(response), 0);
    if (err != 0) {
        return 1;
    }
    if (response.length != sizeof(response)) {
        // Need a different-sized buffer; but provided one of exactly required
        // size?!
        return 2;
    }
    if (response.returned.commonattr != request_attrs) {
        // Didn’t get back all requested common attributes
        return 2;
    }

    out->tv_sec = response.added.tv_sec;
    out->tv_nsec = response.added.tv_nsec;

    return 0;
}

/*
 * Set kMDItemDateAdded of path.
 *
 * Returns:
 *   • 0 on success
 *   • 1 if a system call failed: check errno
 */
int set_date_added(const char* path, struct timespec in) {
    attrgroup_t request_attrs = ATTR_CMN_ADDEDTIME;

    struct attrlist request;
    memset(&request, 0, sizeof(request));
    request.bitmapcount = ATTR_BIT_MAP_COUNT;
    request.commonattr = request_attrs;

    typedef struct {
        struct timespec added;
    } __attribute__((aligned(4), packed)) request_buf_t;

    request_buf_t request_buf;
    request_buf.added.tv_sec = in.tv_sec;
    request_buf.added.tv_nsec = in.tv_nsec;

    int err = setattrlist(path, &request, &request_buf, sizeof(request_buf), 0);
    if (err != 0) {
        return 1;
    }

    return 0;
}
andrewdotn
  • 32,721
  • 10
  • 101
  • 130
  • Hi, could you explain how to use this please? 1. how to make this code actually run and 2. how to call it? – DavidT Oct 31 '22 at 21:46
  • This is C code; you could add it to an existing C program. As a quick hack, you could save it as `program.c` and at the end add the tiny C program: `#include `, `int main() {`, ` struct timespec t = { 1234567890 };`, ` printf("Returned %d\n", set_date_added("foo", t));`, `}`. Then in the terminal run `cc -o program program.c` and `./program`. That’ll change the added date of a file `foo` in the current directory. You can change the filename and timestamp in the program, re-compile, and re-run. Search the web for ‘unix timestamp’ for how the ‘1234567890’ date gets interpreted. – andrewdotn Nov 13 '22 at 16:01
  • oops the code quoting there got mangled, should say: (each on separate lines) `#include `, `int main() {`, `struct timespec t = { 1234567890 };`, `printf("Returned %d\n", set_date_added("foo", t));`, `}`. Then in the terminal run `cc -o program program.c` and `./program` – andrewdotn Nov 15 '22 at 15:26