Implement the Linux cp Command, Then Explain Its OS and C++ Internals

Quick Overview

A low-level engineering exercise to implement the Linux cp command in C++ and then explain what happens beneath it. It tests system calls, short writes and error handling, the page cache and kernel-assisted copying, and RAII-based management of file descriptors.

Implement the Linux cp Command, Then Explain Its OS and C++ Internals

Company: Microsoft

Role: Software Engineer

Category: Software Engineering Fundamentals

Difficulty: medium

Interview Round: Onsite

How would you implement the Linux `cp` command? Write it, then be ready for the discussion to go deep into the operating-system and C++ concepts underneath your implementation. ### Clarifying Questions - Which interfaces may I use: POSIX system calls, the C standard library, or C++17 `std::filesystem`? - Should the first version handle only regular files, or also directories (`-r`), symbolic links and special files? - If DEST already exists, should it be overwritten silently, or should the program ask or fail? - Must permissions, ownership and timestamps be preserved, as `cp -p` does? ### Part 1 — Implement the basic copy Write a C++ program, invoked as `cp SOURCE DEST`, that copies a regular file. DEST may be a new path, an existing file, or an existing directory. Handle the errors a real command-line tool has to handle, and exit with a non-zero status on failure. ```hint Think in system calls Decide which calls you need to open, read, write and close the files, and what each one can return besides full success. ``` ```hint Look closely at DEST before writing to it DEST may already exist, may be a directory, or may even be SOURCE itself under another name. ``` #### What This Part Should Cover - A correct copy loop, including partial writes, interrupted calls and end of file - Handling of an existing file, a directory target, and SOURCE and DEST being the same file - Clear error messages and release of every resource on every path ### Part 2 — What happens underneath Walk through what the operating system does while your program runs: from each system call into the kernel, through the file system and its caches, down to the disk. Then explain how you would make the copy faster for very large files. ```hint Count the copies and the crossings Follow one block of data and count how many times it crosses between user space and the kernel, and how many times it is copied in memory. ``` ```hint Ask whether your program needs the bytes at all Consider what the kernel or the file system could do if the data never had to pass through your buffer. ``` #### What This Part Should Cover - User space and kernel space, the cost of a system call, and the effect of the buffer size - The page cache, read-ahead and write-back, and what a successful `write` actually guarantees - Kernel-assisted copying and when each option applies or falls back ### Part 3 — Make it robust C++ Now treat the code as production C++. How do you guarantee that file descriptors are always released and that errors are reported correctly? What would it take to support directories, symbolic links and preserving metadata, and to avoid leaving a half-written DEST behind when the copy fails? ```hint Let object lifetime do the cleanup Think about tying the release of a descriptor to the end of an object's lifetime, and what copying such an object should mean. ``` ```hint Decide what a failure leaves behind If the copy fails halfway, think about what the user finds at DEST afterwards. ``` #### What This Part Should Cover - Descriptor ownership through RAII, including copy and move semantics - An error model (exceptions or error codes) and when `errno` must be read - Recursive copy, symbolic links, metadata, and never leaving a half-written file behind ### What a Strong Answer Covers - Working code that is correct on the error paths, not only the happy path - Accurate operating-system mechanics tied to the performance choices in the code - Sound C++ resource management - Explicit trade-offs, such as portability against speed and in-place overwrite against atomic replacement ### Follow-up Questions - How would you make the new DEST appear atomically to other processes reading it? - How do you copy a sparse file without filling its holes with zeros? - What does your program produce if another process is writing to SOURCE during the copy? - How does moving a file differ from copying it when the source and destination are on the same file system, and when they are not?

Overview: A low-level engineering exercise to implement the Linux cp command in C++ and then explain what happens beneath it. It tests system calls, short writes and error handling, the page cache and kernel-assisted copying, and RAII-based management of file descriptors.

|Home/Software Engineering Fundamentals/Microsoft
Microsoft logo
Microsoft
Sep 6, 2026
mediumSoftware EngineerOnsiteSoftware Engineering Fundamentals
0
0

How would you implement the Linux cp command? Write it, then be ready for the discussion to go deep into the operating-system and C++ concepts underneath your implementation.

Clarifying Questions Guidance

  • Which interfaces may I use: POSIX system calls, the C standard library, or C++17 std::filesystem ?
  • Should the first version handle only regular files, or also directories ( -r ), symbolic links and special files?
  • If DEST already exists, should it be overwritten silently, or should the program ask or fail?
  • Must permissions, ownership and timestamps be preserved, as cp -p does?

Part 1 — Implement the basic copy

Write a C++ program, invoked as cp SOURCE DEST, that copies a regular file. DEST may be a new path, an existing file, or an existing directory. Handle the errors a real command-line tool has to handle, and exit with a non-zero status on failure.

What This Part Should Cover Guidance

  • A correct copy loop, including partial writes, interrupted calls and end of file
  • Handling of an existing file, a directory target, and SOURCE and DEST being the same file
  • Clear error messages and release of every resource on every path

Part 2 — What happens underneath

Walk through what the operating system does while your program runs: from each system call into the kernel, through the file system and its caches, down to the disk. Then explain how you would make the copy faster for very large files.

What This Part Should Cover Guidance

  • User space and kernel space, the cost of a system call, and the effect of the buffer size
  • The page cache, read-ahead and write-back, and what a successful write actually guarantees
  • Kernel-assisted copying and when each option applies or falls back

Part 3 — Make it robust C++

Now treat the code as production C++. How do you guarantee that file descriptors are always released and that errors are reported correctly? What would it take to support directories, symbolic links and preserving metadata, and to avoid leaving a half-written DEST behind when the copy fails?

What This Part Should Cover Guidance

  • Descriptor ownership through RAII, including copy and move semantics
  • An error model (exceptions or error codes) and when errno must be read
  • Recursive copy, symbolic links, metadata, and never leaving a half-written file behind

What a Strong Answer Covers Guidance

  • Working code that is correct on the error paths, not only the happy path
  • Accurate operating-system mechanics tied to the performance choices in the code
  • Sound C++ resource management
  • Explicit trade-offs, such as portability against speed and in-place overwrite against atomic replacement

Follow-up Questions Guidance

  • How would you make the new DEST appear atomically to other processes reading it?
  • How do you copy a sparse file without filling its holes with zeros?
  • What does your program produce if another process is writing to SOURCE during the copy?
  • How does moving a file differ from copying it when the source and destination are on the same file system, and when they are not?
Loading comments...