process.cc revision 13867
12SN/A/*
21762SN/A * Copyright (c) 2014-2016 Advanced Micro Devices, Inc.
32SN/A * Copyright (c) 2012 ARM Limited
42SN/A * All rights reserved
52SN/A *
62SN/A * The license below extends only to copyright in the software and shall
72SN/A * not be construed as granting a license to any other intellectual
82SN/A * property including but not limited to intellectual property relating
92SN/A * to a hardware implementation of the functionality of the software
102SN/A * licensed hereunder.  You may use the software subject to the license
112SN/A * terms below provided that you ensure that this notice is replicated
122SN/A * unmodified and in its entirety in all distributions of the software,
132SN/A * modified or unmodified, in source code or in binary form.
142SN/A *
152SN/A * Copyright (c) 2001-2005 The Regents of The University of Michigan
162SN/A * All rights reserved.
172SN/A *
182SN/A * Redistribution and use in source and binary forms, with or without
192SN/A * modification, are permitted provided that the following conditions are
202SN/A * met: redistributions of source code must retain the above copyright
212SN/A * notice, this list of conditions and the following disclaimer;
222SN/A * redistributions in binary form must reproduce the above copyright
232SN/A * notice, this list of conditions and the following disclaimer in the
242SN/A * documentation and/or other materials provided with the distribution;
252SN/A * neither the name of the copyright holders nor the names of its
262SN/A * contributors may be used to endorse or promote products derived from
272665Ssaidi@eecs.umich.edu * this software without specific prior written permission.
282665Ssaidi@eecs.umich.edu *
292665Ssaidi@eecs.umich.edu * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
302SN/A * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
312SN/A * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
322SN/A * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
332SN/A * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
342SN/A * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
352520SN/A * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
362207SN/A * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
372207SN/A * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
386214Snate@binkert.org * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
392SN/A * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
402519SN/A *
412SN/A * Authors: Nathan Binkert
422SN/A *          Steve Reinhardt
432SN/A *          Ali Saidi
442SN/A *          Brandon Potter
45360SN/A */
46360SN/A
47360SN/A#include "sim/process.hh"
48360SN/A
492207SN/A#include <fcntl.h>
504111Sgblack@eecs.umich.edu#include <unistd.h>
514111Sgblack@eecs.umich.edu
524155Sgblack@eecs.umich.edu#include <array>
535874Sgblack@eecs.umich.edu#include <csignal>
545874Sgblack@eecs.umich.edu#include <map>
555335Shines@cs.fsu.edu#include <string>
56360SN/A#include <vector>
57360SN/A
58360SN/A#include "base/intmath.hh"
59360SN/A#include "base/loader/object_file.hh"
60360SN/A#include "base/loader/symtab.hh"
612207SN/A#include "base/statistics.hh"
622207SN/A#include "config/the_isa.hh"
63360SN/A#include "cpu/thread_context.hh"
64360SN/A#include "mem/page_table.hh"
652SN/A#include "mem/se_translating_port_proxy.hh"
6612SN/A#include "params/Process.hh"
672SN/A#include "sim/emul_driver.hh"
6812SN/A#include "sim/fd_array.hh"
692SN/A#include "sim/fd_entry.hh"
702SN/A#include "sim/syscall_desc.hh"
71360SN/A#include "sim/system.hh"
72360SN/A
73360SN/A#if THE_ISA == ALPHA_ISA
7412SN/A#include "arch/alpha/linux/process.hh"
75360SN/A
76360SN/A#elif THE_ISA == SPARC_ISA
7712SN/A#include "arch/sparc/linux/process.hh"
782SN/A#include "arch/sparc/solaris/process.hh"
792SN/A
802SN/A#elif THE_ISA == MIPS_ISA
812SN/A#include "arch/mips/linux/process.hh"
822SN/A
832520SN/A#elif THE_ISA == ARM_ISA
842520SN/A#include "arch/arm/freebsd/process.hh"
853812Ssaidi@eecs.umich.edu#include "arch/arm/linux/process.hh"
863812Ssaidi@eecs.umich.edu
873812Ssaidi@eecs.umich.edu#elif THE_ISA == X86_ISA
883812Ssaidi@eecs.umich.edu#include "arch/x86/linux/process.hh"
892SN/A
905070Ssaidi@eecs.umich.edu#elif THE_ISA == POWER_ISA
915070Ssaidi@eecs.umich.edu#include "arch/power/linux/process.hh"
923917Ssaidi@eecs.umich.edu
93360SN/A#elif THE_ISA == RISCV_ISA
94360SN/A#include "arch/riscv/linux/process.hh"
95360SN/A
962SN/A#else
972SN/A#error "THE_ISA not set"
9812SN/A#endif
992420SN/A
1002420SN/A
1012420SN/Ausing namespace std;
10212SN/Ausing namespace TheISA;
10312SN/A
10412SN/AProcess::Process(ProcessParams *params, EmulationPageTable *pTable,
10512SN/A                 ObjectFile *obj_file)
10612SN/A    : SimObject(params), system(params->system),
10712SN/A      useArchPT(params->useArchPT),
10812SN/A      kvmInSE(params->kvmInSE),
10912SN/A      useForClone(false),
1102SN/A      pTable(pTable),
1112520SN/A      initVirtMem(system->getSystemPort(), this,
1122472SN/A                  SETranslatingPortProxy::Always),
1132420SN/A      objFile(obj_file),
1142SN/A      argv(params->cmd), envp(params->env), cwd(params->cwd),
11512SN/A      executable(params->executable),
1162472SN/A      _uid(params->uid), _euid(params->euid),
11712SN/A      _gid(params->gid), _egid(params->egid),
1182SN/A      _pid(params->pid), _ppid(params->ppid),
11912SN/A      _pgid(params->pgid), drivers(params->drivers),
12012SN/A      fds(make_shared<FDArray>(params->input, params->output, params->errout)),
12112SN/A      childClearTID(0)
12212SN/A{
12312SN/A    if (_pid >= System::maxPID)
12412SN/A        fatal("_pid is too large: %d", _pid);
12512SN/A
1263584Ssaidi@eecs.umich.edu    auto ret_pair = system->PIDs.emplace(_pid);
1273584Ssaidi@eecs.umich.edu    if (!ret_pair.second)
1282SN/A        fatal("_pid %d is already used", _pid);
1292SN/A
1303584Ssaidi@eecs.umich.edu    /**
1312SN/A     * Linux bundles together processes into this concept called a thread
1322SN/A     * group. The thread group is responsible for recording which processes
1332SN/A     * behave as threads within a process context. The thread group leader
134     * is the process who's tgid is equal to its pid. Other processes which
135     * belong to the thread group, but do not lead the thread group, are
136     * treated as child threads. These threads are created by the clone system
137     * call with options specified to create threads (differing from the
138     * options used to implement a fork). By default, set up the tgid/pid
139     * with a new, equivalent value. If CLONE_THREAD is specified, patch
140     * the tgid value with the old process' value.
141     */
142    _tgid = params->pid;
143
144    exitGroup = new bool();
145    sigchld = new bool();
146
147    if (!debugSymbolTable) {
148        debugSymbolTable = new SymbolTable();
149        if (!objFile->loadGlobalSymbols(debugSymbolTable) ||
150            !objFile->loadLocalSymbols(debugSymbolTable) ||
151            !objFile->loadWeakSymbols(debugSymbolTable)) {
152            delete debugSymbolTable;
153            debugSymbolTable = nullptr;
154        }
155    }
156}
157
158void
159Process::clone(ThreadContext *otc, ThreadContext *ntc,
160               Process *np, RegVal flags)
161{
162#ifndef CLONE_VM
163#define CLONE_VM 0
164#endif
165#ifndef CLONE_FILES
166#define CLONE_FILES 0
167#endif
168#ifndef CLONE_THREAD
169#define CLONE_THREAD 0
170#endif
171    if (CLONE_VM & flags) {
172        /**
173         * Share the process memory address space between the new process
174         * and the old process. Changes in one will be visible in the other
175         * due to the pointer use.
176         */
177        delete np->pTable;
178        np->pTable = pTable;
179        ntc->getMemProxy().setPageTable(np->pTable);
180
181        np->memState = memState;
182    } else {
183        /**
184         * Duplicate the process memory address space. The state needs to be
185         * copied over (rather than using pointers to share everything).
186         */
187        typedef std::vector<pair<Addr,Addr>> MapVec;
188        MapVec mappings;
189        pTable->getMappings(&mappings);
190
191        for (auto map : mappings) {
192            Addr paddr, vaddr = map.first;
193            bool alloc_page = !(np->pTable->translate(vaddr, paddr));
194            np->replicatePage(vaddr, paddr, otc, ntc, alloc_page);
195        }
196
197        *np->memState = *memState;
198    }
199
200    if (CLONE_FILES & flags) {
201        /**
202         * The parent and child file descriptors are shared because the
203         * two FDArray pointers are pointing to the same FDArray. Opening
204         * and closing file descriptors will be visible to both processes.
205         */
206        np->fds = fds;
207    } else {
208        /**
209         * Copy the file descriptors from the old process into the new
210         * child process. The file descriptors entry can be opened and
211         * closed independently of the other process being considered. The
212         * host file descriptors are also dup'd so that the flags for the
213         * host file descriptor is independent of the other process.
214         */
215        for (int tgt_fd = 0; tgt_fd < fds->getSize(); tgt_fd++) {
216            std::shared_ptr<FDArray> nfds = np->fds;
217            std::shared_ptr<FDEntry> this_fde = (*fds)[tgt_fd];
218            if (!this_fde) {
219                nfds->setFDEntry(tgt_fd, nullptr);
220                continue;
221            }
222            nfds->setFDEntry(tgt_fd, this_fde->clone());
223
224            auto this_hbfd = std::dynamic_pointer_cast<HBFDEntry>(this_fde);
225            if (!this_hbfd)
226                continue;
227
228            int this_sim_fd = this_hbfd->getSimFD();
229            if (this_sim_fd <= 2)
230                continue;
231
232            int np_sim_fd = dup(this_sim_fd);
233            assert(np_sim_fd != -1);
234
235            auto nhbfd = std::dynamic_pointer_cast<HBFDEntry>((*nfds)[tgt_fd]);
236            nhbfd->setSimFD(np_sim_fd);
237        }
238    }
239
240    if (CLONE_THREAD & flags) {
241        np->_tgid = _tgid;
242        delete np->exitGroup;
243        np->exitGroup = exitGroup;
244    }
245
246    np->argv.insert(np->argv.end(), argv.begin(), argv.end());
247    np->envp.insert(np->envp.end(), envp.begin(), envp.end());
248}
249
250void
251Process::regStats()
252{
253    SimObject::regStats();
254
255    using namespace Stats;
256
257    numSyscalls
258        .name(name() + ".numSyscalls")
259        .desc("Number of system calls")
260        ;
261}
262
263ThreadContext *
264Process::findFreeContext()
265{
266    for (auto &it : system->threadContexts) {
267        if (ThreadContext::Halted == it->status())
268            return it;
269    }
270    return nullptr;
271}
272
273void
274Process::revokeThreadContext(int context_id)
275{
276    std::vector<ContextID>::iterator it;
277    for (it = contextIds.begin(); it != contextIds.end(); it++) {
278        if (*it == context_id) {
279            contextIds.erase(it);
280            return;
281        }
282    }
283    warn("Unable to find thread context to revoke");
284}
285
286void
287Process::initState()
288{
289    if (contextIds.empty())
290        fatal("Process %s is not associated with any HW contexts!\n", name());
291
292    // first thread context for this process... initialize & enable
293    ThreadContext *tc = system->getThreadContext(contextIds[0]);
294
295    // mark this context as active so it will start ticking.
296    tc->activate();
297
298    pTable->initState(tc);
299}
300
301DrainState
302Process::drain()
303{
304    fds->updateFileOffsets();
305    return DrainState::Drained;
306}
307
308void
309Process::allocateMem(Addr vaddr, int64_t size, bool clobber)
310{
311    int npages = divCeil(size, (int64_t)PageBytes);
312    Addr paddr = system->allocPhysPages(npages);
313    pTable->map(vaddr, paddr, size,
314                clobber ? EmulationPageTable::Clobber :
315                          EmulationPageTable::MappingFlags(0));
316}
317
318void
319Process::replicatePage(Addr vaddr, Addr new_paddr, ThreadContext *old_tc,
320                       ThreadContext *new_tc, bool allocate_page)
321{
322    if (allocate_page)
323        new_paddr = system->allocPhysPages(1);
324
325    // Read from old physical page.
326    uint8_t *buf_p = new uint8_t[PageBytes];
327    old_tc->getMemProxy().readBlob(vaddr, buf_p, PageBytes);
328
329    // Create new mapping in process address space by clobbering existing
330    // mapping (if any existed) and then write to the new physical page.
331    bool clobber = true;
332    pTable->map(vaddr, new_paddr, PageBytes, clobber);
333    new_tc->getMemProxy().writeBlob(vaddr, buf_p, PageBytes);
334    delete[] buf_p;
335}
336
337bool
338Process::fixupStackFault(Addr vaddr)
339{
340    Addr stack_min = memState->getStackMin();
341    Addr stack_base = memState->getStackBase();
342    Addr max_stack_size = memState->getMaxStackSize();
343
344    // Check if this is already on the stack and there's just no page there
345    // yet.
346    if (vaddr >= stack_min && vaddr < stack_base) {
347        allocateMem(roundDown(vaddr, PageBytes), PageBytes);
348        return true;
349    }
350
351    // We've accessed the next page of the stack, so extend it to include
352    // this address.
353    if (vaddr < stack_min && vaddr >= stack_base - max_stack_size) {
354        while (vaddr < stack_min) {
355            stack_min -= TheISA::PageBytes;
356            if (stack_base - stack_min > max_stack_size)
357                fatal("Maximum stack size exceeded\n");
358            allocateMem(stack_min, TheISA::PageBytes);
359            inform("Increasing stack size by one page.");
360        }
361        memState->setStackMin(stack_min);
362        return true;
363    }
364    return false;
365}
366
367void
368Process::serialize(CheckpointOut &cp) const
369{
370    memState->serialize(cp);
371    pTable->serialize(cp);
372    /**
373     * Checkpoints for file descriptors currently do not work. Need to
374     * come back and fix them at a later date.
375     */
376
377    warn("Checkpoints for file descriptors currently do not work.");
378#if 0
379    for (int x = 0; x < fds->getSize(); x++)
380        (*fds)[x].serializeSection(cp, csprintf("FDEntry%d", x));
381#endif
382
383}
384
385void
386Process::unserialize(CheckpointIn &cp)
387{
388    memState->unserialize(cp);
389    pTable->unserialize(cp);
390    /**
391     * Checkpoints for file descriptors currently do not work. Need to
392     * come back and fix them at a later date.
393     */
394    warn("Checkpoints for file descriptors currently do not work.");
395#if 0
396    for (int x = 0; x < fds->getSize(); x++)
397        (*fds)[x]->unserializeSection(cp, csprintf("FDEntry%d", x));
398    fds->restoreFileOffsets();
399#endif
400    // The above returns a bool so that you could do something if you don't
401    // find the param in the checkpoint if you wanted to, like set a default
402    // but in this case we'll just stick with the instantiated value if not
403    // found.
404}
405
406bool
407Process::map(Addr vaddr, Addr paddr, int size, bool cacheable)
408{
409    pTable->map(vaddr, paddr, size,
410                cacheable ? EmulationPageTable::MappingFlags(0) :
411                            EmulationPageTable::Uncacheable);
412    return true;
413}
414
415void
416Process::syscall(int64_t callnum, ThreadContext *tc, Fault *fault)
417{
418    numSyscalls++;
419
420    SyscallDesc *desc = getDesc(callnum);
421    if (desc == nullptr)
422        fatal("Syscall %d out of range", callnum);
423
424    desc->doSyscall(callnum, this, tc, fault);
425}
426
427RegVal
428Process::getSyscallArg(ThreadContext *tc, int &i, int width)
429{
430    return getSyscallArg(tc, i);
431}
432
433EmulatedDriver *
434Process::findDriver(std::string filename)
435{
436    for (EmulatedDriver *d : drivers) {
437        if (d->match(filename))
438            return d;
439    }
440
441    return nullptr;
442}
443
444void
445Process::updateBias()
446{
447    ObjectFile *interp = objFile->getInterpreter();
448
449    if (!interp || !interp->relocatable())
450        return;
451
452    // Determine how large the interpreters footprint will be in the process
453    // address space.
454    Addr interp_mapsize = roundUp(interp->mapSize(), TheISA::PageBytes);
455
456    // We are allocating the memory area; set the bias to the lowest address
457    // in the allocated memory region.
458    Addr mmap_end = memState->getMmapEnd();
459    Addr ld_bias = mmapGrowsDown() ? mmap_end - interp_mapsize : mmap_end;
460
461    // Adjust the process mmap area to give the interpreter room; the real
462    // execve system call would just invoke the kernel's internal mmap
463    // functions to make these adjustments.
464    mmap_end = mmapGrowsDown() ? ld_bias : mmap_end + interp_mapsize;
465    memState->setMmapEnd(mmap_end);
466
467    interp->updateBias(ld_bias);
468}
469
470ObjectFile *
471Process::getInterpreter()
472{
473    return objFile->getInterpreter();
474}
475
476Addr
477Process::getBias()
478{
479    ObjectFile *interp = getInterpreter();
480
481    return interp ? interp->bias() : objFile->bias();
482}
483
484Addr
485Process::getStartPC()
486{
487    ObjectFile *interp = getInterpreter();
488
489    return interp ? interp->entryPoint() : objFile->entryPoint();
490}
491
492Process *
493ProcessParams::create()
494{
495    Process *process = nullptr;
496
497    // If not specified, set the executable parameter equal to the
498    // simulated system's zeroth command line parameter
499    if (executable == "") {
500        executable = cmd[0];
501    }
502
503    ObjectFile *obj_file = createObjectFile(executable);
504    if (obj_file == nullptr) {
505        fatal("Can't load object file %s", executable);
506    }
507
508#if THE_ISA == ALPHA_ISA
509    if (obj_file->getArch() != ObjectFile::Alpha)
510        fatal("Object file architecture does not match compiled ISA (Alpha).");
511
512    switch (obj_file->getOpSys()) {
513      case ObjectFile::UnknownOpSys:
514        warn("Unknown operating system; assuming Linux.");
515        // fall through
516      case ObjectFile::Linux:
517        process = new AlphaLinuxProcess(this, obj_file);
518        break;
519
520      default:
521        fatal("Unknown/unsupported operating system.");
522    }
523#elif THE_ISA == SPARC_ISA
524    if (obj_file->getArch() != ObjectFile::SPARC64 &&
525        obj_file->getArch() != ObjectFile::SPARC32)
526        fatal("Object file architecture does not match compiled ISA (SPARC).");
527    switch (obj_file->getOpSys()) {
528      case ObjectFile::UnknownOpSys:
529        warn("Unknown operating system; assuming Linux.");
530        // fall through
531      case ObjectFile::Linux:
532        if (obj_file->getArch() == ObjectFile::SPARC64) {
533            process = new Sparc64LinuxProcess(this, obj_file);
534        } else {
535            process = new Sparc32LinuxProcess(this, obj_file);
536        }
537        break;
538
539      case ObjectFile::Solaris:
540        process = new SparcSolarisProcess(this, obj_file);
541        break;
542
543      default:
544        fatal("Unknown/unsupported operating system.");
545    }
546#elif THE_ISA == X86_ISA
547    if (obj_file->getArch() != ObjectFile::X86_64 &&
548        obj_file->getArch() != ObjectFile::I386)
549        fatal("Object file architecture does not match compiled ISA (x86).");
550    switch (obj_file->getOpSys()) {
551      case ObjectFile::UnknownOpSys:
552        warn("Unknown operating system; assuming Linux.");
553        // fall through
554      case ObjectFile::Linux:
555        if (obj_file->getArch() == ObjectFile::X86_64) {
556            process = new X86_64LinuxProcess(this, obj_file);
557        } else {
558            process = new I386LinuxProcess(this, obj_file);
559        }
560        break;
561
562      default:
563        fatal("Unknown/unsupported operating system.");
564    }
565#elif THE_ISA == MIPS_ISA
566    if (obj_file->getArch() != ObjectFile::Mips)
567        fatal("Object file architecture does not match compiled ISA (MIPS).");
568    switch (obj_file->getOpSys()) {
569      case ObjectFile::UnknownOpSys:
570        warn("Unknown operating system; assuming Linux.");
571        // fall through
572      case ObjectFile::Linux:
573        process = new MipsLinuxProcess(this, obj_file);
574        break;
575
576      default:
577        fatal("Unknown/unsupported operating system.");
578    }
579#elif THE_ISA == ARM_ISA
580    ObjectFile::Arch arch = obj_file->getArch();
581    if (arch != ObjectFile::Arm && arch != ObjectFile::Thumb &&
582        arch != ObjectFile::Arm64)
583        fatal("Object file architecture does not match compiled ISA (ARM).");
584    switch (obj_file->getOpSys()) {
585      case ObjectFile::UnknownOpSys:
586        warn("Unknown operating system; assuming Linux.");
587        // fall through
588      case ObjectFile::Linux:
589        if (arch == ObjectFile::Arm64) {
590            process = new ArmLinuxProcess64(this, obj_file,
591                                            obj_file->getArch());
592        } else {
593            process = new ArmLinuxProcess32(this, obj_file,
594                                            obj_file->getArch());
595        }
596        break;
597      case ObjectFile::FreeBSD:
598        if (arch == ObjectFile::Arm64) {
599            process = new ArmFreebsdProcess64(this, obj_file,
600                                              obj_file->getArch());
601        } else {
602            process = new ArmFreebsdProcess32(this, obj_file,
603                                              obj_file->getArch());
604        }
605        break;
606      case ObjectFile::LinuxArmOABI:
607        fatal("M5 does not support ARM OABI binaries. Please recompile with an"
608              " EABI compiler.");
609      default:
610        fatal("Unknown/unsupported operating system.");
611    }
612#elif THE_ISA == POWER_ISA
613    if (obj_file->getArch() != ObjectFile::Power)
614        fatal("Object file architecture does not match compiled ISA (Power).");
615    switch (obj_file->getOpSys()) {
616      case ObjectFile::UnknownOpSys:
617        warn("Unknown operating system; assuming Linux.");
618        // fall through
619      case ObjectFile::Linux:
620        process = new PowerLinuxProcess(this, obj_file);
621        break;
622
623      default:
624        fatal("Unknown/unsupported operating system.");
625    }
626#elif THE_ISA == RISCV_ISA
627    ObjectFile::Arch arch = obj_file->getArch();
628    if (arch != ObjectFile::Riscv64 && arch != ObjectFile::Riscv32)
629        fatal("Object file architecture does not match compiled ISA (RISCV).");
630    switch (obj_file->getOpSys()) {
631      case ObjectFile::UnknownOpSys:
632        warn("Unknown operating system; assuming Linux.");
633        // fall through
634      case ObjectFile::Linux:
635        if (arch == ObjectFile::Riscv64) {
636            process = new RiscvLinuxProcess64(this, obj_file);
637        } else {
638            process = new RiscvLinuxProcess32(this, obj_file);
639        }
640        break;
641      default:
642        fatal("Unknown/unsupported operating system.");
643    }
644#else
645#error "THE_ISA not set"
646#endif
647
648    if (process == nullptr)
649        fatal("Unknown error creating process object.");
650    return process;
651}
652
653std::string
654Process::fullPath(const std::string &file_name)
655{
656    if (file_name[0] == '/' || cwd.empty())
657        return file_name;
658
659    std::string full = cwd;
660
661    if (cwd[cwd.size() - 1] != '/')
662        full += '/';
663
664    return full + file_name;
665}
666