This code searches a directory for a filename. If the file is not found, the parent directory is searched. It continues searching upwards until the file is found or there are no more directories to search.

If the file is found its full path is returned, otherwise NIL is returned.

(defun sodaware/file-search-upward (directory file)
  "Search DIRECTORY for FILE and return its full path if found, or NIL if not.

If FILE is not found in DIRECTORY, the parent of DIRECTORY will be searched."
  (let ((parent-dir (file-truename (concat (file-name-directory directory) "../")))
        (current-path (if (not (string= (substring directory (- (length directory) 1)) "/"))
                         (concat directory "/" file)
                         (concat directory file))))
    (if (file-exists-p current-path)
        (when (and (not (string= (file-truename directory) parent-dir))
                   (< (length parent-dir) (length (file-truename directory))))
          (sodaware/file-search-upward parent-dir file)))))

Example usage

Given the following directory structure:

|-- dir1
    |-- my_file.txt
    |-- dir2
        |-- dir3

Searching dir3 will also search dir2 and dir1:

;; Finding a file that exists in a parent directory.
(sodaware/file-search-upward "/dir1/dir2/dir3/" "my_file.txt")
=> "/dir1/my_file.txt"

;; Failing to find a file.
(sodaware/file-search-upward "/dir1/dir2/dir3/" "not_found.txt")
=> NIL